From 86280c38fd0508e08b4392cfa68341b3fb96ce10 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 14 Nov 2013 21:30:37 -0800 Subject: [PATCH 001/703] bootstrap: drop 'lua-' prefix from project name. * configure.ac (AC_INIT): Set name to 'stdlib'. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- configure.ac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6811576..2392a15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: erlang env: global: - - PACKAGE=lua-stdlib + - PACKAGE=stdlib - ROCKSPEC=$PACKAGE-git-1.rockspec - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - LUAROCKS_BASE=luarocks-2.0.13 diff --git a/configure.ac b/configure.ac index f2ab2d4..33e995e 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script dnl Initialise autoconf and automake -AC_INIT([lua-stdlib], [36], [http://github.com/rrthomas/lua-stdlib/issues]) +AC_INIT([stdlib], [36], [http://github.com/rrthomas/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) From 4d51ca63a1eedd3759201db8b3df850a658856e6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 14 Nov 2013 23:16:24 -0800 Subject: [PATCH 002/703] maint: move lua extension sources to $top_srcdir/ext. For consistency with other Lua projects I maintain, rename the lib directory to ext. * lib/: Rename to ext/. * local.mk (std_path, dist_lua_DATA, dist_luastd_DATA) (mkrockspecs_args, EXTRA_DIST, dist_doc_DATA, dist_files_DATA) (dist_modules_DATA): Adjust. * .gitignore: Update. Signed-off-by: Gary V. Vaughan --- .gitignore | 12 +++---- {lib => ext}/std.lua.in | 0 {lib => ext}/std/base.lua | 0 {lib => ext}/std/debug.lua | 0 {lib => ext}/std/debug_init.lua | 0 {lib => ext}/std/functional.lua | 0 {lib => ext}/std/getopt.lua | 0 {lib => ext}/std/io.lua | 0 {lib => ext}/std/list.lua | 0 {lib => ext}/std/math.lua | 0 {lib => ext}/std/modules.lua | 0 {lib => ext}/std/object.lua | 0 {lib => ext}/std/package.lua | 0 {lib => ext}/std/set.lua | 0 {lib => ext}/std/strbuf.lua | 0 {lib => ext}/std/strict.lua | 0 {lib => ext}/std/string.lua | 0 {lib => ext}/std/table.lua | 0 {lib => ext}/std/tree.lua | 0 local.mk | 56 ++++++++++++++++----------------- 20 files changed, 34 insertions(+), 34 deletions(-) rename {lib => ext}/std.lua.in (100%) rename {lib => ext}/std/base.lua (100%) rename {lib => ext}/std/debug.lua (100%) rename {lib => ext}/std/debug_init.lua (100%) rename {lib => ext}/std/functional.lua (100%) rename {lib => ext}/std/getopt.lua (100%) rename {lib => ext}/std/io.lua (100%) rename {lib => ext}/std/list.lua (100%) rename {lib => ext}/std/math.lua (100%) rename {lib => ext}/std/modules.lua (100%) rename {lib => ext}/std/object.lua (100%) rename {lib => ext}/std/package.lua (100%) rename {lib => ext}/std/set.lua (100%) rename {lib => ext}/std/strbuf.lua (100%) rename {lib => ext}/std/strict.lua (100%) rename {lib => ext}/std/string.lua (100%) rename {lib => ext}/std/table.lua (100%) rename {lib => ext}/std/tree.lua (100%) diff --git a/.gitignore b/.gitignore index a5c98a2..c29c5d0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,12 +13,12 @@ /config.log /config.status /configure -/lib/files -/lib/index.html -/lib/luadoc.css -/lib/modules -/lib/std.lua -/lua-stdlib-*.tar.gz +/ext/files +/ext/index.html +/ext/luadoc.css +/ext/modules +/ext/std.lua +/stdlib-*.tar.gz /luarocks /m4/ax_compare_version.m4 /m4/ax_lua.m4 diff --git a/lib/std.lua.in b/ext/std.lua.in similarity index 100% rename from lib/std.lua.in rename to ext/std.lua.in diff --git a/lib/std/base.lua b/ext/std/base.lua similarity index 100% rename from lib/std/base.lua rename to ext/std/base.lua diff --git a/lib/std/debug.lua b/ext/std/debug.lua similarity index 100% rename from lib/std/debug.lua rename to ext/std/debug.lua diff --git a/lib/std/debug_init.lua b/ext/std/debug_init.lua similarity index 100% rename from lib/std/debug_init.lua rename to ext/std/debug_init.lua diff --git a/lib/std/functional.lua b/ext/std/functional.lua similarity index 100% rename from lib/std/functional.lua rename to ext/std/functional.lua diff --git a/lib/std/getopt.lua b/ext/std/getopt.lua similarity index 100% rename from lib/std/getopt.lua rename to ext/std/getopt.lua diff --git a/lib/std/io.lua b/ext/std/io.lua similarity index 100% rename from lib/std/io.lua rename to ext/std/io.lua diff --git a/lib/std/list.lua b/ext/std/list.lua similarity index 100% rename from lib/std/list.lua rename to ext/std/list.lua diff --git a/lib/std/math.lua b/ext/std/math.lua similarity index 100% rename from lib/std/math.lua rename to ext/std/math.lua diff --git a/lib/std/modules.lua b/ext/std/modules.lua similarity index 100% rename from lib/std/modules.lua rename to ext/std/modules.lua diff --git a/lib/std/object.lua b/ext/std/object.lua similarity index 100% rename from lib/std/object.lua rename to ext/std/object.lua diff --git a/lib/std/package.lua b/ext/std/package.lua similarity index 100% rename from lib/std/package.lua rename to ext/std/package.lua diff --git a/lib/std/set.lua b/ext/std/set.lua similarity index 100% rename from lib/std/set.lua rename to ext/std/set.lua diff --git a/lib/std/strbuf.lua b/ext/std/strbuf.lua similarity index 100% rename from lib/std/strbuf.lua rename to ext/std/strbuf.lua diff --git a/lib/std/strict.lua b/ext/std/strict.lua similarity index 100% rename from lib/std/strict.lua rename to ext/std/strict.lua diff --git a/lib/std/string.lua b/ext/std/string.lua similarity index 100% rename from lib/std/string.lua rename to ext/std/string.lua diff --git a/lib/std/table.lua b/ext/std/table.lua similarity index 100% rename from lib/std/table.lua rename to ext/std/table.lua diff --git a/lib/std/tree.lua b/ext/std/tree.lua similarity index 100% rename from lib/std/tree.lua rename to ext/std/tree.lua diff --git a/local.mk b/local.mk index 3dd9a4b..814f4e2 100644 --- a/local.mk +++ b/local.mk @@ -4,7 +4,7 @@ ## Environment. ## ## ------------ ## -std_path = $(abs_srcdir)/lib/?.lua +std_path = $(abs_srcdir)/ext/?.lua LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" @@ -34,41 +34,41 @@ include specs/specs.mk ## ------ ## dist_lua_DATA += \ - lib/std.lua \ + ext/std.lua \ $(NOTHING_ELSE) luastddir = $(luadir)/std dist_luastd_DATA = \ - lib/std/base.lua \ - lib/std/debug.lua \ - lib/std/debug_init.lua \ - lib/std/functional.lua \ - lib/std/getopt.lua \ - lib/std/io.lua \ - lib/std/list.lua \ - lib/std/math.lua \ - lib/std/modules.lua \ - lib/std/object.lua \ - lib/std/package.lua \ - lib/std/set.lua \ - lib/std/strbuf.lua \ - lib/std/strict.lua \ - lib/std/string.lua \ - lib/std/table.lua \ - lib/std/tree.lua \ + ext/std/base.lua \ + ext/std/debug.lua \ + ext/std/debug_init.lua \ + ext/std/functional.lua \ + ext/std/getopt.lua \ + ext/std/io.lua \ + ext/std/list.lua \ + ext/std/math.lua \ + ext/std/modules.lua \ + ext/std/object.lua \ + ext/std/package.lua \ + ext/std/set.lua \ + ext/std/strbuf.lua \ + ext/std/strict.lua \ + ext/std/string.lua \ + ext/std/table.lua \ + ext/std/tree.lua \ $(NOTHING_ELSE) # In order to avoid regenerating std.lua at configure time, which # causes the documentation to be rebuilt and hence requires users to # have luadoc installed, put std/std.lua in as a Makefile dependency. # (Strictly speaking, distributing an AC_CONFIG_FILE would be wrong.) -lib/std.lua: lib/std.lua.in +ext/std.lua: ext/std.lua.in ./config.status --file=$@ -## Use a builtin rockspec build with root at $(srcdir)/lib -mkrockspecs_args = --module-dir $(srcdir)/lib +## Use a builtin rockspec build with root at $(srcdir)/ext +mkrockspecs_args = --module-dir $(srcdir)/ext ## ------------- ## @@ -76,7 +76,7 @@ mkrockspecs_args = --module-dir $(srcdir)/lib ## ------------- ## EXTRA_DIST += \ - lib/std.lua.in \ + ext/std.lua.in \ $(NOTHING_ELSE) @@ -85,11 +85,11 @@ EXTRA_DIST += \ ## -------------- ## dist_doc_DATA += \ - $(srcdir)/lib/index.html \ - $(srcdir)/lib/luadoc.css + $(srcdir)/ext/index.html \ + $(srcdir)/ext/luadoc.css -dist_files_DATA += $(wildcard $(srcdir)/lib/files/*.html) -dist_modules_DATA += $(wildcard $(srcdir)/lib/modules/*.html) +dist_files_DATA += $(wildcard $(srcdir)/ext/files/*.html) +dist_modules_DATA += $(wildcard $(srcdir)/ext/modules/*.html) $(dist_doc_DATA): $(dist_lua_DATA) $(dist_luastd_DATA) - cd $(srcdir)/lib && $(LUADOC) *.lua std/*.lua + cd $(srcdir)/ext && $(LUADOC) *.lua std/*.lua From 31b314c835391a06e0d8eafaa5e17909aba5d7d1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Nov 2013 00:05:09 -0800 Subject: [PATCH 003/703] doc: convert doc-comments to LDoc 1.4. * doc/config.ld: New file. Configuration for LDoc. * local.mk (EXTRA_DIST): Add it. (dist_doc_DATA): Remove Luadoc files. Add LDoc files. ($(dist_doc_DATA)): Invoke LDoc. * configure.ac (SS_CONFIG_TRAVIS): Request ldoc instead of luadoc. * .travis.yml: Regenerate. * .gitignore: Adjust. * ext/std.lua.in, ext/std/base.lua, ext/std/debug.lua, ext/std/functional.lua, ext/std/getopt.lua, ext/std/io.lua, ext/std/list.lua, ext/std/math.lua, ext/std/modules.lua, ext/std/object.lua, ext/std/package.lua, ext/std/set.lua, ext/std/strbuf.lua, ext/std/strict.lua, ext/std/string.lua, ext/std/table.lua, ext/std/tree.lua: Convert doc-comment syntax to richer LDoc 1.4 format. Signed-off-by: Gary V. Vaughan --- .gitignore | 10 +- .travis.yml | 6 +- configure.ac | 4 +- doc/config.ld | 26 ++++ ext/std.lua.in | 144 +++++++++++++++++++-- ext/std/base.lua | 116 +++++++---------- ext/std/debug.lua | 17 +-- ext/std/functional.lua | 64 +++++++--- ext/std/getopt.lua | 81 ++++++------ ext/std/io.lua | 24 ++-- ext/std/list.lua | 278 +++++++++++++++++++++++++---------------- ext/std/math.lua | 21 ++-- ext/std/modules.lua | 2 + ext/std/object.lua | 160 +++++++++++++++++------- ext/std/package.lua | 14 ++- ext/std/set.lua | 93 ++++++++++---- ext/std/strbuf.lua | 30 +++-- ext/std/strict.lua | 21 ++-- ext/std/string.lua | 80 ++++++------ ext/std/table.lua | 129 ++++++++++++------- ext/std/tree.lua | 121 ++++++++++-------- local.mk | 7 +- 22 files changed, 937 insertions(+), 511 deletions(-) create mode 100644 doc/config.ld diff --git a/.gitignore b/.gitignore index c29c5d0..830bffd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store /*.rockspec /.autom4te.cfg /.gitmodules @@ -13,10 +14,11 @@ /config.log /config.status /configure -/ext/files -/ext/index.html -/ext/luadoc.css -/ext/modules +/doc/classes +/doc/files +/doc/index.html +/doc/luadoc.css +/doc/modules /ext/std.lua /stdlib-*.tar.gz /luarocks diff --git a/.travis.yml b/.travis.yml index 2392a15..6296b66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ env: - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - LUAROCKS_BASE=luarocks-2.0.13 - LUAROCKS="$LUA $HOME/bin/luarocks" - - GENDOC=luarocks/bin/luadoc + - GENDOC=luarocks/bin/ldoc - SPECL=bin/specl matrix: - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 @@ -26,9 +26,9 @@ install: - sudo apt-get install liblua5.2-dev # Luadoc and Ldoc work best on Travis with Lua 5.1. - sudo apt-get install luarocks - - sudo luarocks install luadoc + - sudo luarocks install ldoc - mkdir -p luarocks/bin - - sed 's|^exec "[^"]*"|exec lua5.1|' `which luadoc` > $GENDOC + - sed 's|^exec "[^"]*"|exec lua5.1|' `which ldoc` > $GENDOC - chmod a+rx $GENDOC # Install a recent luarocks release locally for everything else. - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz diff --git a/configure.ac b/configure.ac index 33e995e..b3108b2 100644 --- a/configure.ac +++ b/configure.ac @@ -13,13 +13,13 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) dnl Check for programs AX_PROG_LUA([5.1], [5.3]) -AC_PATH_PROG([LUADOC], [luadoc], [:]) +AC_PATH_PROG([LDOC], [ldoc], [:]) AC_PATH_PROG([SPECL], [specl], [:]) AC_PROG_EGREP AC_PROG_SED dnl Generate output files SPECL_MIN=8 -SS_CONFIG_TRAVIS([luadoc specl]) +SS_CONFIG_TRAVIS([ldoc specl]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/doc/config.ld b/doc/config.ld new file mode 100644 index 0000000..115aae1 --- /dev/null +++ b/doc/config.ld @@ -0,0 +1,26 @@ +-- -*- lua -*- +project = "stdlib" +description = "Standard Lua Libraries" +dir = "." + +new_type("metamethod", "Metamethods", false, "params") +file = { + "../ext/std/debug.lua", + "../ext/std/functional.lua", + "../ext/std/getopt.lua", + "../ext/std/io.lua", + "../ext/std/list.lua", + "../ext/std/math.lua", + "../ext/std/object.lua", + "../ext/std/package.lua", + "../ext/std/set.lua", + "../ext/std/strbuf.lua", + "../ext/std/strict.lua", + "../ext/std/string.lua", + "../ext/std/table.lua", + "../ext/std/tree.lua", + "../ext/std.lua", +} + +format = "markdown" +sort = true diff --git a/ext/std.lua.in b/ext/std.lua.in index 4821af2..508f0b3 100644 --- a/ext/std.lua.in +++ b/ext/std.lua.in @@ -1,12 +1,27 @@ ---- Lua standard library ---
    ---
  • 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: Add tests for each function immediately after the function; --- this also helps to check module dependencies.
  • ---
  • TODO: pre-compile.
  • ---
+--[[-- + Global namespace scribbler. + + For backwards compatibility with older releases, `require "std"` + will inject the same functions into the global namespace as it + has done previously, even though it is now deprecated. + + For new code, much better than scribbling all over the global + namespace, it's more hygienic to explicitly assign the results of + requiring just the submodules you actually use to a local variable, + and access its functions via that table. + + @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 Add tests for each function immediately after the function; + this also helps to check module dependencies. + @todo pre-compile. + @module std +]] + +--- Module table. +-- @table std +-- @field version release version string local version = "General Lua libraries / @VERSION@" for m, globally in pairs (require "std.modules") do @@ -27,36 +42,147 @@ file_metatable.writelines = io.writelines -- Maintain old global interface access points. for _, api in ipairs { + --- Partially apply a function. + -- @function _G.bind + -- @see std.functional.bind "functional.bind", + + --- Collect the results of an iterator. + -- @function _G.collect + -- @see std.functional.collect "functional.collect", + + --- Compose functions. + -- @function _G.compose + -- @see std.functional.compose "functional.compose", + + --- Curry a function. + -- @function _G.curry + -- @see std.functional.curry "functional.curry", + + --- Evaluate a string. + -- @function _G.eval + -- @see std.functional.eval "functional.eval", + + --- Filter an iterator with a predicate. + -- @function _G.filter + -- @see std.functional.filter "functional.filter", + + --- Fold a binary function into an iterator. + -- @function _G.fold + -- @see std.functional.fold "functional.fold", + + --- Identity function. + -- @function _G.id + -- @see std.functional.id "functional.id", + + --- Map a function over an iterator. + -- @function _G.map + -- @see std.functional.map "functional.map", + + --- Memoize a function, by wrapping it in a functable. + -- @function _G.memoize + -- @see std.functional.memoize "functional.memoize", + + --- Return given metamethod, if any, else nil. + -- @function _G.metamethod + -- @see std.functional.metamethod "functional.metamethod", + + --- Functional forms of infix operators. + -- @table _G.op + -- @see std.functional.op "functional.op", + + + --- Die with an error. + -- @function _G.die + -- @see std.io.die "io.die", + + --- Give a warning with the name of program and file (if any). + -- @function _G.warn + -- @see std.io.warn "io.warn", + + + --- Extend to allow formatted arguments. + -- @function _G.assert + -- @see std.string.assert "string.assert", + + --- Convert a value to a string. + -- @function _G.pickle + -- @see std.string.pickle "string.pickle", + + --- Pretty-print a table. + -- @function _G.prettytostring + -- @see std.string.prettytostring "string.prettytostring", + + --- Turn tables into strings with recursion detection. + -- @function _G.render + -- @see std.string.render "string.render", + + --- Require a module with a particular version. + -- @function _G.require_version + -- @see std.string.require_version "string.require_version", + + --- Extend `tostring` to work better on tables. + -- @function _G.tostring + -- @see std.string.tostring "string.tostring", + + + --- Turn a tuple into a list. + -- @function _G.pack + -- @see std.table.pack "table.pack", + + --- An iterator like ipairs, but in reverse. + -- @function _G.ripairs + -- @see std.table.ripairs "table.ripairs", + + --- Turn an object into a table, according to `__totable` metamethod. + -- @function _G.totable + -- @see std.table.totable "table.totable", + + + --- Tree iterator which returns just numbered leaves, in order. + -- @function _G.ileaves + -- @see std.tree.ileaves "tree.ileaves", + + --- Tree iterator over numbered nodes, in order. + -- @function _G.inodes + -- @see std.tree.inodes "tree.inodes", + + --- Tree iterator which returns just leaves. + -- @function _G.leaves + -- @see std.tree.leaves "tree.leaves", + + --- Tree iterator. + -- @function _G.nodes + -- @see std.tree.nodes "tree.nodes", } do local module, method = api:match "^(.*)%.(.-)$" diff --git a/ext/std/base.lua b/ext/std/base.lua index aba7226..92730e2 100644 --- a/ext/std/base.lua +++ b/ext/std/base.lua @@ -1,20 +1,44 @@ ---- Shared functions. +------ +-- @module std.base ---- Append an item to a list. --- @param l list --- @param x item --- @return {l[1], ..., l[#l], x} +-- Doc-commented in table.lua... +local function clone (t, nometa) + local u = {} + if not nometa then + setmetatable (u, getmetatable (t)) + end + for i, v in pairs (t) do + u[i] = v + end + return u +end + +-- Doc-commented in table.lua... +local function clone_rename (map, t) + local r = clone (t) + for i, v in pairs (map) do + r[v] = t[i] + r[i] = nil + end + return r +end + +-- Doc-commented in table.lua... +local function merge (t, u) + for i, v in pairs (u) do + t[i] = v + end + return t +end + +-- Doc-commented in list.lua... local function append (l, x) local r = {unpack (l)} table.insert (r, x) return r end ---- Compare two lists element by element left-to-right --- @param l first list --- @param m second list --- @return -1 if l is less than m, 0 if they --- are the same, and 1 if l is greater than m +-- Doc-commented in list.lua... local function compare (l, m) for i = 1, math.min (#l, #m) do if l[i] < m[i] then @@ -31,11 +55,7 @@ local function compare (l, m) return 0 end ---- An iterator over the elements of a list. --- @param l list to iterate over --- @return iterator function which returns successive elements of the list --- @return the list l as above --- @return true +-- Doc-commented in list.lua... local function elems (l) local n = 0 return function (l) @@ -49,9 +69,9 @@ end --- Concatenate lists. -- @param ... lists --- @return {l1[1], ..., +-- @return `{l1[1], ..., -- l1[#l1], ..., ln[1], ..., --- ln[#ln]} +-- ln[#ln]}` local function concat (...) local r = new () for l in elems ({...}) do @@ -75,22 +95,6 @@ local function _leaves (it, tr) return coroutine.wrap (visit), tr end ---- Tree iterator which returns just numbered leaves, in order. --- @param tr tree to iterate over --- @return iterator function --- @return the tree, as above -local function ileaves (tr) - return _leaves (ipairs, tr) -end - ---- Tree iterator which returns just leaves. --- @param tr tree to iterate over --- @return iterator function --- @return the tree, as above -local function leaves (tr) - return _leaves (pairs, tr) -end - -- Metamethods for lists -- It would be nice to define this in `list.lua`, but then we -- couldn't keep `new` here, and other modules that really only @@ -115,49 +119,19 @@ local metatable = { -- Needed in order to use metamethods. -- @param t list (as a table), or nil for empty list -- @return list (with list metamethods) -function new (l) - return setmetatable (l or {}, metatable) +function new (t) + return setmetatable (t or {}, metatable) end ---- Make a shallow copy of a table, including any metatable (for a --- deep copy, use tree.clone). --- @param t table --- @param nometa if non-nil don't copy metatable --- @return copy of table -local function clone (t, nometa) - local u = {} - if not nometa then - setmetatable (u, getmetatable (t)) - end - for i, v in pairs (t) do - u[i] = v - end - return u -end - ---- Clone a table, renaming some keys. --- @param map table {old_key=new_key, ...} --- @param t table to copy --- @return copy of table -local function clone_rename (map, t) - local r = clone (t) - for i, v in pairs (map) do - r[v] = t[i] - r[i] = nil - end - return r +-- Doc-commented in tree.lua... +local function ileaves (tr) + return _leaves (ipairs, tr) end ---- Merge one table into another. u is merged into t. --- @param t first table --- @param u second table --- @return first table -local function merge (t, u) - for i, v in pairs (u) do - t[i] = v - end - return t +-- Doc-commented in tree.lua... +local function leaves (tr) + return _leaves (pairs, tr) end local M = { diff --git a/ext/std/debug.lua b/ext/std/debug.lua index 5629de4..555d09a 100644 --- a/ext/std/debug.lua +++ b/ext/std/debug.lua @@ -1,4 +1,7 @@ ---- Additions to the debug module +--[[-- + Additions to the debug module + @module std.debug +]] local init = require "std.debug_init" local io = require "std.io" @@ -14,7 +17,7 @@ local string = require "std.string" -- @field std do standard library debugging (run examples & test code) ---- Print a debugging message +--- Print a debugging message. -- @param n debugging level, defaults to 1 -- @param ... objects to print (as for print) local function say (n, ...) @@ -32,7 +35,7 @@ local function say (n, ...) end end ---- Trace function calls +--- Trace function calls. -- Use as debug.sethook (trace, "cr"), which is done automatically -- when _DEBUG.call is set. -- Based on test/trace-calls.lua from the Lua distribution. @@ -72,6 +75,7 @@ if type (init._DEBUG) == "table" and init._DEBUG.call then debug.sethook (trace, "cr") end +--- @export local M = { say = say, trace = trace, @@ -81,11 +85,8 @@ for k, v in pairs (debug) do M[k] = M[k] or v end ---- --- The global function debug is an abbreviation for --- debug.say (1, ...) --- @class function --- @name debug +--- The global function `debug` is an abbreviation for `debug.say (1, ...)` +-- @function debug -- @see say local metatable = { __call = function (self, ...) diff --git a/ext/std/functional.lua b/ext/std/functional.lua index 0fded29..a151ab8 100644 --- a/ext/std/functional.lua +++ b/ext/std/functional.lua @@ -1,7 +1,12 @@ ---- Adds to the existing global functions +--[[-- + Functional programming. + @module std.functional +]] local list = require "std.base" +local functional -- forward declaration + --- Return given metamethod, if any, or nil. -- @param x object to get metamethod of @@ -19,6 +24,7 @@ local function metamethod (x, n) return m end + --- Identity function. -- @param ... -- @return the arguments passed to the function @@ -26,6 +32,7 @@ local function id (...) return ... end + --- Partially apply a function. -- @param f function to apply partially -- @param ... arguments to bind @@ -37,6 +44,7 @@ local function bind (f, ...) end end + --- Curry a function. -- @param f function to curry -- @param n number of arguments @@ -51,6 +59,7 @@ local function curry (f, n) end end + --- Compose functions. -- @param f1...fn functions to compose -- @return composition of f1 ... fn @@ -66,6 +75,7 @@ local function compose (...) end end + --- Memoize a function, by wrapping it in a functable. -- @param fn function that returns a single result -- @return memoized function @@ -83,6 +93,7 @@ local function memoize (fn) }) end + --- Evaluate a string. -- @param s string -- @return value of string @@ -90,6 +101,7 @@ local function eval (s) return loadstring ("return " .. s)() end + --- Collect the results of an iterator. -- @param i iterator -- @return results of running the iterator on its arguments @@ -101,6 +113,7 @@ local function collect (i, ...) return t end + --- Map a function over an iterator. -- @param f function -- @param i iterator @@ -116,6 +129,7 @@ local function map (f, i, ...) return t end + --- Filter an iterator with a predicate. -- @param p predicate -- @param i iterator @@ -130,6 +144,7 @@ local function filter (p, i, ...) return t end + --- Fold a binary function into an iterator. -- @param f function -- @param d initial first argument @@ -143,11 +158,35 @@ local function fold (f, d, i, ...) return r end +--- @export +functional = { + bind = bind, + collect = collect, + compose = compose, + curry = curry, + eval = eval, + filter = filter, + fold = fold, + id = id, + map = map, + memoize = memoize, + metamethod = metamethod, +} + --- Functional forms of infix operators. -- Defined here so that other modules can write to it. --- @class table --- @name op -local op = { +-- @table op +-- @field [] dereference table index +-- @field + addition +-- @field - subtraction +-- @field * multiplication +-- @field / division +-- @field and logical and +-- @field or logical or +-- @field not logical not +-- @field == equality +-- @field ~= inequality +functional.op = { ["[]"] = function (t, s) return t[s] end, ["+"] = function (a, b) return a + b end, ["-"] = function (a, b) return a - b end, @@ -160,19 +199,4 @@ local op = { ["~="] = function (a, b) return a ~= b end, } -local M = { - bind = bind, - collect = collect, - compose = compose, - curry = curry, - eval = eval, - filter = filter, - fold = fold, - id = id, - map = map, - memoize = memoize, - metamethod = metamethod, - op = op, -} - -return M +return functional diff --git a/ext/std/getopt.lua b/ext/std/getopt.lua index cb4d898..6437c1b 100644 --- a/ext/std/getopt.lua +++ b/ext/std/getopt.lua @@ -1,35 +1,38 @@ ---- Simplified getopt, based on Svenne Panne's Haskell GetOpt.
+--- Simplified getopt, based on Svenne Panne's Haskell GetOpt. +-- -- Usage: ---
    ---
  • prog = {< --- name = , --- [usage = ,] --- [options = { --- {{[, ...]}, , [ [, ]]}, --- ... --- },] --- [banner = ,] --- [purpose = ,] --- [notes = ] --- }
  • ---
  • The type of option argument is one of Req(uired), --- Opt(ional)
  • ---
  • The varis a descriptive name for the option argument.
  • ---
  • getopt.processargs (prog)
  • ---
  • Options take a single dash, but may have a double dash.
  • ---
  • Arguments may be given as -opt=arg or -opt arg.
  • ---
  • If an option taking an argument is given multiple times, only the --- last value is returned; missing arguments are returned as 1.
  • ---
+-- +-- prog = {< +-- name = , +-- [usage = ,] +-- [options = { +-- {{[, ...]}, , [ [, ]]}, +-- ... +-- },] +-- [banner = ,] +-- [purpose = ,] +-- [notes = ] +-- } +-- +-- * The `type` of option argument is one of `Req`(uired), +-- `Opt`(ional) +-- * The `var`is a descriptive name for the option argument. +-- * `getopt.processargs (prog)` +-- * Options take a single dash, but may have a double dash. +-- * Arguments may be given as `-opt=arg` or `-opt arg`. +-- * If an option taking an argument is given multiple times, only the +-- last value is returned; missing arguments are returned as 1. +-- -- getOpt, usageinfo and usage can be called directly (see -- below, and the example at the end). Set _DEBUG.std to a non-nil -- value to run the example. ---
    ---
  • TODO: Wrap all messages; do all wrapping in processargs, not --- usageinfo; use sdoc-like library (see string.format todos).
  • ---
  • TODO: Don't require name to be repeated in banner.
  • ---
  • TODO: Store version separately (construct banner?).
  • ---
+-- +-- @todo Wrap all messages; do all wrapping in processargs, not +-- usageinfo; use sdoc-like library (see string.format todos). +-- @todo Don't require name to be repeated in banner. +-- @todo Store version separately (construct banner?). +-- +-- @module std.getopt local io = require "std.io" local List = require "std.list" @@ -108,7 +111,7 @@ end -- Object that defines a single Option entry. local Option = Object {_init = {"name", "desc", "type", "var"}} ---- Options table constructor: adds lookup tables for the option names +- Options table constructor: adds lookup tables for the option names. local function makeOptions (t) local options, name = {}, {} local function appendOpt (v, nodupes) @@ -138,7 +141,7 @@ local function makeOptions (t) end ---- Produce usage info for the given options +--- Produce usage info for the given options. -- @param header header string -- @param optDesc option descriptors -- @param pageWidth width to format to [78] @@ -242,11 +245,10 @@ end --- Simple getopt wrapper. --- If the caller didn't supply their own already, --- adds --version/-V and --- --help/-h options automatically; --- stops program if there was an error, or if --help or --- --version was used. +-- If the caller didn't supply their own already, -- adds `--version`/`-V` +-- and `--help`/`-h` options automatically; +-- stops program if there was an error, or if `--help` or `--version` was +-- used. -- @param prog table of named parameters -- @param ... extra arguments for getopt local function processargs (prog, ...) @@ -279,8 +281,8 @@ local function processargs (prog, ...) end --- Public interface -return table.merge (M, { +--- @export +local Getopt = { getopt = getopt, processargs = processargs, usage = usage, @@ -289,6 +291,7 @@ return table.merge (M, { -- camelCase compatibility. getOpt = getopt, processArgs = processargs, - usage = usage, usageInfo = usageinfo, -}) +} + +return table.merge (M, Getopt) diff --git a/ext/std/io.lua b/ext/std/io.lua index 3553a53..bcf43f7 100644 --- a/ext/std/io.lua +++ b/ext/std/io.lua @@ -1,4 +1,7 @@ ---- Additions to the io module +--[[-- + Additions to the io module. + @module std.io +]] local package = require "std.package" local string = require "std.string" @@ -6,7 +9,7 @@ local tree = require "std.tree" -- Get an input file handle. --- @param h file handle or name (default: io.input ()) +-- @param h file handle or name (default: `io.input ()`) -- @return file handle, or nil on error local function input_handle (h) if h == nil then @@ -18,7 +21,7 @@ local function input_handle (h) end --- Slurp a file handle. --- @param h file handle or name (default: io.input ()) +-- @param h file handle or name (default: `io.input ()`) -- @return contents of file or handle, or nil if error local function slurp (h) h = input_handle (h) @@ -30,7 +33,7 @@ local function slurp (h) end --- Read a file or file handle into a list of lines. --- @param h file handle or name (default: io.input ()); +-- @param h file handle or name (default: `io.input ()`); -- if h is a handle, the file is closed after reading -- @return list of lines local function readlines (h) @@ -44,7 +47,7 @@ local function readlines (h) end --- Write values adding a newline after each. --- @param h file handle (default: io.output () +-- @param h file handle (default: `io.output ()` -- @param ... values to write (as for write) local function writelines (h, ...) if io.type (h) ~= "file" then @@ -57,7 +60,7 @@ local function writelines (h, ...) end --- Split a directory path into components. --- Empty components are retained: the root directory becomes {"", ""}. +-- Empty components are retained: the root directory becomes `{"", ""}`. -- @param path path -- @return list of path components local function splitdir (path) @@ -86,11 +89,11 @@ local function shell (c) end --- Process files specified on the command-line. --- If no files given, process io.stdin; in list of files, --- - means io.stdin. ---
FIXME: Make the file list an argument to the function. +-- If no files given, process `io.stdin`; in list of files, +-- `-` means `io.stdin`. +-- @todo Make the file list an argument to the function. -- @param f function to process files with, which is passed --- (name, arg_no) +-- `(name, arg_no)` local function process_files (f) -- N.B. "arg" below refers to the global array of command-line args if #arg == 0 then @@ -132,6 +135,7 @@ local function die (...) end +--- @export local M = { catdir = catdir, catfile = catfile, diff --git a/ext/std/list.lua b/ext/std/list.lua index da18496..88a182c 100644 --- a/ext/std/list.lua +++ b/ext/std/list.lua @@ -1,37 +1,61 @@ ---- Tables as lists. +--[[-- + Tables as lists. -local base = require "std.base" -local compare, elems, ileaves = base.compare, base.elems, base.ileaves + Every list is also an object, and thus inherits all of the `std.object` + methods, particularly use of object cloning for making new list objects. -local func = require "std.functional" -local Object = require "std.object" + @classmod std.list +]] + +local base = require "std.base" +local func = require "std.functional" +local Object = require "std.object" + + +--- Compare two lists element-by-element, from left-to-right. +-- @function compare +-- @tparam table l a list +-- @tparam table m another list +-- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 +-- if `l` is greater than `m` +local compare = base.compare + + +--- An iterator over the elements of a list. +-- @function elems +-- @tparam table l a list +-- @treturn function iterator function which returns successive elements of `self` +-- @treturn table `l` +-- @return `true` +local elems = base.elems + + +local List -- list prototype object forward declaration -local List -- forward declaration --- Append an item to a list. --- @param l list --- @param x item --- @return {l[1], ..., l[#l], x} +-- @tparam table l a list +-- @param x item +-- @treturn std.list new list containing `{l[1], ..., l[#l], x}` local function append (l, x) - local r = l {} - table.insert (r, x) - return r + return List (base.append (l, x)) end ---- Concatenate lists. --- @param ... lists --- @return {l1[1], ..., --- l1[#l1], ..., ln[1], ..., --- ln[#ln]} + +--- Concatenate arguments into a list. +-- @param ... tuple of lists +-- @treturn std.list new list containing +-- `{l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` local function concat (...) return List (base.concat (...)) end + --- An iterator over the elements of a list, in reverse. --- @param l list to iterate over --- @return iterator function which returns precessive elements of the list --- @return the list l as above --- @return true +-- @tparam table l a list +-- @treturn function iterator function which returns precessive elements of the list +-- @treturn std.list `l` +-- @return `true` local function relems (l) local n = #l + 1 return function (l) @@ -43,37 +67,41 @@ local function relems (l) l, true end + --- Map a function over a list. --- @param f function --- @param l list --- @return result list {f (l[1]), ..., f (l[#l])} +-- @tparam table l a list +-- @tparam function f map function +-- @treturn std.list new list containing `{f (list[1]), ..., f (list[#list])}` local function map (l, f) return List (func.map (f, elems, l)) end + --- Map a function over a list of lists. --- @param f function --- @param ls list of lists --- @return result list {f (unpack (ls[1]))), ..., f (unpack (ls[#ls]))} -local function map_with (l, f) - return List (func.map (func.compose (f, unpack), elems, l)) +-- @tparam table ls a list of lists +-- @tparam function f map function +-- @treturn std.list new list `{f (unpack (ls[1]))), ..., f (unpack (ls[#ls]))}` +local function map_with (ls, f) + return List (func.map (func.compose (f, unpack), elems, ls)) end + --- Filter a list according to a predicate. --- @param p predicate (function of one argument returning a boolean) --- @param l list of lists --- @return result list containing elements e of --- l for which p (e) is true +-- @tparam table l a list +-- @tparam function p predicate function, of one argument returning a boolean +-- @treturn std.list new list containing elements `e` of `l` for which `p (e)` is true local function filter (l, p) return List (func.filter (p, elems, l)) end ---- Return a sub-range of a list. (The equivalent of string.sub --- on strings; negative list indices count from the end of the list.) --- @param l list --- @param from start of range (default: 1) --- @param to end of range (default: #l) --- @return {l[from], ..., l[to]} + +--- Return a sub-range of a list. +-- (The equivalent of `string.sub` -- on strings; negative list indices +-- count from the end of the list.) +-- @tparam table l a list +-- @tparam number from start of range (default: 1) +-- @tparam number to end of range (default: `#list`) +-- @treturn std.list new list containing `{l[from], ..., l[to]}` local function sub (l, from, to) local r = List {} local len = #l @@ -91,44 +119,49 @@ local function sub (l, from, to) return r end + --- Return a list with its first element removed. --- @param l list --- @return {l[2], ..., l[#l]} +-- @tparam table l a list +-- @treturn std.list new list containing `{l[2], ..., l[#l]}` local function tail (l) return sub (l, 2) end + --- Fold a binary function through a list left associatively. --- @param f function --- @param e element to place in left-most position --- @param l list +-- @tparam table l a list +-- @tparam function f binary function +-- @param e element to place in left-most position -- @return result local function foldl (l, f, e) return func.fold (f, e, elems, l) end + --- Fold a binary function through a list right associatively. --- @param f function --- @param e element to place in right-most position --- @param l list +-- @tparam table l a list +-- @tparam function f binary function +-- @param e element to place in right-most position -- @return result local function foldr (l, f, e) return List (func.fold (function (x, y) return f (y, x) end, e, relems, l)) end + --- Prepend an item to a list. --- @param l list --- @param x item --- @return {x, unpack (l)} +-- @tparam table l a list +-- @param x item +-- @treturn std.list new list containing `{x, unpack (l)}` local function cons (l, x) return List {x, unpack (l)} end + --- Repeat a list. --- @param l list --- @param n number of times to repeat --- @return n copies of l appended together +-- @tparam table l a list +-- @tparam number n number of times to repeat +-- @treturn std.list `n` copies of `l` appended together local function rep (l, n) local r = List {} for i = 1, n do @@ -137,9 +170,10 @@ local function rep (l, n) return r end + --- Reverse a list. --- @param l list --- @return list {l[#l], ..., l[1]} +-- @tparam table l a list +-- @treturn std.list new list containing `{l[#l], ..., l[1]}` local function reverse (l) local r = List {} for i = #l, 1, -1 do @@ -148,13 +182,14 @@ local function reverse (l) return r end + --- Transpose a list of lists. --- This function in Lua is equivalent to zip and unzip in more --- strongly typed languages. --- @param ls {{l1,1, ..., l1,c}, ..., --- {lr,1, ..., lr,c}} --- @return {{l1,1, ..., lr,1}, ..., --- {l1,c, ..., lr,c}} +-- This function in Lua is equivalent to zip and unzip in more strongly +-- typed languages. +-- @tparam table ls +-- `{{ls<1,1>, ..., ls<1,c>}, ..., {ls<r,1>, ..., ls<r,c>}}` +-- @treturn std.list new list containing +-- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` local function transpose (ls) local rs, len = List {}, #ls for i = 1, math.max (unpack (map (ls, function (l) return #l end))) do @@ -166,29 +201,32 @@ local function transpose (ls) return rs end ---- Zip lists together with a function. --- @param f function --- @param ls list of lists --- @return {f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N]) --- where N = max {map (function (l) return #l end, ls)} + +--- Zip a list of lists together with a function. +-- @tparam table ls list of lists +-- @tparam function f function +-- @treturn std.list a new list containing +-- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` +-- where `N = max {map (function (l) return #l end, ls)}` local function zip_with (ls, f) return map_with (transpose (ls), f) end + --- Project a list of fields from a list of tables. --- @param f field to project --- @param l list of tables --- @return list of f fields +-- @tparam table l a list +-- @param f field to project +-- @treturn std.list list of `f` fields local function project (l, f) return map (l, function (t) return t[f] end) end + --- Turn a table into a list of pairs. ---
FIXME: Find a better name. --- @param t table {i1=v1, ..., --- in=vn} --- @return list {{i1, v1}, ..., --- {in, vn}} +-- @todo Find a better name. +-- @tparam table t a table `{i1=v1, ..., in=vn}` +-- @treturn std.list a new list containing `{{i1, v1}, ..., {in, vn}}` +-- @see std.list:depair local function enpair (t) local ls = List {} for i, v in pairs (t) do @@ -197,14 +235,14 @@ local function enpair (t) return ls end + --- Turn a list of pairs into a table. ---
FIXME: Find a better name. --- @param ls list {{i1, v1}, ..., --- {in, vn}} --- @return table {i1=v1, ..., --- in=vn} +-- @todo Find a better name. +-- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` +-- @treturn std.list a new list containing table `{i1=v1, ..., in=vn}` +-- @see std.list:enpair local function depair (ls) - local t = {} + local t = List {} for v in elems (ls) do t[v[1]] = v[2] end @@ -213,35 +251,36 @@ end --- Flatten a list. --- @param l list to flatten --- @return flattened list +-- @tparam table l a list +-- @treturn std.list flattened list local function flatten (l) local r = List {} - for v in ileaves (l) do + for v in base.ileaves (l) do table.insert (r, v) end return r end + --- Shape a list according to a list of dimensions. -- -- Dimensions are given outermost first and items from the original -- list are distributed breadth first; there may be one 0 indicating --- an indefinite number. Hence, {0} is a flat list, --- {1} is a singleton, {2, 0} is a list of --- two lists, and {0, 2} is a list of pairs. ---
+-- an indefinite number. Hence, `{0}` is a flat list, +-- `{1}` is a singleton, `{2, 0}` is a list of +-- two lists, and `{0, 2}` is a list of pairs. +-- -- Algorithm: turn shape into all positive numbers, calculating -- the zero if necessary and making sure there is at most one; -- recursively walk the shape, adding empty tables until the bottom -- level is reached at which point add table items instead, using a -- counter to walk the flattened original list. ---
--- @param s {d1, ..., dn} --- @param l list to reshape --- @return reshaped list --- FIXME: Use ileaves instead of flatten (needs a while instead of a +-- +-- @todo Use ileaves instead of flatten (needs a while instead of a -- for in fill function) +-- @tparam table l a list +-- @tparam table s `{d1, ..., dn}` +-- @return reshaped list local function shape (l, s) l = flatten (l) -- Check the shape and calculate the size of the zero, if any @@ -277,12 +316,11 @@ local function shape (l, s) return (fill (1, 1)) end + --- Make an index of a list of tables on a given field --- @param f field --- @param l list of tables {t1, ..., --- tn} --- @return index {t1[f]=1, ..., --- tn[f]=n} +-- @tparam table l list of tables `{t1, ..., tn}` +-- @param f field +-- @treturn std.list index `{t1[f]=1, ..., tn[f]=n}` local function index_key (l, f) local r = List {} for i, v in ipairs (l) do @@ -294,12 +332,11 @@ local function index_key (l, f) return r end + --- Copy a list of tables, indexed on a given field --- @param f field whose value should be used as index --- @param l list of tables {i1=t1, ..., --- in=tn} --- @return index {t1[f]=t1, ..., --- tn[f]=tn} +-- @tparam table l list of tables `{i1=t1, ..., in=tn}` +-- @param f field whose value should be used as index +-- @treturn std.list index `{t1[f]=t1, ..., tn[f]=tn}` local function index_value (l, f) local r = List {} for i, v in ipairs (l) do @@ -316,18 +353,39 @@ List = Object { -- Derived object type. _type = "List", - __concat = concat, -- list .. table - __add = append, -- list + element - - -- list == list retains its referential meaning - -- - -- list < list = list.compare returns < 0 + ------ + -- Concatenate lists. + -- new = list .. table + -- @metamethod __concat + -- @see std.list:concat + __concat = concat, + + ------ + -- Append to list. + -- list = list + element + -- @metamethod __add + -- @see std.list:append + __add = append, + + ------ + -- List order operator. + -- max = list1 > list2 and list1 or list2 + -- @metamethod __lt + -- @tparam std.list list1 a list + -- @tparam std.list list2 another list + -- @see std.list:compare __lt = function (l, m) return compare (l, m) < 0 end, - -- list <= list = list.compare returns <= 0 + ------ + -- List equality or order operator. + -- min = list1 <= list2 and list1 or list2 + -- @metamethod __le + -- @tparam std.list list1 a list + -- @tparam std.list list2 another list + -- @see std.list:compare __le = function (l, m) return compare (l, m) <= 0 end, - -- list:method () + --- @export __index = { append = append, compare = compare, diff --git a/ext/std/math.lua b/ext/std/math.lua index 3a3f9ae..a50859f 100644 --- a/ext/std/math.lua +++ b/ext/std/math.lua @@ -1,11 +1,16 @@ ---- Additions to the math module. +--[[-- + Additions to the math module. + @module std.math +]] local _floor = math.floor ---- Extend math.floor to take the number of decimal places. + +--- Extend `math.floor` to take the number of decimal places. +-- @function floor -- @param n number -- @param p number of decimal places to truncate to (default: 0) --- @return n truncated to p decimal places +-- @return `n` truncated to `p` decimal places local function floor (n, p) if p and p ~= 0 then local e = 10 ^ p @@ -15,17 +20,19 @@ local function floor (n, p) end end + --- Round a number to a given number of decimal places +-- @function round -- @param n number -- @param p number of decimal places to round to (default: 0) --- @return n rounded to p decimal places +-- @return `n` rounded to `p` decimal places local function round (n, p) local e = 10 ^ (p or 0) return _floor (n * e + 0.5) / e end -local M = { +local Math = { floor = floor, round = round, @@ -34,7 +41,7 @@ local M = { } for k, v in pairs (math) do - M[k] = M[k] or v + Math[k] = Math[k] or v end -return M +return Math diff --git a/ext/std/modules.lua b/ext/std/modules.lua index 00b04de..2beaf18 100644 --- a/ext/std/modules.lua +++ b/ext/std/modules.lua @@ -1,3 +1,5 @@ +-- Set of imported modules. + return { -- true => module symbols injected into equivalent core namespace -- with `require 'std'`: diff --git a/ext/std/object.lua b/ext/std/object.lua index ea9a5ac..30bba8c 100644 --- a/ext/std/object.lua +++ b/ext/std/object.lua @@ -1,30 +1,50 @@ ---- Prototype-based objects ---
    ---
  • Create an object/class:
  • ---
      ---
    • Either, if the _init field is a list: ---
        ---
      • object/Class = prototype {value, ...; field = value, ...}
      • ---
      • Named values are assigned to the corresponding fields, and unnamed values --- to the fields given by _init.
      • ---
      ---
    • Or, if the _init field is a function: ---
        ---
      • object/Class = prototype (value, ...)
      • ---
      • The given values are passed as arguments to the _init function.
      • ---
      ---
    • An object's metatable is itself.
    • ---
    • Private fields and methods start with "_".
    • ---
    ---
  • Access an object field: object.field
  • ---
  • Call an object method: object:method (...)
  • ---
  • Call a class method: Class.method (object, ...)
  • ---
  • Add a field: object.field = x
  • ---
  • Add a method: function object:method (...) ... end
  • --- +--[[-- + Prototype-based objects. -local base = require "std.base" + This module creates the root prototype object from which every other + object is descended. There are no classes as such, rather new objects + are created by cloning an existing prototype object, and then changing + or adding to it. Further objects can then be made by cloning the changed + object, and so on. + + Objects are cloned by simply calling an existing object which then + serves as a prototype, from which the new object is copied. + + All valid objects contain a field `_init`, which determines the syntax + required to execute the cloning process: + + 1. `_init` can be a list of keys; then the unnamed `init_1` through + `init_n` values from the argument table are assigned to the + corresponding keys in `new_object`; + + new_object = prototype { + init_1, ..., init_m; + field_1 = value_1, + ... + field_n = value_n, + } + + 2. Or it can be a function, in which the arguments passed to the + prototype during cloning are simply handed to the `_init` function: + + new_object = prototype (value, ...) + Field names beginning with "_" are *private*, and moved into the object + metatable during cloning. Unless `new_object` changes the metatable this + way, then it will share a metatable with `prototype` for efficiency. + + Objects, then, are essentially tables of `field\_n = value\_n` pairs: + + * Access an object field: `object.field` + * Call an object method: `object:method (...)` + * Call a "class" method: `Class.method (object, ...)` + * Add a field: `object.field = x` + * Add a method: `function object:method (...) ... end` + + @classmod std.object +]] + +local base = require "std.base" -- Return the named entry from x's metatable, if any, else nil. local function metaentry (x, n) @@ -37,17 +57,45 @@ local function metaentry (x, n) end --- Return the extended object type, if any, else primitive type. -local function object_type (self) - local _type = metaentry (self, "_type") - if type (self) == "table" and _type ~= nil then +--- Return the extended object type, if any, else primitive type. +-- +-- It's conventional to organise similar objects according to a string +-- valued `_type` field, which can then be queried using this function. +-- +-- Stack = Object { +-- _type = "Stack", +-- +-- __tostring = function (self) ... end, +-- +-- __index = { +-- push = function (self) ... end, +-- pop = function (self) ... end, +-- }, +-- } +-- stack = Stack {} +-- +-- stack:type () --> "Stack" +-- +-- @function type +-- @tparam std.object o an object +-- @treturn string type of the object +local function object_type (o) + local _type = metaentry (o, "_type") + if type (o) == "table" and _type ~= nil then return _type end - return type (self) + return type (o) end --- Return a new object, cloned from prototype. +--- Clone an object. +-- +-- Prototypes are cloned by calling directly as described above, so this +-- `clone` method is rarely used explicitly. +-- @tparam std.object prototype source object +-- @param ... arguments +-- @treturn std.object a clone of `prototype`, adjusted +-- according to the rules above, and sharing a metatable where possible. local function clone (prototype, ...) local mt = getmetatable (prototype) @@ -102,15 +150,21 @@ local function clone (prototype, ...) end --- Return a stringified version of the contents of object. +--- Return a stringified version of the contents of object. +-- -- First the object type, and then between { and } a list of the array -- part of the object table (without numeric keys) followed by the -- remaining key-value pairs. +-- -- This function doesn't recurse explicity, but relies upon suitable --- __tostring metamethods in contained objects. -local function stringify (object) - local totable = getmetatable (object).__totable - local array = base.clone (totable (object), "nometa") +-- `__tostring` metamethods in contained objects. +-- +-- @function tostring +-- @tparam std.object o an object +-- @treturn string stringified object representation +local function stringify (o) + local totable = getmetatable (o).__totable + local array = base.clone (totable (o), "nometa") local other = base.clone (array, "nometa") local s = "" if #other > 0 then @@ -134,15 +188,19 @@ local function stringify (object) s = s .. table.concat (dict, ", ") end - return metaentry (object, "_type") .. " {" .. s .. "}" + return metaentry (o, "_type") .. " {" .. s .. "}" end --- Return a new table with a shallow copy of all non-private fields --- in object (private fields have keys prefixed with "_"). -local function totable (object) +--- Return a new table with a shallow copy of all non-private fields +-- in object. +-- +-- Where private fields have keys prefixed with "_". +-- @tparam std.object o an object +-- @treturn table raw (non-object) table of object fields +local function totable (o) local t = {} - for k, v in pairs (object) do + for k, v in pairs (o) do if type (k) ~= "string" or k:sub (1, 1) ~= "_" then t[k] = v end @@ -160,10 +218,23 @@ local metatable = { _type = "Object", _init = {}, + ------ + -- Return a shallow copy of non-private object fields. + -- + -- This pseudo-metamethod is used during object cloning to make the + -- intial new object table, and can be overridden in other objects + -- for greater control of which fields are considered non-private. + -- @metamethod __totable + -- @see std.object:totable __totable = totable, + + ------ + -- Return a string representation of *object*. + -- @metamethod __tostring + -- @see std.object:tostring __tostring = stringify, - -- object:method () + --- @export __index = { clone = clone, tostring = stringify, @@ -177,9 +248,4 @@ local metatable = { end, } ---- Root object --- @class functable --- @name Object --- @field _init constructor method or list of fields to be initialised by the --- constructor return setmetatable ({}, metatable) diff --git a/ext/std/package.lua b/ext/std/package.lua index 7f32eaa..ab791b5 100644 --- a/ext/std/package.lua +++ b/ext/std/package.lua @@ -1,16 +1,18 @@ --- Additions to the package module. +--[[-- + Additions to the package module. + @module std.package +]] local M = {} ---- Make named constants for package.config (undocumented --- in 5.1; see luaconf.h for C equivalents). --- @class table --- @name package +--- Make named constants for `package.config` +-- (undocumented in 5.1; see luaconf.h for C equivalents). +-- @table package -- @field dirsep directory separator -- @field pathsep path separator -- @field path_mark string that marks substitution points in a path template -- @field execdir (Windows only) replaced by the executable's directory in a path --- @field igmark Mark to ignore all before it when building luaopen_ function name. +-- @field igmark Mark to ignore all before it when building `luaopen_` function name. M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark = string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") diff --git a/ext/std/set.lua b/ext/std/set.lua index 91c28d7..cb527cc 100644 --- a/ext/std/set.lua +++ b/ext/std/set.lua @@ -1,4 +1,7 @@ --- Sets. +--[[-- + Set object. + @classmod std.set + ]] local list = require "std.base" local Object = require "std.object" @@ -9,16 +12,16 @@ local Set -- forward declaration -- The representation is a table whose tags are the elements, and -- whose values are true. ---- Say whether an element is in a set +--- Say whether an element is in a set. -- @param s set -- @param e element --- @return true if e is in set, false +-- @return `true` if e is in set, `false` -- otherwise local function member (s, e) return rawget (s, e) == true end ---- Insert an element into a set +--- Insert an element into a set. -- @param s set -- @param e element -- @return the modified set @@ -27,7 +30,7 @@ local function insert (s, e) return s end ---- Delete an element from a set +--- Delete an element from a set. -- @param s set -- @param e element -- @return the modified set @@ -36,8 +39,8 @@ local function delete (s, e) return s end ---- Iterator for sets --- TODO: Make the iterator return only the key +--- Iterator for sets. +-- @todo Make the iterator return only the key local function elems (s) return pairs (s) end @@ -47,7 +50,7 @@ end local difference, symmetric_difference, intersection, union, subset, equal ---- Find the difference of two sets +--- Find the difference of two sets. -- @param s set -- @param t set -- @return s with elements of t removed @@ -64,7 +67,7 @@ function difference (s, t) return r end ---- Find the symmetric difference of two sets +--- Find the symmetric difference of two sets. -- @param s set -- @param t set -- @return elements of s and t that are in s or t but not both @@ -75,7 +78,7 @@ function symmetric_difference (s, t) return difference (union (s, t), intersection (t, s)) end ---- Find the intersection of two sets +--- Find the intersection of two sets. -- @param s set -- @param t set -- @return set intersection of s and t @@ -92,7 +95,7 @@ function intersection (s, t) return r end ---- Find the union of two sets +--- Find the union of two sets. -- @param s set -- @param t set or set-like table -- @return set union of s and t @@ -110,11 +113,10 @@ function union (s, t) return r end ---- Find whether one set is a subset of another +--- Find whether one set is a subset of another. -- @param s set -- @param t set --- @return true if s is a subset of t, false --- otherwise +-- @return `true` if s is a subset of t, `false` otherwise function subset (s, t) if Object.type (t) == "table" then t = Set (t) @@ -127,10 +129,10 @@ function subset (s, t) return true end ---- Find whether one set is a proper subset of another +--- Find whether one set is a proper subset of another. -- @param s set -- @param t set --- @return true if s is a proper subset of t, false otherwise +-- @return `true` if s is a proper subset of t, false otherwise function propersubset (s, t) if Object.type (t) == "table" then t = Set (t) @@ -138,11 +140,10 @@ function propersubset (s, t) return subset (s, t) and not subset (t, s) end ---- Find whether two sets are equal +--- Find whether two sets are equal. -- @param s set -- @param t set --- @return true if sets are equal, false --- otherwise +-- @return `true` if sets are equal, `false` otherwise function equal (s, t) return subset (s, t) and subset (t, s) end @@ -160,13 +161,53 @@ Set = Object { return self end, - __add = union, -- set + table = union - __sub = difference, -- set - table = set difference - __mul = intersection, -- set * table = intersection - __div = symmetric_difference, -- set / table = symmetric difference - __le = subset, -- set <= table = subset - __lt = propersubset, -- set < table = proper subset + ------ + -- Union operator. + -- set + table = union + -- @metamethod __add + -- @see std.set:union + __add = union, + + ------ + -- Difference operator. + -- set - table = set difference + -- @metamethod __sub + -- @see std.set:difference + __sub = difference, + + ------ + -- Intersection operator. + -- set * table = intersection + -- @metamethod __mul + -- @see std.set:intersection + __mul = intersection, + + ------ + -- Symmetric difference operator. + -- set / table = symmetric difference + -- @metamethod __div + -- @see std.set:symmetric_difference + __div = symmetric_difference, + + ------ + -- Subset operator. + -- set <= table = subset + -- @metamethod __le + -- @see std.set:subset + __le = subset, + + ------ + -- Proper subset operator. + -- set < table = proper subset + -- @metamethod __lt + -- @see std.set:propersubset + __lt = propersubset, + + ------ + -- Object to table conversion. + -- table = set:totable () + -- @metamethod __totable __totable = function (self) local t = {} for e in elems (self) do @@ -176,7 +217,7 @@ Set = Object { return t end, - -- set:method () + --- @export __index = { delete = delete, difference = difference, diff --git a/ext/std/strbuf.lua b/ext/std/strbuf.lua index f7cb61e..f360669 100644 --- a/ext/std/strbuf.lua +++ b/ext/std/strbuf.lua @@ -1,9 +1,13 @@ ---- String buffers. +--[[-- + String buffers. + @classmod std.strbuf +]] + local Object = require "std.object" ---- Add a string to a buffer +--- Add a string to a buffer. -- @param b buffer -- @param s string to add -- @return buffer @@ -13,7 +17,7 @@ local function concat (b, s) end ---- Convert a buffer to a string +--- Convert a buffer to a string. -- @param b buffer -- @return string local function tostring (b) @@ -25,11 +29,23 @@ return Object { -- Derived object type. _type = "StrBuf", - -- Metamethods. - __concat = concat, -- buffer .. string - __tostring = tostring, -- tostring (buffer) + ------ + -- Support concatenation of StrBuf objects. + -- buffer = buffer .. str + -- @metamethod __concat + -- @see std.strbuf:concat + __concat = concat, + + + ------ + -- Support fast conversion to Lua string. + -- str = tostring (buffer) + -- @metamethod __tostring + -- @see std.strbuf:tostring + __tostring = tostring, + - -- strbuf:method () + --- @export __index = { concat = concat, tostring = tostring, diff --git a/ext/std/strict.lua b/ext/std/strict.lua index 1eb22c7..46ac782 100644 --- a/ext/std/strict.lua +++ b/ext/std/strict.lua @@ -1,10 +1,13 @@ ---- Checks uses of undeclared global variables. --- All global variables must be 'declared' through a regular --- assignment (even assigning nil will do) in a top-level --- chunk before being used anywhere or assigned to inside a function. --- From Lua distribution (etc/strict.lua). --- @class module --- @name strict +--[[-- + Checks uses of undeclared global variables. + + All global variables must be 'declared' through a regular + assignment (even assigning `nil` will do) in a top-level + chunk before being used anywhere or assigned to inside a function. + From Lua distribution (`etc/strict.lua`). + + @module std.strict +]] local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget @@ -21,6 +24,8 @@ local function what () return d and d.what or "C" end +--- Detect assignment to undeclared global. +-- @metamethod __newindex mt.__newindex = function (t, n, v) if not mt.__declared[n] then local w = what () @@ -32,6 +37,8 @@ mt.__newindex = function (t, n, v) rawset (t, n, v) end +--- Detect derefrence of undeclared global. +-- @metamethod __index mt.__index = function (t, n) if not mt.__declared[n] and what () ~= "C" then error ("variable '" .. n .. "' is not declared", 2) diff --git a/ext/std/string.lua b/ext/std/string.lua index 20ac896..f668a45 100644 --- a/ext/std/string.lua +++ b/ext/std/string.lua @@ -1,19 +1,26 @@ ---- Additions to the string module --- TODO: Pretty printing (use in getopt); see source for details. +--[[-- + Additions to the string module. + @module std.string +]] local func = require "std.functional" local list = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" +local _assert = _G.assert +local _format = string.format +local _tostring = _G.tostring +local old__index = getmetatable ("").__index + local M = {} --- Extend to work better with one argument. -- If only one argument is passed, no formatting is attempted. -- @param f format +-- @param arg1 first argument to format -- @param ... arguments to format -- @return formatted string -local _format = string.format local function format (f, arg1, ...) if arg1 == nil then return f @@ -27,7 +34,6 @@ end -- @param f format -- @param ... arguments to format -- @return value -local _assert = assert local function assert (v, f, ...) if not v then if f == nil then @@ -55,12 +61,12 @@ local function tfind (s, p, init, plain) return pack (p.find (s, p, init, plain)) end ---- Do multiple finds on a string. +--- Do multiple `find`s on a string. -- @param s target string -- @param p pattern -- @param init start position (default: 1) -- @param plain inhibit magic characters (default: nil) --- @return list of {from, to; capt = {captures}} +-- @return list of `{from, to; capt = {captures}}` local function finds (s, p, init, plain) init = init or 1 local l = {} @@ -76,7 +82,7 @@ local function finds (s, p, init, plain) end --- Split a string at a given separator. --- FIXME: Consider Perl and Python versions. +-- @todo Consider Perl and Python versions. -- @param s string to split -- @param sep separator pattern -- @return list of strings @@ -93,12 +99,12 @@ local function split (s, sep) return l end ---- Require a module with a particular version +--- Require a module with a particular version. -- @param module module to require -- @param min lowest acceptable version (default: any) -- @param too_big lowest version that is too big (default: none) --- @pattern pattern to match version in module.version or --- module.VERSION (default: ".*[%.%d]+" +-- @param pattern to match version in `module.version` or +-- `module.VERSION` (default: `".*[%.%d]+"` local function require_version (module, min, too_big, pattern) local function version_to_list (v) return list.new (split (v, "%.")) @@ -155,6 +161,7 @@ end -- @param elem element renderer -- @param pair pair renderer -- @param sep separator renderer +-- @param roots accumulates table references to detect recursion -- @return string representation local function render (x, open, close, elem, pair, sep, roots) local function stop_roots (x) @@ -186,28 +193,22 @@ local function render (x, open, close, elem, pair, sep, roots) end --- --- @class function --- @name render_OpenRenderer +-- @function render_OpenRenderer -- @param t table -- @return open table string --- --- @class function --- @name render_CloseRenderer +-- @function render_CloseRenderer -- @param t table -- @return close table string --- --- @class function --- @name render_ElementRenderer +-- @function render_ElementRenderer -- @param e element -- @return element string ---- --- @class function --- @name render_PairRenderer --- N.B. the function should not try to render i and v, or treat --- them recursively. +--- NB. the function should not try to render i and v, or treat them recursively. +-- @function render_PairRenderer -- @param t table -- @param i index -- @param v value @@ -216,8 +217,7 @@ end -- @return element string --- --- @class function --- @name render_SeparatorRenderer +-- @function render_SeparatorRenderer -- @param t table -- @param i preceding index (nil on first call) -- @param v preceding value (nil on first call) @@ -225,12 +225,10 @@ end -- @param w following value (nil on last call) -- @return separator string ---- Extend tostring to work better on tables. --- @class function --- @name tostring +--- Extend `tostring` to work better on tables. +-- @function tostring -- @param x object to convert to string -- @return string representation -local _tostring = _G.tostring local function tostring (x) return render (x, function () return "{" end, @@ -309,7 +307,7 @@ end --- Convert a value to a string. -- The string can be passed to dostring to retrieve the value. ---
    TODO: Make it work for recursive tables. +-- @todo Make it work for recursive tables. -- @param x object to pickle -- @return string such that eval (s) is the same value as x local function pickle (x) @@ -338,9 +336,8 @@ end --- Give strings a subscription operator. -- @param s string -- @param i index --- @return string.sub (s, i, i) if i is a number, or +-- @return `string.sub (s, i, i)` if i is a number, or -- falls back to any previous metamethod (by default, string methods) -local old__index = getmetatable ("").__index getmetatable ("").__index = function (s, i) if type (i) == "number" then return s:sub (i, i) @@ -353,7 +350,7 @@ end --- Give strings an append metamethod. -- @param s string -- @param c character (1-character string) --- @return s .. c +-- @return `s .. c` getmetatable ("").__append = function (s, c) return s .. c end @@ -383,10 +380,9 @@ local function chomp (s) return (string.gsub (s, "\n$", "")) end ---- Escape a string to be used as a pattern +--- Escape a string to be used as a pattern. -- @param s string to process --- @return --- @param s_: processed string +-- @return processed string local function escape_pattern (s) return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) end @@ -423,7 +419,7 @@ end -- @param s string to justify -- @param w width to justify to (-ve means right-justify; +ve means -- left-justify) --- @param p string to pad with (default: " ") +-- @param p string to pad with (default: `" "`) -- @return justified string local function pad (s, w, p) p = string.rep (p or " ", math.abs (w)) @@ -492,7 +488,7 @@ end --- Remove leading matter from a string. -- @param s string --- @param r leading pattern (default: "%s+") +-- @param r leading pattern (default: `"%s+"`) -- @return string without leading r local function ltrim (s, r) r = r or "%s+" @@ -501,7 +497,7 @@ end --- Remove trailing matter from a string. -- @param s string --- @param r trailing pattern (default: "%s+") +-- @param r trailing pattern (default: `"%s+"`) -- @return string without trailing r local function rtrim (s, r) r = r or "%s+" @@ -510,14 +506,15 @@ end --- Remove leading and trailing matter from a string. -- @param s string --- @param r leading/trailing pattern (default: "%s+") +-- @param r leading/trailing pattern (default: `"%s+"`) -- @return string without leading/trailing r local function trim (s, r) return rtrim (ltrim (s, r), r) end -for k, v in pairs { +--- @export +local String = { __index = old__index, assert = assert, caps = caps, @@ -549,7 +546,10 @@ for k, v in pairs { -- Core Lua function implementations. _format = _format, _tostring = _tostring, -} do +} + +for k, v in pairs (String) +do M[k] = v end diff --git a/ext/std/table.lua b/ext/std/table.lua index 6ed1f6c..1c6699d 100644 --- a/ext/std/table.lua +++ b/ext/std/table.lua @@ -1,25 +1,59 @@ --- Extensions to the table module +--[[-- + Extensions to the table module. + @module std.table +]] local base = require "std.base" local func = require "std.functional" + +--- Make a shallow copy of a table, including any metatable. +-- +-- To make deep copies, use @{std.tree.clone}. +-- @function clone +-- @tparam table t source table +-- @tparam boolean nometa if non-nil don't copy metatable +-- @return copy of *table* +local clone = base.clone + + +--- Clone a table, renaming some keys. +-- @function clone_rename +-- @tparam table t source table +-- @tparam table map table `{old_key=new_key, ...}` +-- @return copy of *table* +local clone_rename = base.clone_rename + + +--- Destructively merge another table's fields into *table*. +-- @function merge +-- @tparam table t destination table +-- @tparam table u table with fields to merge +-- @return table `t` with fields from `u` merged in +local merge = base.merge + + +-- Preserve core table sort function. local _sort = table.sort + --- Make table.sort return its result. --- @param t table --- @param c comparator function --- @return sorted table +-- @tparam table t unsorted table +-- @tparam function c comparator function +-- @return `t` with keys sorted accordind to `c` local function sort (t, c) _sort (t, c) return t end + --- Return whether table is empty. --- @param t table --- @return true if empty or false otherwise +-- @tparam table t any table +-- @return `true` if `t` is empty, otherwise `false` local function empty (t) return not next (t) end + --- Turn a tuple into a list. -- @param ... tuple -- @return list @@ -27,9 +61,10 @@ local function pack (...) return {...} end + --- Find the number of elements in a table. --- @param t table --- @return number of elements in t +-- @tparam table t any table +-- @return number of non-nil values in `t` local function size (t) local n = 0 for _ in pairs (t) do @@ -38,44 +73,48 @@ local function size (t) return n end ---- Make the list of keys of a table. --- @param t table --- @return list of keys + +--- Make the list of keys in table. +-- @tparam table t any table +-- @treturn table list of keys local function keys (t) - local u = {} - for i, v in pairs (t) do - table.insert (u, i) + local l = {} + for k, _ in pairs (t) do + table.insert (l, k) end - return u + return l end + --- Make the list of values of a table. --- @param t table --- @return list of values +-- @tparam table t any table +-- @treturn table list of values local function values (t) - local u = {} - for i, v in pairs (t) do - table.insert (u, v) + local l = {} + for _, v in pairs (t) do + table.insert (l, v) end - return u + return l end + --- Invert a table. --- @param t table {i=v, ...} --- @return inverted table {v=i, ...} +-- @tparam table t a table with `{k=v, ...}` +-- @treturn table inverted table `{v=k, ...}` local function invert (t) - local u = {} - for i, v in pairs (t) do - u[v] = i + local i = {} + for k, v in pairs (t) do + i[v] = k end - return u + return i end + --- An iterator like ipairs, but in reverse. --- @param t table to iterate over --- @return iterator function --- @return the table, as above --- @return #t + 1 +-- @tparam table t any table +-- @treturn function iterator function +-- @treturn table the table, `t` +-- @treturn number `#t + 1` local function ripairs (t) return function (t, n) n = n - 1 @@ -86,9 +125,10 @@ local function ripairs (t) t, #t + 1 end + --- Turn an object into a table according to __totable metamethod. --- @param x object to turn into a table --- @return table or nil +-- @tparam std.object x object to turn into a table +-- @treturn table resulting table or `nil` local function totable (x) local m = func.metamethod (x, "__totable") if m then @@ -100,10 +140,11 @@ local function totable (x) end end + --- Make a table with a default value for unset keys. --- @param x default entry value (default: nil) --- @param t initial table (default: {}) --- @return table whose unset elements are x +-- @param x default entry value (default: `nil`) +-- @tparam table t initial table (default: `{}`) +-- @treturn table table whose unset elements are x local function new (x, t) return setmetatable (t or {}, {__index = function (t, i) @@ -111,13 +152,15 @@ local function new (x, t) end}) end -local M = { - clone = base.clone, - clone_rename = base.clone_rename, + +--- @export +local Table = { + clone = clone, + clone_rename = clone_rename, empty = empty, invert = invert, keys = keys, - merge = base.merge, + merge = merge, new = new, pack = pack, ripairs = ripairs, @@ -126,12 +169,12 @@ local M = { totable = totable, values = values, - -- Core Lua table.sort function. + -- Core Lua table.sort function _sort = _sort, } for k, v in pairs (table) do - M[k] = M[k] or v + Table[k] = Table[k] or v end -return M +return Table diff --git a/ext/std/tree.lua b/ext/std/tree.lua index f9f93b1..7913de2 100644 --- a/ext/std/tree.lua +++ b/ext/std/tree.lua @@ -1,28 +1,46 @@ ---- Tables as trees. -local base = require "std.base" -local ileaves, leaves = base.ileaves, base.leaves +--[[-- + Tables as trees. + @module std.tree +]] +local base = require "std.base" local list = require "std.list" local func = require "std.functional" - local metatable = {} ---- Make a table into a tree --- @param t table --- @return tree + + +--- Tree iterator which returns just numbered leaves, in order. +-- @function ileaves +-- @tparam std.tree tr tree table +-- @treturn function iterator function +-- @treturn std.tree the tree `tr` +local ileaves = base.ileaves + + +--- Tree iterator which returns just leaves. +-- @function leaves +-- @tparam std.tree tr tree table +-- @treturn function iterator function +-- @treturn std.tree the tree, `tr` +local leaves = base.leaves + + +--- Make a table into a tree. +-- @tparam table t any table +-- @treturn std.tree a new tree table local function new (t) return setmetatable (t or {}, metatable) end ---- Tree __index metamethod. --- @param tr tree --- @param i non-table, or list of keys {i1 ... --- in} --- @return tr[i]...[in] if i is a table, or --- tr[i] otherwise + +--- Tree `__index` metamethod. +-- @metamethod __index +-- @param i non-table, or list of keys `{i\_1 ... i\_n}` +-- @return `tr[i]...[i\_n]` if i is a table, or `tr[i]` otherwise +-- @todo the following doesn't treat list keys correctly +-- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? function metatable.__index (tr, i) - -- FIXME: the following doesn't treat list keys correctly - -- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? if type (i) == "table" and #i > 0 then return list.foldl (func.op["[]"], tr, i) else @@ -30,12 +48,12 @@ function metatable.__index (tr, i) end end ---- Tree __newindex metamethod. --- Sets tr[i1]...[in] = v if i is a --- table, or tr[i] = v otherwise --- @param tr tree --- @param i non-table, or list of keys {i1 ... --- in} + +--- Tree `__newindex` metamethod. +-- +-- Sets `tr[i\_1]...[i\_n] = v` if i is a table, or `tr[i] = v` otherwise +-- @metamethod __newindex +-- @param i non-table, or list of keys `{i\_1 ... i\_n}` -- @param v value function metatable.__newindex (tr, i, v) if type (i) == "table" then @@ -51,10 +69,13 @@ function metatable.__newindex (tr, i, v) end end ---- Make a deep copy of a tree, including any metatables --- @param t table --- @param nometa if non-nil don't copy metatables --- @return copy of table + +--- Make a deep copy of a tree, including any metatables. +-- +-- To make fast shallow copies, use @{std.table.clone}. +-- @tparam table t table to be cloned +-- @tparam boolean nometa if non-nil don't copy metatables +-- @treturn table a deep copy of `t` local function clone (t, nometa) local r = {} if not nometa then @@ -82,13 +103,13 @@ local function clone (t, nometa) return copy (r, t) end ---- --- @class function --- @name tree_Iterator --- @param n current node --- @return type ("leaf", "branch" (pre-order) or "join" (post-order)) --- @return path to node ({i1...ik}) --- @return node + +--- Tree iterator. +-- @tparam function it iterator function +-- @tparam std.tree tr tree +-- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order)) +-- @treturn table path to node ({i\_1...i\_k}) +-- @return node local function _nodes (it, tr) local p = {} local function visit (n) @@ -107,29 +128,30 @@ local function _nodes (it, tr) return coroutine.wrap (visit), tr end ---- Tree iterator. --- @see tree_Iterator --- @param tr tree to iterate over --- @return iterator function --- @return the tree, as above + +--- Tree iterator over all nodes. +-- @tparam std.tree tr tree to iterate over +-- @treturn function iterator function +-- @treturn std.tree the tree, `tr` local function nodes (tr) return _nodes (pairs, tr) end + --- Tree iterator over numbered nodes, in order. --- @see tree_Iterator --- @param tr tree to iterate over --- @return iterator function --- @return the tree, as above +-- @tparam std.tree tr tree to iterate over +-- @treturn function iterator function +-- @treturn std.tree the tree, `t` local function inodes (tr) return _nodes (ipairs, tr) end ---- Deep-merge one tree into another. u is merged into ---- t. --- @param t first tree --- @param u second tree --- @return first tree + +--- Destructively deep-merge one tree into another. +-- @tparam std.tree t destination tree +-- @tparam std.tree u tree with nodes to merge +-- @treturn std.tree `t` with nodes from `u` merged in +-- @see std.table.merge local function merge (t, u) for ty, p, n in nodes (u) do if ty == "leaf" then @@ -139,8 +161,9 @@ local function merge (t, u) return t end --- Public interface -local M = { + +--- @export +local Tree = { clone = clone, ileaves = ileaves, inodes = inodes, @@ -150,4 +173,4 @@ local M = { nodes = nodes, } -return M +return Tree diff --git a/local.mk b/local.mk index 814f4e2..edf4107 100644 --- a/local.mk +++ b/local.mk @@ -76,6 +76,7 @@ mkrockspecs_args = --module-dir $(srcdir)/ext ## ------------- ## EXTRA_DIST += \ + doc/config.ld \ ext/std.lua.in \ $(NOTHING_ELSE) @@ -85,11 +86,11 @@ EXTRA_DIST += \ ## -------------- ## dist_doc_DATA += \ - $(srcdir)/ext/index.html \ - $(srcdir)/ext/luadoc.css + $(srcdir)/doc/index.html \ + $(srcdir)/doc/luadoc.css dist_files_DATA += $(wildcard $(srcdir)/ext/files/*.html) dist_modules_DATA += $(wildcard $(srcdir)/ext/modules/*.html) $(dist_doc_DATA): $(dist_lua_DATA) $(dist_luastd_DATA) - cd $(srcdir)/ext && $(LUADOC) *.lua std/*.lua + cd $(srcdir) && $(LDOC) -c doc/config.ld . From 6738034c0a856956e91568ec3abe21dc39e0e11b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Nov 2013 00:42:38 -0800 Subject: [PATCH 004/703] getopt: revert an accidental comment deletion. * ext/std/getopt.lua (makeOptions): Revert accidental deletion. Signed-off-by: Gary V. Vaughan --- ext/std/getopt.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/std/getopt.lua b/ext/std/getopt.lua index 6437c1b..80677f3 100644 --- a/ext/std/getopt.lua +++ b/ext/std/getopt.lua @@ -111,7 +111,7 @@ end -- Object that defines a single Option entry. local Option = Object {_init = {"name", "desc", "type", "var"}} -- Options table constructor: adds lookup tables for the option names. +--- Options table constructor: adds lookup tables for the option names. local function makeOptions (t) local options, name = {}, {} local function appendOpt (v, nodupes) From 803b7ee4de4d59340095b8ea2de8b26426ef7b23 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Nov 2013 00:44:36 -0800 Subject: [PATCH 005/703] .gitignore: ignore ldoc output, not luadoc output. * .gitignore: Remove /doc/luadoc.css. Add /doc/ldoc.css. Signed-off-by: Gary V. Vaughan --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 830bffd..60ec914 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ /doc/classes /doc/files /doc/index.html -/doc/luadoc.css +/doc/ldoc.css /doc/modules /ext/std.lua /stdlib-*.tar.gz From 46bccaba9afacf2790b8be5ca42cccd9bd87e13c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Nov 2013 00:49:03 -0800 Subject: [PATCH 006/703] list: fix broken specs. * ext/std/list.lua (depair): Needs to be the inverse operation of enpair, which requires returning a bare table, and not a List object. Signed-off-by: Gary V. Vaughan --- ext/std/list.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/std/list.lua b/ext/std/list.lua index 88a182c..3d292eb 100644 --- a/ext/std/list.lua +++ b/ext/std/list.lua @@ -238,11 +238,11 @@ end --- Turn a list of pairs into a table. -- @todo Find a better name. --- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` --- @treturn std.list a new list containing table `{i1=v1, ..., in=vn}` +-- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` +-- @treturn table a new list containing table `{i1=v1, ..., in=vn}` -- @see std.list:enpair local function depair (ls) - local t = List {} + local t = {} for v in elems (ls) do t[v[1]] = v[2] end From 934948906a98feca09255bded9bb5e30a50e4bfa Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Nov 2013 00:54:21 -0800 Subject: [PATCH 007/703] travis: use unofficial ldoc 1.4.0 release candidate. We're using features from the release candidate already, so temporarily point the travis installer to that rockspec while waiting for an official release. * .travis.yml: Adjust ldoc rockspec location. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6296b66..3b2a043 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ install: - sudo apt-get install liblua5.2-dev # Luadoc and Ldoc work best on Travis with Lua 5.1. - sudo apt-get install luarocks - - sudo luarocks install ldoc + - sudo luarocks install http://stevedonovan.github.io/files/ldoc-1.4.0-1.rockspec - mkdir -p luarocks/bin - sed 's|^exec "[^"]*"|exec lua5.1|' `which ldoc` > $GENDOC - chmod a+rx $GENDOC From 37117966be2df30e74f10ef4132da2b47bbc7157 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Nov 2013 01:01:35 -0800 Subject: [PATCH 008/703] maint: remove last references to luadoc. * local.mk (dist_doc_DATA): Remove luadoc.css. Add ldoc.css. Signed-off-by: Gary V. Vaughan --- local.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/local.mk b/local.mk index edf4107..1a95b79 100644 --- a/local.mk +++ b/local.mk @@ -61,7 +61,7 @@ dist_luastd_DATA = \ # In order to avoid regenerating std.lua at configure time, which # causes the documentation to be rebuilt and hence requires users to -# have luadoc installed, put std/std.lua in as a Makefile dependency. +# have ldoc installed, put std/std.lua in as a Makefile dependency. # (Strictly speaking, distributing an AC_CONFIG_FILE would be wrong.) ext/std.lua: ext/std.lua.in ./config.status --file=$@ @@ -87,7 +87,7 @@ EXTRA_DIST += \ dist_doc_DATA += \ $(srcdir)/doc/index.html \ - $(srcdir)/doc/luadoc.css + $(srcdir)/doc/ldoc.css dist_files_DATA += $(wildcard $(srcdir)/ext/files/*.html) dist_modules_DATA += $(wildcard $(srcdir)/ext/modules/*.html) From 0b8dfe7a178cc91ec65a6fad04d2d693577db365 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Nov 2013 01:18:40 -0800 Subject: [PATCH 009/703] doc: point to updated online LDoc documentation. * README.md (Documentation): Refer to latest LDoc docs. Signed-off-by: Gary V. Vaughan --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a7bb94..a076bf0 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ stdlib. Documentation ------------- -The libraries are [documented in LuaDoc][github.io]. Pre-built HTML +The libraries are [documented in LDoc][github.io]. Pre-built HTML files are included. [github.io]: http://rrthomas.github.io/lua-stdlib From 1860a8ad5672279eb2f9d1da9bd8045d56b0fe55 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Nov 2013 13:42:25 +1300 Subject: [PATCH 010/703] doc: LDoc master now handles local references differently. * .travis.yml (install): Switch to github master rockspec. * ext/std/list.lua (enpair, depair, __concat, __add): Adjust @see argument. * ext/std/object.lua (__totable, __tostring): Likewise. * ext/std/set.lua (__add, __sub, __mul, __div, __le, __lt): Likewise. * ext/std/strbuf.lua (__concat, __tostring): Likewise. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- ext/std/list.lua | 8 ++++---- ext/std/object.lua | 4 ++-- ext/std/set.lua | 12 ++++++------ ext/std/strbuf.lua | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3b2a043..f0e88d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ install: - sudo apt-get install liblua5.2-dev # Luadoc and Ldoc work best on Travis with Lua 5.1. - sudo apt-get install luarocks - - sudo luarocks install http://stevedonovan.github.io/files/ldoc-1.4.0-1.rockspec + - sudo luarocks install http://raw.github.com/stevedonovan/LDoc/master/ldoc-scm-2.rockspec - mkdir -p luarocks/bin - sed 's|^exec "[^"]*"|exec lua5.1|' `which ldoc` > $GENDOC - chmod a+rx $GENDOC diff --git a/ext/std/list.lua b/ext/std/list.lua index 3d292eb..9bd23e0 100644 --- a/ext/std/list.lua +++ b/ext/std/list.lua @@ -226,7 +226,7 @@ end -- @todo Find a better name. -- @tparam table t a table `{i1=v1, ..., in=vn}` -- @treturn std.list a new list containing `{{i1, v1}, ..., {in, vn}}` --- @see std.list:depair +-- @see depair local function enpair (t) local ls = List {} for i, v in pairs (t) do @@ -240,7 +240,7 @@ end -- @todo Find a better name. -- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` --- @see std.list:enpair +-- @see enpair local function depair (ls) local t = {} for v in elems (ls) do @@ -357,14 +357,14 @@ List = Object { -- Concatenate lists. -- new = list .. table -- @metamethod __concat - -- @see std.list:concat + -- @see concat __concat = concat, ------ -- Append to list. -- list = list + element -- @metamethod __add - -- @see std.list:append + -- @see append __add = append, ------ diff --git a/ext/std/object.lua b/ext/std/object.lua index 30bba8c..1255347 100644 --- a/ext/std/object.lua +++ b/ext/std/object.lua @@ -225,13 +225,13 @@ local metatable = { -- intial new object table, and can be overridden in other objects -- for greater control of which fields are considered non-private. -- @metamethod __totable - -- @see std.object:totable + -- @see totable __totable = totable, ------ -- Return a string representation of *object*. -- @metamethod __tostring - -- @see std.object:tostring + -- @see tostring __tostring = stringify, --- @export diff --git a/ext/std/set.lua b/ext/std/set.lua index cb527cc..457acc3 100644 --- a/ext/std/set.lua +++ b/ext/std/set.lua @@ -166,42 +166,42 @@ Set = Object { -- Union operator. -- set + table = union -- @metamethod __add - -- @see std.set:union + -- @see union __add = union, ------ -- Difference operator. -- set - table = set difference -- @metamethod __sub - -- @see std.set:difference + -- @see difference __sub = difference, ------ -- Intersection operator. -- set * table = intersection -- @metamethod __mul - -- @see std.set:intersection + -- @see intersection __mul = intersection, ------ -- Symmetric difference operator. -- set / table = symmetric difference -- @metamethod __div - -- @see std.set:symmetric_difference + -- @see symmetric_difference __div = symmetric_difference, ------ -- Subset operator. -- set <= table = subset -- @metamethod __le - -- @see std.set:subset + -- @see subset __le = subset, ------ -- Proper subset operator. -- set < table = proper subset -- @metamethod __lt - -- @see std.set:propersubset + -- @see propersubset __lt = propersubset, ------ diff --git a/ext/std/strbuf.lua b/ext/std/strbuf.lua index f360669..2903ac0 100644 --- a/ext/std/strbuf.lua +++ b/ext/std/strbuf.lua @@ -33,7 +33,7 @@ return Object { -- Support concatenation of StrBuf objects. -- buffer = buffer .. str -- @metamethod __concat - -- @see std.strbuf:concat + -- @see concat __concat = concat, @@ -41,7 +41,7 @@ return Object { -- Support fast conversion to Lua string. -- str = tostring (buffer) -- @metamethod __tostring - -- @see std.strbuf:tostring + -- @see tostring __tostring = tostring, From c2b6cc5c949d3866d9ccbb1b7831f9caeaf6ff99 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Nov 2013 13:57:03 +1300 Subject: [PATCH 011/703] doc: markup std.string.escape_string correctly for LDoc. * ext/std/string.lua (escape_string): Add missing leading hyphen. Signed-off-by: Gary V. Vaughan --- ext/std/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/std/string.lua b/ext/std/string.lua index f668a45..392d982 100644 --- a/ext/std/string.lua +++ b/ext/std/string.lua @@ -387,7 +387,7 @@ local function escape_pattern (s) return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) end --- Escape a string to be used as a shell token. +--- Escape a string to be used as a shell token. -- Quotes spaces, parentheses, brackets, quotes, apostrophes and -- whitespace. -- @param s string to process From a9fc7aab88e18ced7c7e6e36000d3a3af22f417d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Nov 2013 14:30:56 +1300 Subject: [PATCH 012/703] doc: separate camelCaseCompat methods to avoid LDoc warnings. Using the @export feature of LDoc provides the flexibility to change the exported functions from a single table, but we also have to be careful not to sweep up undocumented access points in the same table, otherwise LDoc correctly warns us that those functions have no doc-comments. * ext/std/getopt.lua (getOpt, processArgs, usageInfo): Move to a separate table, and merge back into the module table before return. * ext/std/io.lua (processFiles): Likewise. * ext/std/list.lua (indexKey, indexValue, mapWith, zipWith): Likewise. * ext/std/string.lua (escapePattern, escapeShell, ordinalSuffix): Likewise. Signed-off-by: Gary V. Vaughan --- ext/std/getopt.lua | 6 +++-- ext/std/io.lua | 6 ++--- ext/std/list.lua | 59 ++++++++++++++++++++++++---------------------- ext/std/string.lua | 8 +++---- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/ext/std/getopt.lua b/ext/std/getopt.lua index 80677f3..8d9c2cb 100644 --- a/ext/std/getopt.lua +++ b/ext/std/getopt.lua @@ -287,11 +287,13 @@ local Getopt = { processargs = processargs, usage = usage, usageinfo = usageinfo, +} - -- camelCase compatibility. +-- camelCase compatibility. +Getopt = table.merge (Getopt, { getOpt = getopt, processArgs = processargs, usageInfo = usageinfo, -} +}) return table.merge (M, Getopt) diff --git a/ext/std/io.lua b/ext/std/io.lua index bcf43f7..d72883d 100644 --- a/ext/std/io.lua +++ b/ext/std/io.lua @@ -147,11 +147,11 @@ local M = { splitdir = splitdir, warn = warn, writelines = writelines, - - -- camelCase compatibility. - processFiles = process_files, } +-- camelCase compatibility. +M.processFiles = process_files + for k, v in pairs (io) do M[k] = M[k] or v end diff --git a/ext/std/list.lua b/ext/std/list.lua index 9bd23e0..5eae3df 100644 --- a/ext/std/list.lua +++ b/ext/std/list.lua @@ -349,6 +349,35 @@ local function index_value (l, f) end +--- @export +local metamethods = { + append = append, + compare = compare, + concat = concat, + cons = cons, + depair = depair, + elems = elems, + enpair = enpair, + filter = filter, + flatten = flatten, + foldl = foldl, + foldr = foldr, + index_key = index_key, + index_value = index_value, + map = map, + map_with = map_with, + project = project, + relems = relems, + rep = rep, + reverse = reverse, + shape = shape, + sub = sub, + tail = tail, + transpose = transpose, + zip_with = zip_with, +} + + List = Object { -- Derived object type. _type = "List", @@ -385,39 +414,13 @@ List = Object { -- @see std.list:compare __le = function (l, m) return compare (l, m) <= 0 end, - --- @export - __index = { - append = append, - compare = compare, - concat = concat, - cons = cons, - depair = depair, - elems = elems, - enpair = enpair, - filter = filter, - flatten = flatten, - foldl = foldl, - foldr = foldr, - index_key = index_key, - index_value = index_value, - map = map, - map_with = map_with, - project = project, - relems = relems, - rep = rep, - reverse = reverse, - shape = shape, - sub = sub, - tail = tail, - transpose = transpose, - zip_with = zip_with, - + __index = base.merge (metamethods, { -- camelCase compatibility. indexKey = index_key, indexValue = index_value, mapWith = map_with, zipWith = zip_with, - }, + }), } diff --git a/ext/std/string.lua b/ext/std/string.lua index 392d982..b015fa6 100644 --- a/ext/std/string.lua +++ b/ext/std/string.lua @@ -537,7 +537,10 @@ local String = { tostring = tostring, trim = trim, wrap = wrap, +} +-- Merge non-@export functions: +for k,v in pairs (table.merge (String, { -- camelCase compatibility: escapePattern = escape_pattern, escapeShell = escape_shell, @@ -546,10 +549,7 @@ local String = { -- Core Lua function implementations. _format = _format, _tostring = _tostring, -} - -for k, v in pairs (String) -do +})) do M[k] = v end From 66a9a2cae7c9fbfb5265ee3445ca8f8f4ee56e3d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Nov 2013 16:04:51 +1300 Subject: [PATCH 013/703] doc: use a more topographical ordering for document sidebar. Now that the latest LDoc doesn't enforce strict alphabetical ordering, reorder the documentation into a more topographical order for easier reading. * doc/config.ld (file): Reorder for clarity. Signed-off-by: Gary V. Vaughan --- doc/config.ld | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/config.ld b/doc/config.ld index 115aae1..9e9b318 100644 --- a/doc/config.ld +++ b/doc/config.ld @@ -5,22 +5,26 @@ dir = "." new_type("metamethod", "Metamethods", false, "params") file = { + -- Modules + "../ext/std.lua", "../ext/std/debug.lua", "../ext/std/functional.lua", "../ext/std/getopt.lua", "../ext/std/io.lua", - "../ext/std/list.lua", "../ext/std/math.lua", - "../ext/std/object.lua", "../ext/std/package.lua", - "../ext/std/set.lua", - "../ext/std/strbuf.lua", "../ext/std/strict.lua", "../ext/std/string.lua", "../ext/std/table.lua", "../ext/std/tree.lua", - "../ext/std.lua", + + -- Classes + "../ext/std/object.lua", + "../ext/std/list.lua", + "../ext/std/set.lua", + "../ext/std/strbuf.lua", } format = "markdown" sort = true + From f5db9844bccb27993246e58ee3dc268622aead20 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Nov 2013 16:07:00 +1300 Subject: [PATCH 014/703] doc: distinguish object and class methods in object doc-comments. * ext/std/object.lua (clone): Rename first argument to self, and remove associated @param as a hint to LDoc. (stringify, totable): Likewise. (metatable): Document important fields. (metatable.__call): Add documentation. Signed-off-by: Gary V. Vaughan --- ext/std/object.lua | 80 ++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/ext/std/object.lua b/ext/std/object.lua index 1255347..7153edc 100644 --- a/ext/std/object.lua +++ b/ext/std/object.lua @@ -77,8 +77,8 @@ end -- stack:type () --> "Stack" -- -- @function type --- @tparam std.object o an object --- @treturn string type of the object +-- @tparam std.object o an object +-- @treturn string type of the object local function object_type (o) local _type = metaentry (o, "_type") if type (o) == "table" and _type ~= nil then @@ -90,23 +90,23 @@ end --- Clone an object. -- --- Prototypes are cloned by calling directly as described above, so this --- `clone` method is rarely used explicitly. --- @tparam std.object prototype source object --- @param ... arguments --- @treturn std.object a clone of `prototype`, adjusted --- according to the rules above, and sharing a metatable where possible. -local function clone (prototype, ...) - local mt = getmetatable (prototype) +-- Objects are usually cloned by calling a prototype directly, so this +-- method is rarely used explicitly. +-- @param ... arguments for _init +-- @treturn std.object a clone of `prototype`, adjusted +-- according to the rules above, and sharing a metatable where possible +-- @see __call +local function clone (self, ...) + local mt = getmetatable (self) -- Make a shallow copy of prototype. local object = {} - for k, v in pairs (prototype) do + for k, v in pairs (self) do object[k] = v end -- Map arguments according to _init metamethod. - local _init = metaentry (prototype, "_init") + local _init = metaentry (self, "_init") if type (_init) == "table" then base.merge (object, base.clone_rename (_init, ...)) else @@ -123,7 +123,7 @@ local function clone (prototype, ...) if next (object_mt) == nil then -- Reuse metatable if possible - object_mt = getmetatable (prototype) + object_mt = getmetatable (self) else -- Otherwise copy the prototype metatable... @@ -160,11 +160,10 @@ end -- `__tostring` metamethods in contained objects. -- -- @function tostring --- @tparam std.object o an object -- @treturn string stringified object representation -local function stringify (o) - local totable = getmetatable (o).__totable - local array = base.clone (totable (o), "nometa") +local function stringify (self) + local totable = getmetatable (self).__totable + local array = base.clone (totable (self), "nometa") local other = base.clone (array, "nometa") local s = "" if #other > 0 then @@ -188,19 +187,16 @@ local function stringify (o) s = s .. table.concat (dict, ", ") end - return metaentry (o, "_type") .. " {" .. s .. "}" + return metaentry (self, "_type") .. " {" .. s .. "}" end --- Return a new table with a shallow copy of all non-private fields --- in object. --- --- Where private fields have keys prefixed with "_". --- @tparam std.object o an object --- @treturn table raw (non-object) table of object fields -local function totable (o) +-- in object, where private fields have keys prefixed with "_". +-- @treturn table raw (non-object) table of object fields +local function totable (self) local t = {} - for k, v in pairs (o) do + for k, v in pairs (self) do if type (k) ~= "string" or k:sub (1, 1) ~= "_" then t[k] = v end @@ -209,11 +205,17 @@ local function totable (o) end --- Metatable for objects --- Normally a cloned object will share its metatable with its prototype, --- unless some new fields for the cloned object begin with '_', in which --- case they are merged into a copy of the prototype metatable to form --- a new metatable for the cloned object (and its clones). +--- Metatable for objects. +-- +-- This can't and shouldn't be set directly, because the Object class +-- manages it transparently during cloning. +-- @table __metatable +-- @tfield string _type derived objects can override this for objects +-- intended to be a prototype for further specialised objects. +-- @tfield table|function _init Derived objects can override this to be +-- a set of keys to use for assigning unnamed arguments, or a function +-- to that will be called with unnamed arguments durict cloning. +-- @see type local metatable = { _type = "Object", _init = {}, @@ -234,6 +236,19 @@ local metatable = { -- @see tostring __tostring = stringify, + --- Return a clone of *object*. + -- + -- Normally a cloned object will share its metatable with its prototype, + -- unless some new fields for the cloned object begin with '_', in which + -- case they are merged into a copy of the prototype metatable to form + -- a new metatable for the cloned object (and its clones). + -- @metamethod __call + -- @param ... arguments for `_init` + -- @treturn std.object a clone of the called object. + __call = function (self, ...) + return self:clone (...) + end, + --- @export __index = { clone = clone, @@ -241,11 +256,6 @@ local metatable = { totable = totable, type = object_type, }, - - -- Sugar instance creation - __call = function (self, ...) - return self:clone (...) - end, } return setmetatable ({}, metatable) From b3935fcc0fa2be5b8f895640b4cb3f7e241eae13 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Nov 2013 19:59:32 +1300 Subject: [PATCH 015/703] maint: install libyaml-dev for Travis. Seems recent tests have been failing because libyaml-dev is no longer installed by default. * .travis.yml (install): Add libyaml-dev which pulls in libyaml. Signed-off-by: Gary V. Vaughan --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f0e88d8..49b3e6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ env: # Tool setup. install: - sudo apt-get install help2man + - sudo apt-get install libyaml-dev - sudo apt-get install luajit - sudo apt-get install libluajit-5.1-dev - sudo apt-get install lua5.1 From 4cab49c3cad3405e7fe552602c5f6cad75e6d45c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Nov 2013 20:30:21 +1300 Subject: [PATCH 016/703] travis: replace recently missing links for libyaml Rerunning previously passing integration test shows that the multilibbed libyaml libraries are no longer available from /usr/lib alongside other libs - maybe an OS upgrade, or an upstream packaging error? * .travis.yml (install): Remove libyaml-dev reinstall. Run a shell find command to link the multilib libyaml libs back into /usr/lib. If this command starts failing in future then it should mean libyaml has moved back to the proper place and we're failing to relink it manually, so this workaround can be removed. Signed-off-by: Gary V. Vaughan --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 49b3e6a..4aa54a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,13 +18,14 @@ env: # Tool setup. install: - sudo apt-get install help2man - - sudo apt-get install libyaml-dev - sudo apt-get install luajit - sudo apt-get install libluajit-5.1-dev - sudo apt-get install lua5.1 - sudo apt-get install liblua5.1-dev - sudo apt-get install lua5.2 - sudo apt-get install liblua5.2-dev + # Put back the missing links for libyaml, which are recently missing otherwise + - sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; # Luadoc and Ldoc work best on Travis with Lua 5.1. - sudo apt-get install luarocks - sudo luarocks install http://raw.github.com/stevedonovan/LDoc/master/ldoc-scm-2.rockspec From a7a6e05389cd7b4141b70950edbdb0c8dbb35287 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Tue, 26 Nov 2013 22:13:26 +0000 Subject: [PATCH 017/703] Remove some spurious comment markers in docstrings. These were introduced by commit 31b314c835 converting doc-comments to LDoc 1.4. --- ext/std/getopt.lua | 2 +- ext/std/list.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/std/getopt.lua b/ext/std/getopt.lua index 8d9c2cb..f57fb10 100644 --- a/ext/std/getopt.lua +++ b/ext/std/getopt.lua @@ -245,7 +245,7 @@ end --- Simple getopt wrapper. --- If the caller didn't supply their own already, -- adds `--version`/`-V` +-- If the caller didn't supply their own already, adds `--version`/`-V` -- and `--help`/`-h` options automatically; -- stops program if there was an error, or if `--help` or `--version` was -- used. diff --git a/ext/std/list.lua b/ext/std/list.lua index 5eae3df..ad80d39 100644 --- a/ext/std/list.lua +++ b/ext/std/list.lua @@ -96,7 +96,7 @@ end --- Return a sub-range of a list. --- (The equivalent of `string.sub` -- on strings; negative list indices +-- (The equivalent of `string.sub` on strings; negative list indices -- count from the end of the list.) -- @tparam table l a list -- @tparam number from start of range (default: 1) From b93f4c410cdefd0e0de9b79dd2049d3c26452e68 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Nov 2013 16:08:13 +1300 Subject: [PATCH 018/703] tree: fix argument order for list.foldl call. The argument order for list.foldl changed in 9fe27bb, but one call site wasn't updated to match. * ext/std/tree.lua (metatable.__index): Use new argument order for list.foldl call. Signed-off-by: Gary V. Vaughan --- ext/std/tree.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/std/tree.lua b/ext/std/tree.lua index 7913de2..4a29475 100644 --- a/ext/std/tree.lua +++ b/ext/std/tree.lua @@ -42,7 +42,7 @@ end -- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? function metatable.__index (tr, i) if type (i) == "table" and #i > 0 then - return list.foldl (func.op["[]"], tr, i) + return list.foldl (i, func.op["[]"], tr) else return rawget (tr, i) end From 9d1abd6b1a569424ba9c36f8904ed0332f8aa199 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Nov 2013 16:20:51 +1300 Subject: [PATCH 019/703] string: report assert failures from assert caller level. * ext/std/string.lua (assert): Call error with level 2, to report errors from the assert caller not assert itself. Signed-off-by: Gary V. Vaughan --- ext/std/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/std/string.lua b/ext/std/string.lua index b015fa6..c4008e8 100644 --- a/ext/std/string.lua +++ b/ext/std/string.lua @@ -39,7 +39,7 @@ local function assert (v, f, ...) if f == nil then f = "" end - error (format (f, ...)) + error (format (f, ...), 2) end return v end From a76a1dcfd16207307c1a7b570e18b82c225711f4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Nov 2013 11:07:30 +1300 Subject: [PATCH 020/703] tree: add specl specs, and fix simple API inconsistencies. * specs/tree_spec.yaml: New file. Examples of how tree APIs should behave. * specs/specs.mk (specl_SPECS): Add specs/tree_spec.yaml. * ext/std/base.lua (ileaves, leaves): Report non-table arguments. * ext/std/tree.lua (clone, inodes, nodes, merge): Likewise. Signed-off-by: Gary V. Vaughan --- ext/std/base.lua | 4 + ext/std/tree.lua | 10 ++ specs/specs.mk | 1 + specs/tree_spec.yaml | 362 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 377 insertions(+) create mode 100644 specs/tree_spec.yaml diff --git a/ext/std/base.lua b/ext/std/base.lua index 92730e2..bc26f82 100644 --- a/ext/std/base.lua +++ b/ext/std/base.lua @@ -126,11 +126,15 @@ end -- Doc-commented in tree.lua... local function ileaves (tr) + assert (type (tr) == "table", + "bad argument #1 to 'ileaves' (table expected, got " .. type (tr) .. ")") return _leaves (ipairs, tr) end -- Doc-commented in tree.lua... local function leaves (tr) + assert (type (tr) == "table", + "bad argument #1 to 'leaves' (table expected, got " .. type (tr) .. ")") return _leaves (pairs, tr) end diff --git a/ext/std/tree.lua b/ext/std/tree.lua index 4a29475..9af4113 100644 --- a/ext/std/tree.lua +++ b/ext/std/tree.lua @@ -77,6 +77,8 @@ end -- @tparam boolean nometa if non-nil don't copy metatables -- @treturn table a deep copy of `t` local function clone (t, nometa) + assert (type (t) == "table", + "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") local r = {} if not nometa then setmetatable (r, getmetatable (t)) @@ -134,6 +136,8 @@ end -- @treturn function iterator function -- @treturn std.tree the tree, `tr` local function nodes (tr) + assert (type (tr) == "table", + "bad argument #1 to 'nodes' (table expected, got " .. type (tr) .. ")") return _nodes (pairs, tr) end @@ -143,6 +147,8 @@ end -- @treturn function iterator function -- @treturn std.tree the tree, `t` local function inodes (tr) + assert (type (tr) == "table", + "bad argument #1 to 'inodes' (table expected, got " .. type (tr) .. ")") return _nodes (ipairs, tr) end @@ -153,6 +159,10 @@ end -- @treturn std.tree `t` with nodes from `u` merged in -- @see std.table.merge local function merge (t, u) + assert (type (t) == "table", + "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") + assert (type (u) == "table", + "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") for ty, p, n in nodes (u) do if ty == "leaf" then t[p] = n diff --git a/specs/specs.mk b/specs/specs.mk index dbec97e..5ffbf30 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -24,6 +24,7 @@ specl_SPECS = \ $(srcdir)/specs/strbuf_spec.yaml \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ + $(srcdir)/specs/tree_spec.yaml \ $(NOTHING_ELSE) EXTRA_DIST += \ diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml new file mode 100644 index 0000000..abeaf07 --- /dev/null +++ b/specs/tree_spec.yaml @@ -0,0 +1,362 @@ +specify tree: +- before: | + M = require "std.tree" + + tr = M.new {foo="foo", fnord=M.new {branch=M.new {bar="bar", baz="baz"}}, quux="quux"} + mt = getmetatable (tr) + +- describe new: + - before: + f = M.new + - it returns a new tree when nil is passed: + expect (f (nil)).should_equal {} + - it turns any table passed into a tree: + t = { "unique table" } + expect (f (t)).should_be (t) + expect (getmetatable (f (t))).should_be (mt) + - it understands branched nodes: + t = f {foo=f {branch=f {bar="leaf"}}} + expect (t).should_equal {foo={branch={bar="leaf"}}} + expect (t["foo"]).should_equal {branch={bar="leaf"}} + expect (getmetatable(t["foo"])).should_be (mt) + expect (t[{"foo", "branch", "bar"}]).should_equal "leaf" + + +- describe __index: + - it returns nil for a missing key: + expect (tr["no such key"]).should_be (nil) + - it returns nil for missing single element key lists: + expect (tr[{"no such key"}]).should_be (nil) + - it returns nil for missing multi-element key lists: | + expect (tr[{"fnord", "foo"}]).should_be (nil) + pending "see issue #39" + expect (tr[{"no", "such", "key"}]).should_be (nil) + - it returns a value for the given key: + expect (tr["foo"]).should_be "foo" + expect (tr["quux"]).should_be "quux" + - it returns values for single element key lists: + expect (tr[{"foo"}]).should_be "foo" + expect (tr[{"quux"}]).should_be "quux" + - it returns values for multi-element key lists: + expect (tr[{"fnord", "branch", "bar"}]).should_be "bar" + expect (tr[{"fnord", "branch", "baz"}]).should_be "baz" + + +- describe __newindex: + - before: + tr = M.new {} + - it stores values for simple keys: + tr["foo"] = "foo" + expect (tr).should_equal {foo="foo"} + - it stores values for single element key lists: + tr[{"foo"}] = "foo" + expect (tr).should_equal {foo="foo"} + - it stores values for multi-element key lists: + tr[{"foo", "bar"}] = "baz" + expect (tr).should_equal {foo={bar="baz"}} + - it separates branches for diverging key lists: + tr[{"foo", "branch", "bar"}] = "leaf1" + tr[{"foo", "branch", "baz"}] = "leaf2" + expect (tr).should_equal {foo={branch={bar="leaf1", baz="leaf2"}}} + + +- describe clone: + - before: + subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } + f = M.clone + - it does not just return the subject: + expect (f (subject)).should_not_be (subject) + - it does copy the subject: + expect (f (subject)).should_equal (subject) + - it makes a deep copy: + expect (f (subject).k1).should_not_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).should_equal (target) + expect (subject).should_be (subject) + - it diagnoses non-table arguments: + expect (f ()).should_error ("table expected") + expect (f "foo").should_error ("table expected") + + +- describe ileaves: + - before: + f = M.ileaves + l = {} + - it iterates over array part of a table argument: + for v in f {"first", "second", "3rd"} do l[1+#l]=v end + expect (l).should_equal {"first", "second", "3rd"} + - it iterates over array parts of nested table argument: + for v in f {{"one", {"two"}, {{"three"}, "four"}}, "five"} do + l[1+#l]=v + end + expect (l).should_equal {"one", "two", "three", "four", "five"} + - it skips hash part of a table argument: + for v in f {"first", "second"; third = "2rd"} do l[1+#l]=v end + expect (l).should_equal {"first", "second"} + - it skips hash parts of nested table argument: + for v in f {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} do + l[1+#l]=v + end + expect (l).should_equal {"one", "three", "five"} + - it works on trees too: + for v in f (M.new {M.new {"one", + M.new {two=2}, + M.new {M.new {"three"}, four=4} + }, + foo="bar", "five"}) + do + l[1+#l]=v + end + expect (l).should_equal {"one", "three", "five"} + - it diagnoses non-table arguments: + expect (f ()).should_error ("table expected") + expect (f "string").should_error ("table expected") + + +- describe inodes: + - before: | + f = M.inodes + + function traverse (subject) + l = {} + for ty, p, n in f (subject) do l[1+#l]={ty, p, n} end + return l + end + - it iterates over array part of a table argument: | + pending "see issue #40" + subject = {"first", "second", "3rd"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {3}, subject[3]}, -- 3rd, + {"join", {}, subject}} -- } + - it iterates over array parts of nested table argument: | + pending "see issue #40" + subject = {{"one", {"two"}, {{"three"}, "four"}}, "five"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,1}, subject[1][2][1]}, -- two, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,2}, subject[1][3][2]}, -- four, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"join", {}, subject}} -- } + - it skips hash part of a table argument: | + pending "see issue #40" + subject = {"first", "second"; third = "3rd"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"join", {}, subject}} -- } + - it skips hash parts of nested table argument: | + pending "see issue #40" + subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[3]}, -- five, + {"join", {}, subject}} -- } + - it works on trees too: | + pending "see issue #40" + subject = M.new {M.new {"one", + M.new {two=2}, + M.new {M.new {"three"}, four=4}}, + foo="bar", + "five"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {3}, subject[3]}, -- five, + {"join", {}, subject}} -- } + - it diagnoses non-table arguments: + expect (f ()).should_error ("table expected") + expect (f "string").should_error ("table expected") + + +- describe leaves: + - before: + f = M.leaves + l = {} + - it iterates over elements of a table argument: + for v in f {"first", "second", "3rd"} do l[1+#l]=v end + expect (l).should_equal {"first", "second", "3rd"} + - it iterates over elements of a nested table argument: + for v in f {{"one", {"two"}, {{"three"}, "four"}}, "five"} do + l[1+#l]=v + end + expect (l).should_equal {"one", "two", "three", "four", "five"} + - it includes the hash part of a table argument: + for v in f {"first", "second"; third = "3rd"} do l[1+#l]=v end + expect (l).should_equal {"first", "second", "3rd"} + - it includes hash parts of a nested table argument: + for v in f {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} do + l[1+#l]=v + end + expect (l).should_contain.all_of {"one", 2, "three", 4, "bar", "five"} + - it works on trees too: + for v in f (M.new {M.new {"one", + M.new {two=2}, + M.new {M.new {"three"}, four=4} + }, + foo="bar", "five"}) + do + l[1+#l]=v + end + expect (l).should_contain.all_of {"one", 2, "three", 4, "bar", "five"} + - it diagnoses non-table arguments: + expect (f ()).should_error ("table expected") + expect (f "string").should_error ("table expected") + + +- describe merge: + - before: | + f = M.merge + + -- Additional merge keys which are moderately unusual + t1 = { k1 = {"v1"}, k2 = "if", k3 = {"?"} } + t2 = { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = t1.k1 } + + target = {} + for k, v in pairs (t1) do target[k] = v end + for k, v in pairs (t2) do target[k] = v end + - it does not create a whole new table: + expect (f (t1, t2)).should_be (t1) + - it does not change t1 when t2 is empty: + expect (f (t1, {})).should_be (t1) + - it copies t2 when t1 is empty: | + expect (f ({}, t1)).should_not_be (t1) + pending "see issue #40" + expect (f ({}, t1)).should_equal (t1) + - it merges keys from t2 into t1: | + pending "see issue #40" + expect (f (t1, t2)).should_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).should_be (t2.k3) + expect (m.k3).should_not_be (original.k3) + - it diagnoses non-table arguments: + expect (f (nil, {})).should_error ("table expected") + expect (f ({}, nil)).should_error ("table expected") + + +- describe nodes: + - before: + f = M.nodes + + function traverse (subject) + l = {} + for ty, p, n in f (subject) do l[1+#l]={ty, p, n} end + return l + end + - it iterates over the elements of a table argument: | + pending "see issue #40" + subject = {"first", "second", "3rd"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {3}, subject[3]}, -- 3rd, + {"join", {}, subject}} -- } + - it iterates over the elements of nested a table argument: | + pending "see issue #40" + subject = {{"one", {"two"}, {{"three"}, "four"}}, "five"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,1}, subject[1][2][1]}, -- two, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,2}, subject[1][3][2]}, -- four, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"join", {}, subject}} -- } + - it includes the hash part of a table argument: | + pending "see issue #40" + subject = {"first", "second"; third = "3rd"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {3}, subject[3]}, -- 3rd + {"join", {}, subject}} -- } + - it includes hash parts of a nested table argument: | + pending "see issue #40" + subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"branch", {1,2,1}, subject[1][2][1]}, -- 2, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,2}, subject[1][3][2]}, -- 4, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- bar, + {"leaf", {3}, subject[3]}, -- five, + {"join", {}, subject}} -- } + - it works on trees too: | + pending "see issue #40" + subject = M.new {M.new {"one", + M.new {two=2}, + M.new {M.new {"three"}, four=4}}, + foo="bar", + "five"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[3]}, -- five, + {"join", {}, subject}} -- } + - it diagnoses non-table arguments: + expect (f ()).should_error ("table expected") + expect (f "string").should_error ("table expected") From 4a95f47f7323ed78265fab8457a7b0a58531fb3e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Nov 2013 14:06:35 +1300 Subject: [PATCH 021/703] doc: update doc-comments for latest LDoc master. * doc/config.ld (metamethod): Delete. Metamethods are now separated out automatically. * ext/std/list.lua: Describe use of object methods as module functions. Update doc-comments. * ext/std/object.lua, ext/std/set.lua, ext/std/strbuf.lua, ext/std/strict.lua, ext/std/tree.lua: Update doc-comments. Signed-off-by: Gary V. Vaughan --- doc/config.ld | 1 - ext/std/list.lua | 160 +++++++++++++++++++++++---------------------- ext/std/object.lua | 8 +-- ext/std/set.lua | 151 +++++++++++++++++++++--------------------- ext/std/strbuf.lua | 27 ++++---- ext/std/strict.lua | 4 +- ext/std/tree.lua | 22 +++---- 7 files changed, 191 insertions(+), 182 deletions(-) diff --git a/doc/config.ld b/doc/config.ld index 9e9b318..282fc6a 100644 --- a/doc/config.ld +++ b/doc/config.ld @@ -3,7 +3,6 @@ project = "stdlib" description = "Standard Lua Libraries" dir = "." -new_type("metamethod", "Metamethods", false, "params") file = { -- Modules "../ext/std.lua", diff --git a/ext/std/list.lua b/ext/std/list.lua index ad80d39..ee6d21d 100644 --- a/ext/std/list.lua +++ b/ext/std/list.lua @@ -4,6 +4,24 @@ Every list is also an object, and thus inherits all of the `std.object` methods, particularly use of object cloning for making new list objects. + In addition to calling methods on list objects in OO style... + + local List = require "std.list" + local l = List {1, 2, 3} + for e in l:relems () do print (e) end + => 3 + => 2 + => 1 + + ...they can also be called as module functions with an explicit argument: + + local List = require "std.list" + local l = List {1, 2, 3} + for e in List.relems (l) do print (e) end + => 3 + => 2 + => 1 + @classmod std.list ]] @@ -13,19 +31,19 @@ local Object = require "std.object" --- Compare two lists element-by-element, from left-to-right. +-- +-- if a_list:compare (another_list) == 0 then print "same" end -- @function compare --- @tparam table l a list --- @tparam table m another list --- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 --- if `l` is greater than `m` +-- @tparam table l another list +-- @return -1 if `self` is less than `l`, 0 if they are the same, and 1 +-- if `self` is greater than `l` local compare = base.compare --- An iterator over the elements of a list. -- @function elems --- @tparam table l a list -- @treturn function iterator function which returns successive elements of `self` --- @treturn table `l` +-- @treturn table *list* -- @return `true` local elems = base.elems @@ -34,46 +52,43 @@ local List -- list prototype object forward declaration --- Append an item to a list. --- @tparam table l a list -- @param x item --- @treturn std.list new list containing `{l[1], ..., l[#l], x}` -local function append (l, x) - return List (base.append (l, x)) +-- @treturn std.list new list containing `{self[1], ..., self[#self], x}` +local function append (self, x) + return List (base.append (self, x)) end --- Concatenate arguments into a list. -- @param ... tuple of lists -- @treturn std.list new list containing --- `{l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` -local function concat (...) - return List (base.concat (...)) +-- `{self[1], ..., self[#self], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` +local function concat (self, ...) + return List (base.concat (self, ...)) end --- An iterator over the elements of a list, in reverse. --- @tparam table l a list --- @treturn function iterator function which returns precessive elements of the list --- @treturn std.list `l` +-- @treturn function iterator function which returns precessive elements of the `self` +-- @treturn std.list `self` -- @return `true` -local function relems (l) - local n = #l + 1 - return function (l) +local function relems (self) + local n = #self + 1 + return function (self) n = n - 1 if n > 0 then - return l[n] + return self[n] end end, - l, true + self, true end --- Map a function over a list. --- @tparam table l a list -- @tparam function f map function --- @treturn std.list new list containing `{f (list[1]), ..., f (list[#list])}` -local function map (l, f) - return List (func.map (f, elems, l)) +-- @treturn std.list new list containing `{f (self[1]), ..., f (self[#self])}` +local function map (self, f) + return List (func.map (f, elems, self)) end @@ -87,24 +102,22 @@ end --- Filter a list according to a predicate. --- @tparam table l a list -- @tparam function p predicate function, of one argument returning a boolean --- @treturn std.list new list containing elements `e` of `l` for which `p (e)` is true -local function filter (l, p) - return List (func.filter (p, elems, l)) +-- @treturn std.list new list containing elements `e` of `self` for which `p (e)` is true +local function filter (self, p) + return List (func.filter (p, elems, self)) end --- Return a sub-range of a list. -- (The equivalent of `string.sub` on strings; negative list indices -- count from the end of the list.) --- @tparam table l a list -- @tparam number from start of range (default: 1) --- @tparam number to end of range (default: `#list`) --- @treturn std.list new list containing `{l[from], ..., l[to]}` -local function sub (l, from, to) +-- @tparam number to end of range (default: `#self`) +-- @treturn std.list new list containing `{self[from], ..., self[to]}` +local function sub (self, from, to) local r = List {} - local len = #l + local len = #self from = from or 1 to = to or len if from < 0 then @@ -114,70 +127,64 @@ local function sub (l, from, to) to = to + len + 1 end for i = from, to do - table.insert (r, l[i]) + table.insert (r, self[i]) end return r end --- Return a list with its first element removed. --- @tparam table l a list --- @treturn std.list new list containing `{l[2], ..., l[#l]}` -local function tail (l) - return sub (l, 2) +-- @treturn std.list new list containing `{self[2], ..., self[#self]}` +local function tail (self) + return sub (self, 2) end --- Fold a binary function through a list left associatively. --- @tparam table l a list -- @tparam function f binary function -- @param e element to place in left-most position -- @return result -local function foldl (l, f, e) - return func.fold (f, e, elems, l) +local function foldl (self, f, e) + return func.fold (f, e, elems, self) end --- Fold a binary function through a list right associatively. --- @tparam table l a list -- @tparam function f binary function -- @param e element to place in right-most position -- @return result -local function foldr (l, f, e) +local function foldr (self, f, e) return List (func.fold (function (x, y) return f (y, x) end, - e, relems, l)) + e, relems, self)) end --- Prepend an item to a list. --- @tparam table l a list -- @param x item --- @treturn std.list new list containing `{x, unpack (l)}` -local function cons (l, x) - return List {x, unpack (l)} +-- @treturn std.list new list containing `{x, unpack (self)}` +local function cons (self, x) + return List {x, unpack (self)} end --- Repeat a list. --- @tparam table l a list -- @tparam number n number of times to repeat --- @treturn std.list `n` copies of `l` appended together -local function rep (l, n) +-- @treturn std.list `n` copies of `self` appended together +local function rep (self, n) local r = List {} for i = 1, n do - r = concat (r, l) + r = concat (r, self) end return r end --- Reverse a list. --- @tparam table l a list --- @treturn std.list new list containing `{l[#l], ..., l[1]}` -local function reverse (l) +-- @treturn std.list new list containing `{self[#self], ..., self[1]}` +local function reverse (self) local r = List {} - for i = #l, 1, -1 do - table.insert (r, l[i]) + for i = #self, 1, -1 do + table.insert (r, self[i]) end return r end @@ -214,11 +221,10 @@ end --- Project a list of fields from a list of tables. --- @tparam table l a list -- @param f field to project -- @treturn std.list list of `f` fields -local function project (l, f) - return map (l, function (t) return t[f] end) +local function project (self, f) + return map (self, function (t) return t[f] end) end @@ -251,11 +257,10 @@ end --- Flatten a list. --- @tparam table l a list --- @treturn std.list flattened list -local function flatten (l) +-- @treturn std.list flattened list +local function flatten (self) local r = List {} - for v in base.ileaves (l) do + for v in base.ileaves (self) do table.insert (r, v) end return r @@ -278,11 +283,10 @@ end -- -- @todo Use ileaves instead of flatten (needs a while instead of a -- for in fill function) --- @tparam table l a list -- @tparam table s `{d1, ..., dn}` -- @return reshaped list -local function shape (l, s) - l = flatten (l) +local function shape (self, s) + self = flatten (self) -- Check the shape and calculate the size of the zero, if any local size = 1 local zero @@ -298,11 +302,11 @@ local function shape (l, s) end end if zero then - s[zero] = math.ceil (#l / size) + s[zero] = math.ceil (#self / size) end local function fill (i, d) if d > #s then - return l[i], i + 1 + return self[i], i + 1 else local r = List {} for j = 1, s[d] do @@ -385,34 +389,36 @@ List = Object { ------ -- Concatenate lists. -- new = list .. table - -- @metamethod __concat + -- @function __concat + -- @tparam std.list list a list + -- @tparam table table another list, hash part is ignored -- @see concat __concat = concat, ------ - -- Append to list. + -- Append element to list. -- list = list + element - -- @metamethod __add + -- @function __add + -- @tparam std.list list a list + -- @param element element to append -- @see append __add = append, ------ -- List order operator. -- max = list1 > list2 and list1 or list2 - -- @metamethod __lt -- @tparam std.list list1 a list -- @tparam std.list list2 another list -- @see std.list:compare - __lt = function (l, m) return compare (l, m) < 0 end, + __lt = function (list1, list2) return compare (list1, list2) < 0 end, ------ -- List equality or order operator. -- min = list1 <= list2 and list1 or list2 - -- @metamethod __le -- @tparam std.list list1 a list -- @tparam std.list list2 another list -- @see std.list:compare - __le = function (l, m) return compare (l, m) <= 0 end, + __le = function (list1, list2) return compare (list1, list2) <= 0 end, __index = base.merge (metamethods, { -- camelCase compatibility. diff --git a/ext/std/object.lua b/ext/std/object.lua index 7153edc..c759932 100644 --- a/ext/std/object.lua +++ b/ext/std/object.lua @@ -209,7 +209,7 @@ end -- -- This can't and shouldn't be set directly, because the Object class -- manages it transparently during cloning. --- @table __metatable +-- @table _metatable -- @tfield string _type derived objects can override this for objects -- intended to be a prototype for further specialised objects. -- @tfield table|function _init Derived objects can override this to be @@ -226,13 +226,13 @@ local metatable = { -- This pseudo-metamethod is used during object cloning to make the -- intial new object table, and can be overridden in other objects -- for greater control of which fields are considered non-private. - -- @metamethod __totable + -- @function __totable -- @see totable __totable = totable, ------ -- Return a string representation of *object*. - -- @metamethod __tostring + -- @function __tostring -- @see tostring __tostring = stringify, @@ -242,7 +242,7 @@ local metatable = { -- unless some new fields for the cloned object begin with '_', in which -- case they are merged into a copy of the prototype metatable to form -- a new metatable for the cloned object (and its clones). - -- @metamethod __call + -- @function __call -- @param ... arguments for `_init` -- @treturn std.object a clone of the called object. __call = function (self, ...) diff --git a/ext/std/set.lua b/ext/std/set.lua index 457acc3..77d4a5b 100644 --- a/ext/std/set.lua +++ b/ext/std/set.lua @@ -13,36 +13,33 @@ local Set -- forward declaration -- whose values are true. --- Say whether an element is in a set. --- @param s set -- @param e element -- @return `true` if e is in set, `false` -- otherwise -local function member (s, e) - return rawget (s, e) == true +local function member (self, e) + return rawget (self, e) == true end --- Insert an element into a set. --- @param s set -- @param e element -- @return the modified set -local function insert (s, e) - rawset (s, e, true) - return s +local function insert (self, e) + rawset (self, e, true) + return self end --- Delete an element from a set. --- @param s set -- @param e element -- @return the modified set -local function delete (s, e) - rawset (s, e, nil) - return s +local function delete (self, e) + rawset (self, e, nil) + return self end --- Iterator for sets. -- @todo Make the iterator return only the key -local function elems (s) - return pairs (s) +local function elems (self) + return pairs (self) end @@ -52,77 +49,72 @@ local difference, symmetric_difference, intersection, union, subset, equal --- Find the difference of two sets. -- @param s set --- @param t set --- @return s with elements of t removed -function difference (s, t) - if Object.type (t) == "table" then - t = Set (t) +-- @return `self` with elements of s removed +function difference (self, s) + if Object.type (s) == "table" then + s = Set (s) end - local r = Set {} - for e in elems (s) do - if not member (t, e) then - insert (r, e) + local t = Set {} + for e in elems (self) do + if not member (s, e) then + insert (t, e) end end - return r + return t end --- Find the symmetric difference of two sets. -- @param s set --- @param t set --- @return elements of s and t that are in s or t but not both -function symmetric_difference (s, t) - if Object.type (t) == "table" then - t = Set (t) +-- @return elements of `self` and `s` that are in `self` or `s` but not both +function symmetric_difference (self, s) + if Object.type (s) == "table" then + s = Set (s) end - return difference (union (s, t), intersection (t, s)) + return difference (union (self, s), intersection (s, self)) end --- Find the intersection of two sets. -- @param s set --- @param t set --- @return set intersection of s and t -function intersection (s, t) - if Object.type (t) == "table" then - t = Set (t) +-- @return set intersection of `self` and `s` +function intersection (self, s) + if Object.type (s) == "table" then + s = Set (s) end - local r = Set {} - for e in elems (s) do - if member (t, e) then - insert (r, e) + local t = Set {} + for e in elems (self) do + if member (s, e) then + insert (t, e) end end - return r + return t end --- Find the union of two sets. --- @param s set --- @param t set or set-like table --- @return set union of s and t -function union (s, t) - if Object.type (t) == "table" then - t = Set (t) +-- @param s set or set-like table +-- @return set union of `self` and `s` +function union (self, s) + if Object.type (s) == "table" then + s = Set (s) end - local r = Set {} - for e in elems (s) do - insert (r, e) + local t = Set {} + for e in elems (self) do + insert (t, e) end - for e in elems (t) do - insert (r, e) + for e in elems (s) do + insert (t, e) end - return r + return t end --- Find whether one set is a subset of another. -- @param s set --- @param t set --- @return `true` if s is a subset of t, `false` otherwise -function subset (s, t) - if Object.type (t) == "table" then - t = Set (t) +-- @return `true` if `self` is a subset of `s`, `false` otherwise +function subset (self, s) + if Object.type (s) == "table" then + s = Set (s) end - for e in elems (s) do - if not member (t, e) then + for e in elems (self) do + if not member (s, e) then return false end end @@ -131,21 +123,19 @@ end --- Find whether one set is a proper subset of another. -- @param s set --- @param t set --- @return `true` if s is a proper subset of t, false otherwise -function propersubset (s, t) - if Object.type (t) == "table" then - t = Set (t) +-- @return `true` if `self` is a proper subset of `s`, `false` otherwise +function propersubset (self, s) + if Object.type (s) == "table" then + t = Set (s) end - return subset (s, t) and not subset (t, s) + return subset (self, s) and not subset (s, self) end --- Find whether two sets are equal. -- @param s set --- @param t set --- @return `true` if sets are equal, `false` otherwise -function equal (s, t) - return subset (s, t) and subset (t, s) +-- @return `true` if `self` and `s` are equal, `false` otherwise +function equal (self, s) + return subset (self, s) and subset (s, self) end @@ -165,49 +155,60 @@ Set = Object { ------ -- Union operator. -- set + table = union - -- @metamethod __add + -- @function __add + -- @param set set + -- @param table set or set-like table -- @see union __add = union, ------ -- Difference operator. -- set - table = set difference - -- @metamethod __sub + -- @function __sub + -- @param set set + -- @param table set or set-like table -- @see difference __sub = difference, ------ -- Intersection operator. -- set * table = intersection - -- @metamethod __mul + -- @function __mul + -- @param set set + -- @param table set or set-like table -- @see intersection __mul = intersection, ------ -- Symmetric difference operator. -- set / table = symmetric difference - -- @metamethod __div + -- @function __div + -- @param set set + -- @param table set or set-like table -- @see symmetric_difference __div = symmetric_difference, ------ -- Subset operator. -- set <= table = subset - -- @metamethod __le + -- @function __le + -- @param set set + -- @param table set or set-like table -- @see subset __le = subset, ------ -- Proper subset operator. -- set < table = proper subset - -- @metamethod __lt + -- @function __lt + -- @param set set + -- @param table set or set-like table -- @see propersubset __lt = propersubset, ------ -- Object to table conversion. -- table = set:totable () - -- @metamethod __totable __totable = function (self) local t = {} for e in elems (self) do diff --git a/ext/std/strbuf.lua b/ext/std/strbuf.lua index 2903ac0..77ed1bb 100644 --- a/ext/std/strbuf.lua +++ b/ext/std/strbuf.lua @@ -8,20 +8,18 @@ local Object = require "std.object" --- Add a string to a buffer. --- @param b buffer --- @param s string to add --- @return buffer -local function concat (b, s) - table.insert (b, s) - return b +-- @tparam string s string to add +-- @treturn std.strbuf modified buffer +local function concat (self, s) + table.insert (self, s) + return self end --- Convert a buffer to a string. --- @param b buffer --- @return string -local function tostring (b) - return table.concat (b) +-- @treturn string stringified `self` +local function tostring (self) + return table.concat (self) end @@ -32,7 +30,10 @@ return Object { ------ -- Support concatenation of StrBuf objects. -- buffer = buffer .. str - -- @metamethod __concat + -- @function __concat + -- @tparam std.strbuf buffer StrBuf object + -- @tparam string str a string or string-like object + -- @treturn std.strbuf modified `buffer` -- @see concat __concat = concat, @@ -40,7 +41,9 @@ return Object { ------ -- Support fast conversion to Lua string. -- str = tostring (buffer) - -- @metamethod __tostring + -- @function __tostring + -- @tparam std.strbuf buffer Strbuf object + -- @treturn string concatenation of buffer contents -- @see tostring __tostring = tostring, diff --git a/ext/std/strict.lua b/ext/std/strict.lua index 46ac782..5c06551 100644 --- a/ext/std/strict.lua +++ b/ext/std/strict.lua @@ -25,7 +25,7 @@ local function what () end --- Detect assignment to undeclared global. --- @metamethod __newindex +-- @function __newindex mt.__newindex = function (t, n, v) if not mt.__declared[n] then local w = what () @@ -38,7 +38,7 @@ mt.__newindex = function (t, n, v) end --- Detect derefrence of undeclared global. --- @metamethod __index +-- @function __index mt.__index = function (t, n) if not mt.__declared[n] and what () ~= "C" then error ("variable '" .. n .. "' is not declared", 2) diff --git a/ext/std/tree.lua b/ext/std/tree.lua index 9af4113..2bbd2f8 100644 --- a/ext/std/tree.lua +++ b/ext/std/tree.lua @@ -35,16 +35,16 @@ end --- Tree `__index` metamethod. --- @metamethod __index +-- @function __index -- @param i non-table, or list of keys `{i\_1 ... i\_n}` -- @return `tr[i]...[i\_n]` if i is a table, or `tr[i]` otherwise -- @todo the following doesn't treat list keys correctly -- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? -function metatable.__index (tr, i) +function metatable.__index (self, i) if type (i) == "table" and #i > 0 then - return list.foldl (i, func.op["[]"], tr) + return list.foldl (i, func.op["[]"], self) else - return rawget (tr, i) + return rawget (self, i) end end @@ -52,20 +52,20 @@ end --- Tree `__newindex` metamethod. -- -- Sets `tr[i\_1]...[i\_n] = v` if i is a table, or `tr[i] = v` otherwise --- @metamethod __newindex +-- @function __newindex -- @param i non-table, or list of keys `{i\_1 ... i\_n}` -- @param v value -function metatable.__newindex (tr, i, v) +function metatable.__newindex (self, i, v) if type (i) == "table" then for n = 1, #i - 1 do - if getmetatable (tr[i[n]]) ~= metatable then - rawset (tr, i[n], new ()) + if getmetatable (self[i[n]]) ~= metatable then + rawset (self, i[n], new ()) end - tr = tr[i[n]] + self = self[i[n]] end - rawset (tr, i[#i], v) + rawset (self, i[#i], v) else - rawset (tr, i, v) + rawset (self, i, v) end end From 8a283929d6c3d5b857998d2a00d2a5478ea401e3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Nov 2013 17:41:22 +1300 Subject: [PATCH 022/703] tree: fix broken tests, and improve doc-comments (Fixes #40) * specs/tree_spec.yaml (inodes, nodes): Be sure to clone iterator returned paths before storing. Some corrections to the expected results. Remove pending "see issue #40" instances. (merge): Fix tests. Remove pending "see issue #40" instances. * ext/std/tree.lua (nodes): Much improved doc-comment. (merge): Assert the arguments are actual tree nodes, because raw tables do not work! Signed-off-by: Gary V. Vaughan --- ext/std/tree.lua | 44 ++++++++++++-- specs/tree_spec.yaml | 139 +++++++++++++++++++++---------------------- 2 files changed, 107 insertions(+), 76 deletions(-) diff --git a/ext/std/tree.lua b/ext/std/tree.lua index 2bbd2f8..d9792f5 100644 --- a/ext/std/tree.lua +++ b/ext/std/tree.lua @@ -132,9 +132,41 @@ end --- Tree iterator over all nodes. +-- +-- The returned iterator function performs a depth-first traversal of +-- `tr`, and at each node it returns `{node-type, tree-path, tree-node}` +-- where `node-type` is `branch`, `join` or `leaf`; `tree-path` is a +-- list of keys used to reach this node, and `tree-node` is the current +-- node. +-- +-- Given a `std.tree` to represent: +-- +-- + root +-- +-- node1 +-- | +-- leaf1 +-- | '-- leaf2 +-- '-- leaf 3 +-- +-- tree = std.tree { std.tree { "leaf1", "leaf2"}, "leaf3" } +-- +-- A series of calls to `tree.nodes` will return: +-- +-- "branch", {}, {{"leaf1", "leaf2"}, "leaf3"} +-- "branch", {1}, {"leaf1", "leaf"2") +-- "leaf", {1,1}, "leaf1" +-- "leaf", {1,2}, "leaf2" +-- "join", {1}, {"leaf1", "leaf2"} +-- "leaf", {2}, "leaf3" +-- "join", {}, {{"leaf1", "leaf2"}, "leaf3"} +-- +-- Note that the `tree-path` reuses the same table on each iteration, so +-- you must `table.clone` a copy if you want to take a snap-shot of the +-- current state of the `tree-path` list before the next iteration +-- changes it. -- @tparam std.tree tr tree to iterate over -- @treturn function iterator function -- @treturn std.tree the tree, `tr` +-- @see inodes local function nodes (tr) assert (type (tr) == "table", "bad argument #1 to 'nodes' (table expected, got " .. type (tr) .. ")") @@ -143,9 +175,13 @@ end --- Tree iterator over numbered nodes, in order. +-- +-- The iterator function behaves like @{nodes}, but only traverses the +-- array part of the nodes of `tr`, ignoring any others. -- @tparam std.tree tr tree to iterate over -- @treturn function iterator function -- @treturn std.tree the tree, `t` +-- @see nodes local function inodes (tr) assert (type (tr) == "table", "bad argument #1 to 'inodes' (table expected, got " .. type (tr) .. ")") @@ -159,10 +195,10 @@ end -- @treturn std.tree `t` with nodes from `u` merged in -- @see std.table.merge local function merge (t, u) - assert (type (t) == "table", - "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") - assert (type (u) == "table", - "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") + assert (getmetatable (t) == metatable, + "bad argument #1 to 'merge' (tree table expected, got " .. type (t) .. ")") + assert (getmetatable (u) == metatable, + "bad argument #2 to 'merge' (tree table expected, got " .. type (u) .. ")") for ty, p, n in nodes (u) do if ty == "leaf" then t[p] = n diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index abeaf07..52f7c1b 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -118,23 +118,24 @@ specify tree: - describe inodes: - before: | f = M.inodes + local tostring = (require "std.string").tostring function traverse (subject) l = {} - for ty, p, n in f (subject) do l[1+#l]={ty, p, n} end + for ty, p, n in f (subject) do + l[1+#l]={ty, M.clone (p), n} + end return l end - it iterates over array part of a table argument: | - pending "see issue #40" subject = {"first", "second", "3rd"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {3}, subject[3]}, -- 3rd, - {"join", {}, subject}} -- } + should_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {3}, subject[3]}, -- 3rd, + {"join", {}, subject}} -- } - it iterates over array parts of nested table argument: | - pending "see issue #40" subject = {{"one", {"two"}, {{"three"}, "four"}}, "five"} expect (traverse (subject)). should_equal {{"branch", {}, subject}, -- { @@ -153,15 +154,13 @@ specify tree: {"leaf", {2}, subject[2]}, -- five, {"join", {}, subject}} -- } - it skips hash part of a table argument: | - pending "see issue #40" subject = {"first", "second"; third = "3rd"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"join", {}, subject}} -- } + should_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"join", {}, subject}} -- } - it skips hash parts of nested table argument: | - pending "see issue #40" subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} expect (traverse (subject)). should_equal {{"branch", {}, subject}, -- { @@ -175,10 +174,9 @@ specify tree: {"join", {1,3,1}, subject[1][3][1]}, -- }, {"join", {1,3}, subject[1][3]}, -- }, {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[3]}, -- five, + {"leaf", {2}, subject[2]}, -- five, {"join", {}, subject}} -- } - it works on trees too: | - pending "see issue #40" subject = M.new {M.new {"one", M.new {two=2}, M.new {M.new {"three"}, four=4}}, @@ -196,7 +194,7 @@ specify tree: {"join", {1,3,1}, subject[1][3][1]}, -- }, {"join", {1,3}, subject[1][3]}, -- }, {"join", {1}, subject[1]}, -- }, - {"leaf", {3}, subject[3]}, -- five, + {"leaf", {2}, subject[2]}, -- five, {"join", {}, subject}} -- } - it diagnoses non-table arguments: expect (f ()).should_error ("table expected") @@ -243,22 +241,21 @@ specify tree: f = M.merge -- Additional merge keys which are moderately unusual - t1 = { k1 = {"v1"}, k2 = "if", k3 = {"?"} } - t2 = { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = t1.k1 } + t1 = M.new { k1 = M.new {"v1"}, k2 = "if", k3 = M.new {"?"} } + t2 = M.new { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = M.new {"v1"} } - target = {} - for k, v in pairs (t1) do target[k] = v end - for k, v in pairs (t2) do target[k] = v end + target = M.clone (t1) + for ty, p, n in M.nodes (t2) do + if ty == "leaf" then target[p] = n end + end - it does not create a whole new table: expect (f (t1, t2)).should_be (t1) - it does not change t1 when t2 is empty: - expect (f (t1, {})).should_be (t1) + expect (f (t1, M.new {})).should_be (t1) - it copies t2 when t1 is empty: | - expect (f ({}, t1)).should_not_be (t1) - pending "see issue #40" - expect (f ({}, t1)).should_equal (t1) + expect (f (M.new {}, t1)).should_not_be (t1) + expect (f (M.new {}, t1)).should_equal (t1) - it merges keys from t2 into t1: | - pending "see issue #40" expect (f (t1, t2)).should_equal (target) - it gives precedence to values from t2: original = M.clone (t1) @@ -276,20 +273,18 @@ specify tree: function traverse (subject) l = {} - for ty, p, n in f (subject) do l[1+#l]={ty, p, n} end + for ty, p, n in f (subject) do l[1+#l]={ty, M.clone (p), n} end return l end - it iterates over the elements of a table argument: | - pending "see issue #40" subject = {"first", "second", "3rd"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {3}, subject[3]}, -- 3rd, - {"join", {}, subject}} -- } + should_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {3}, subject[3]}, -- 3rd, + {"join", {}, subject}} -- } - it iterates over the elements of nested a table argument: | - pending "see issue #40" subject = {{"one", {"two"}, {{"three"}, "four"}}, "five"} expect (traverse (subject)). should_equal {{"branch", {}, subject}, -- { @@ -308,55 +303,55 @@ specify tree: {"leaf", {2}, subject[2]}, -- five, {"join", {}, subject}} -- } - it includes the hash part of a table argument: | - pending "see issue #40" subject = {"first", "second"; third = "3rd"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {3}, subject[3]}, -- 3rd - {"join", {}, subject}} -- } + should_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {"third"}, subject["third"]}, -- 3rd + {"join", {}, subject}} -- } - it includes hash parts of a nested table argument: | - pending "see issue #40" subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"branch", {1,2,1}, subject[1][2][1]}, -- 2, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,2}, subject[1][3][2]}, -- 4, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- bar, - {"leaf", {3}, subject[3]}, -- five, - {"join", {}, subject}} -- } + should_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"leaf", {"foo"}, subject["foo"]}, -- bar, + {"join", {}, subject}} -- } - it works on trees too: | - pending "see issue #40" subject = M.new {M.new {"one", M.new {two=2}, M.new {M.new {"three"}, four=4}}, foo="bar", "five"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[3]}, -- five, - {"join", {}, subject}} -- } + should_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"leaf", {"foo"}, subject["foo"]}, -- bar, + {"join", {}, subject}} -- } - it diagnoses non-table arguments: expect (f ()).should_error ("table expected") expect (f "string").should_error ("table expected") From 6b67c3f3cef194e5bb5145a515514e563bb7169d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Nov 2013 18:22:59 +1300 Subject: [PATCH 023/703] specs: don't assume in order traversal by tree.nodes. * specs/tree_spec.yaml (node): Use `should_contain.all_of` in place of `should_equal` to avoid enforcing a particular traversal order. Signed-off-by: Gary V. Vaughan --- specs/tree_spec.yaml | 89 ++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 52f7c1b..a4b7100 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -303,55 +303,64 @@ specify tree: {"leaf", {2}, subject[2]}, -- five, {"join", {}, subject}} -- } - it includes the hash part of a table argument: | + -- like `pairs`, `nodes` can visit elements in any order, so we cannot + -- guarantee the array part is always visited before the hash part, or + -- even that the array elements are visited in order! subject = {"first", "second"; third = "3rd"} - expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {"third"}, subject["third"]}, -- 3rd - {"join", {}, subject}} -- } + expect (traverse (subject)).should_contain. + all_of {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {"third"}, subject["third"]}, -- 3rd + {"join", {}, subject}} -- } - it includes hash parts of a nested table argument: | + -- like `pairs`, `nodes` can visit elements in any order, so we cannot + -- guarantee the array part is always visited before the hash part, or + -- even that the array elements are visited in order! subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} - expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"leaf", {"foo"}, subject["foo"]}, -- bar, - {"join", {}, subject}} -- } + expect (traverse (subject)).should_contain. + all_of {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"leaf", {"foo"}, subject["foo"]}, -- bar, + {"join", {}, subject}} -- } - it works on trees too: | + -- like `pairs`, `nodes` can visit elements in any order, so we cannot + -- guarantee the array part is always visited before the hash part, or + -- even that the array elements are visited in order! subject = M.new {M.new {"one", M.new {two=2}, M.new {M.new {"three"}, four=4}}, foo="bar", "five"} - expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"leaf", {"foo"}, subject["foo"]}, -- bar, - {"join", {}, subject}} -- } + expect (traverse (subject)).should_contain. + all_of {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"leaf", {"foo"}, subject["foo"]}, -- bar, + {"join", {}, subject}} -- } - it diagnoses non-table arguments: expect (f ()).should_error ("table expected") expect (f "string").should_error ("table expected") From 15aaa7c9c7f52faa178fa7274a3c48e06ec86742 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Dec 2013 17:43:06 +1300 Subject: [PATCH 024/703] specs: simplify the tree merge specs. * specs/tree_spec.yaml (tree merge): comparing non-leaf nodes is not a fair test, better to set k3 to a different string that can be tested before and after the merge without worrying about whether a table node is coerced to a tree node. Signed-off-by: Gary V. Vaughan --- specs/tree_spec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index a4b7100..4b3e664 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -241,8 +241,8 @@ specify tree: f = M.merge -- Additional merge keys which are moderately unusual - t1 = M.new { k1 = M.new {"v1"}, k2 = "if", k3 = M.new {"?"} } - t2 = M.new { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = M.new {"v1"} } + t1 = M.new { k1 = "v1", k2 = "if", k3 = M.new {"?"} } + t2 = M.new { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = "v2" } target = M.clone (t1) for ty, p, n in M.nodes (t2) do From f582a9bd5d4e476c61b9d46dea93d95eec602819 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Dec 2013 17:47:34 +1300 Subject: [PATCH 025/703] table: add string support to totable function. * ext/std/table.lua (totable): When passed a string, return a table of each character in the string. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ ext/std/table.lua | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/NEWS b/NEWS index 4124913..1f0e899 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,9 @@ Stdlib NEWS - User visible changes - `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. diff --git a/ext/std/table.lua b/ext/std/table.lua index 1c6699d..f4261b5 100644 --- a/ext/std/table.lua +++ b/ext/std/table.lua @@ -135,6 +135,10 @@ local function totable (x) return m (x) elseif type (x) == "table" then return x + elseif type (x) == "string" then + local t = {} + x:gsub (".", function (c) t[#t + 1] = c end) + return t else return nil end From 3fdb78521e86ad3b4b433dc07babf35f6b00fd4e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Dec 2013 22:11:04 +1300 Subject: [PATCH 026/703] Revert "maint: move lua extension sources to $top_srcdir/ext." This reverts commit 4d51ca63a1eedd3759201db8b3df850a658856e6. I have no idea why I thought moving Lua libraries from the lib tree to the Lua C extensions ext tree seemed like a good idea. Undo the damage. * ext/: Rename back to lib/. * local.mk (std_path, dist_lua_DATA, dist_luastd_DATA) (mkrockspecs_args, EXTRA_DIST, dist_files_DATA) (dist_modules_DATA): Adjust. * doc/config.ld (file): Adjust. * .gitignore: Update. Signed-off-by: Gary V. Vaughan --- .gitignore | 2 +- doc/config.ld | 30 ++++++++++---------- {ext => lib}/std.lua.in | 0 {ext => lib}/std/base.lua | 0 {ext => lib}/std/debug.lua | 0 {ext => lib}/std/debug_init.lua | 0 {ext => lib}/std/functional.lua | 0 {ext => lib}/std/getopt.lua | 0 {ext => lib}/std/io.lua | 0 {ext => lib}/std/list.lua | 0 {ext => lib}/std/math.lua | 0 {ext => lib}/std/modules.lua | 0 {ext => lib}/std/object.lua | 0 {ext => lib}/std/package.lua | 0 {ext => lib}/std/set.lua | 0 {ext => lib}/std/strbuf.lua | 0 {ext => lib}/std/strict.lua | 0 {ext => lib}/std/string.lua | 0 {ext => lib}/std/table.lua | 0 {ext => lib}/std/tree.lua | 0 local.mk | 50 ++++++++++++++++----------------- 21 files changed, 41 insertions(+), 41 deletions(-) rename {ext => lib}/std.lua.in (100%) rename {ext => lib}/std/base.lua (100%) rename {ext => lib}/std/debug.lua (100%) rename {ext => lib}/std/debug_init.lua (100%) rename {ext => lib}/std/functional.lua (100%) rename {ext => lib}/std/getopt.lua (100%) rename {ext => lib}/std/io.lua (100%) rename {ext => lib}/std/list.lua (100%) rename {ext => lib}/std/math.lua (100%) rename {ext => lib}/std/modules.lua (100%) rename {ext => lib}/std/object.lua (100%) rename {ext => lib}/std/package.lua (100%) rename {ext => lib}/std/set.lua (100%) rename {ext => lib}/std/strbuf.lua (100%) rename {ext => lib}/std/strict.lua (100%) rename {ext => lib}/std/string.lua (100%) rename {ext => lib}/std/table.lua (100%) rename {ext => lib}/std/tree.lua (100%) diff --git a/.gitignore b/.gitignore index 60ec914..a31876f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ /doc/index.html /doc/ldoc.css /doc/modules -/ext/std.lua +/lib/std.lua /stdlib-*.tar.gz /luarocks /m4/ax_compare_version.m4 diff --git a/doc/config.ld b/doc/config.ld index 282fc6a..64eff0a 100644 --- a/doc/config.ld +++ b/doc/config.ld @@ -5,23 +5,23 @@ dir = "." file = { -- Modules - "../ext/std.lua", - "../ext/std/debug.lua", - "../ext/std/functional.lua", - "../ext/std/getopt.lua", - "../ext/std/io.lua", - "../ext/std/math.lua", - "../ext/std/package.lua", - "../ext/std/strict.lua", - "../ext/std/string.lua", - "../ext/std/table.lua", - "../ext/std/tree.lua", + "../lib/std.lua", + "../lib/std/debug.lua", + "../lib/std/functional.lua", + "../lib/std/getopt.lua", + "../lib/std/io.lua", + "../lib/std/math.lua", + "../lib/std/package.lua", + "../lib/std/strict.lua", + "../lib/std/string.lua", + "../lib/std/table.lua", + "../lib/std/tree.lua", -- Classes - "../ext/std/object.lua", - "../ext/std/list.lua", - "../ext/std/set.lua", - "../ext/std/strbuf.lua", + "../lib/std/object.lua", + "../lib/std/list.lua", + "../lib/std/set.lua", + "../lib/std/strbuf.lua", } format = "markdown" diff --git a/ext/std.lua.in b/lib/std.lua.in similarity index 100% rename from ext/std.lua.in rename to lib/std.lua.in diff --git a/ext/std/base.lua b/lib/std/base.lua similarity index 100% rename from ext/std/base.lua rename to lib/std/base.lua diff --git a/ext/std/debug.lua b/lib/std/debug.lua similarity index 100% rename from ext/std/debug.lua rename to lib/std/debug.lua diff --git a/ext/std/debug_init.lua b/lib/std/debug_init.lua similarity index 100% rename from ext/std/debug_init.lua rename to lib/std/debug_init.lua diff --git a/ext/std/functional.lua b/lib/std/functional.lua similarity index 100% rename from ext/std/functional.lua rename to lib/std/functional.lua diff --git a/ext/std/getopt.lua b/lib/std/getopt.lua similarity index 100% rename from ext/std/getopt.lua rename to lib/std/getopt.lua diff --git a/ext/std/io.lua b/lib/std/io.lua similarity index 100% rename from ext/std/io.lua rename to lib/std/io.lua diff --git a/ext/std/list.lua b/lib/std/list.lua similarity index 100% rename from ext/std/list.lua rename to lib/std/list.lua diff --git a/ext/std/math.lua b/lib/std/math.lua similarity index 100% rename from ext/std/math.lua rename to lib/std/math.lua diff --git a/ext/std/modules.lua b/lib/std/modules.lua similarity index 100% rename from ext/std/modules.lua rename to lib/std/modules.lua diff --git a/ext/std/object.lua b/lib/std/object.lua similarity index 100% rename from ext/std/object.lua rename to lib/std/object.lua diff --git a/ext/std/package.lua b/lib/std/package.lua similarity index 100% rename from ext/std/package.lua rename to lib/std/package.lua diff --git a/ext/std/set.lua b/lib/std/set.lua similarity index 100% rename from ext/std/set.lua rename to lib/std/set.lua diff --git a/ext/std/strbuf.lua b/lib/std/strbuf.lua similarity index 100% rename from ext/std/strbuf.lua rename to lib/std/strbuf.lua diff --git a/ext/std/strict.lua b/lib/std/strict.lua similarity index 100% rename from ext/std/strict.lua rename to lib/std/strict.lua diff --git a/ext/std/string.lua b/lib/std/string.lua similarity index 100% rename from ext/std/string.lua rename to lib/std/string.lua diff --git a/ext/std/table.lua b/lib/std/table.lua similarity index 100% rename from ext/std/table.lua rename to lib/std/table.lua diff --git a/ext/std/tree.lua b/lib/std/tree.lua similarity index 100% rename from ext/std/tree.lua rename to lib/std/tree.lua diff --git a/local.mk b/local.mk index 1a95b79..373124d 100644 --- a/local.mk +++ b/local.mk @@ -4,7 +4,7 @@ ## Environment. ## ## ------------ ## -std_path = $(abs_srcdir)/ext/?.lua +std_path = $(abs_srcdir)/lib/?.lua LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" @@ -34,41 +34,41 @@ include specs/specs.mk ## ------ ## dist_lua_DATA += \ - ext/std.lua \ + lib/std.lua \ $(NOTHING_ELSE) luastddir = $(luadir)/std dist_luastd_DATA = \ - ext/std/base.lua \ - ext/std/debug.lua \ - ext/std/debug_init.lua \ - ext/std/functional.lua \ - ext/std/getopt.lua \ - ext/std/io.lua \ - ext/std/list.lua \ - ext/std/math.lua \ - ext/std/modules.lua \ - ext/std/object.lua \ - ext/std/package.lua \ - ext/std/set.lua \ - ext/std/strbuf.lua \ - ext/std/strict.lua \ - ext/std/string.lua \ - ext/std/table.lua \ - ext/std/tree.lua \ + lib/std/base.lua \ + lib/std/debug.lua \ + lib/std/debug_init.lua \ + lib/std/functional.lua \ + lib/std/getopt.lua \ + lib/std/io.lua \ + lib/std/list.lua \ + lib/std/math.lua \ + lib/std/modules.lua \ + lib/std/object.lua \ + lib/std/package.lua \ + lib/std/set.lua \ + lib/std/strbuf.lua \ + lib/std/strict.lua \ + lib/std/string.lua \ + lib/std/table.lua \ + lib/std/tree.lua \ $(NOTHING_ELSE) # In order to avoid regenerating std.lua at configure time, which # causes the documentation to be rebuilt and hence requires users to # have ldoc installed, put std/std.lua in as a Makefile dependency. # (Strictly speaking, distributing an AC_CONFIG_FILE would be wrong.) -ext/std.lua: ext/std.lua.in +lib/std.lua: lib/std.lua.in ./config.status --file=$@ -## Use a builtin rockspec build with root at $(srcdir)/ext -mkrockspecs_args = --module-dir $(srcdir)/ext +## Use a builtin rockspec build with root at $(srcdir)/lib +mkrockspecs_args = --module-dir $(srcdir)/lib ## ------------- ## @@ -77,7 +77,7 @@ mkrockspecs_args = --module-dir $(srcdir)/ext EXTRA_DIST += \ doc/config.ld \ - ext/std.lua.in \ + lib/std.lua.in \ $(NOTHING_ELSE) @@ -89,8 +89,8 @@ dist_doc_DATA += \ $(srcdir)/doc/index.html \ $(srcdir)/doc/ldoc.css -dist_files_DATA += $(wildcard $(srcdir)/ext/files/*.html) -dist_modules_DATA += $(wildcard $(srcdir)/ext/modules/*.html) +dist_files_DATA += $(wildcard $(srcdir)/lib/files/*.html) +dist_modules_DATA += $(wildcard $(srcdir)/lib/modules/*.html) $(dist_doc_DATA): $(dist_lua_DATA) $(dist_luastd_DATA) cd $(srcdir) && $(LDOC) -c doc/config.ld . From 4699ca1348297cc4375d88a4c0add3392caa1dc9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 10 Dec 2013 17:46:55 +1300 Subject: [PATCH 027/703] container: new module for objects that use [] to access contents. * specs/container_spec.yaml: Specify behaviour of Container objects. * specs/specs.mk (specl_SPECS): Add specs/container_spec.yaml. * specs/object_spec.yaml: Remove duplicate tests. * doc/config.ld (file), local.mk (dist_luastd_DATA): Add lib/std/container.lua. * lib/std/container.lua: New file. Implement Container objects. They have no methods, so that __index can be used for accessing contents instead. * lib/std/object.lua: Vastly improved doc-comments. Simplify drastically in light of Container implementation. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 27 +++- doc/config.ld | 2 +- lib/std/container.lua | 224 ++++++++++++++++++++++++++ lib/std/object.lua | 324 +++++++++++++------------------------- local.mk | 1 + specs/container_spec.yaml | 108 +++++++++++++ specs/object_spec.yaml | 93 +++++------ specs/specs.mk | 1 + 8 files changed, 502 insertions(+), 278 deletions(-) create mode 100644 lib/std/container.lua create mode 100644 specs/container_spec.yaml diff --git a/NEWS b/NEWS index 1f0e899..4d5bd4c 100644 --- a/NEWS +++ b/NEWS @@ -9,9 +9,9 @@ Stdlib NEWS - User visible changes dependencies on other std modules into the global namespace. - Objects derived from the `std.object` prototype have a new - :type () method that returns the contents of the new - internal `_type` field. This can be overridden during cloning with, - e.g.: + :prototype () method that returns the contents of the + new internal `_type` field. This can be overridden during cloning + with, e.g.: local Object = require "std.object" Prototype = Object { _type = "Prototype", } @@ -21,6 +21,12 @@ Stdlib NEWS - User visible changes begin with "_") when passed to `table.totable` - unless overridden in the derived object's __totable field. + - 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. + - list, set and strbuf are now derived from std.object, which means that they respond to `object.type` with appropriate type names ("set", "strbuf", etc.) and can be used as prototypes for further derived @@ -43,6 +49,19 @@ Stdlib NEWS - User visible changes - 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. + + 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, @@ -55,7 +74,7 @@ Stdlib NEWS - User visible changes These names are now stable, and will be available from here for future releases. - - The std.list module, as a consequence of returning an List object + - 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 diff --git a/doc/config.ld b/doc/config.ld index 64eff0a..fb5c297 100644 --- a/doc/config.ld +++ b/doc/config.ld @@ -18,6 +18,7 @@ file = { "../lib/std/tree.lua", -- Classes + "../lib/std/container.lua", "../lib/std/object.lua", "../lib/std/list.lua", "../lib/std/set.lua", @@ -26,4 +27,3 @@ file = { format = "markdown" sort = true - diff --git a/lib/std/container.lua b/lib/std/container.lua new file mode 100644 index 0000000..627d0cb --- /dev/null +++ b/lib/std/container.lua @@ -0,0 +1,224 @@ +--[[-- + Container object. + + A container is a @{std.object} with no methods. It's functionality is + instead defined by its *meta*methods. + + Where an Object uses the `\_\_index` metatable entry to hold object + methods, a Container stores its contents using `\_\_index`, preventing + it from having methods in there too. + + Although there are no actual methods, Containers are free to use + metamethods (`\_\_index`, `\_\_sub`, etc) and, like Objects, can supply + module functions by listing them in `\_functions`. Also, since a + @{std.container} is a @{std.object}, it can be passed to the + @{std.object} module functions, or anywhere else a @{std.object} is + expected. + + Container derived objects returned directly from a `require` statement + may also provide module functions, which can be called only from the + initial prototype object returned by `require`, but are **not** passed + on to derived objects during cloning: + + > Container = require "std.container" + > x = Container {} + > = Container.prototype (x) + Object + > = x.prototype (o) + stdin:1: attempt to call field 'prototype' (a nil value) + ... + + To add functions like this to your own prototype objects, pass a table + whose keys are the names of the module functions, to the `_functions` + private field before cloning, and the named functions will not be + inherited by clones. + + > Container = require "std.container" + > Graph = Container { _type = "Graph" } + > Graph.nodes = function (graph) + >> local n = 0 + >> for _ in pairs (graph) do n = n + 1 end + >> return n + >> end + > Graph._functions = { nodes = Graph.nodes } + > g = Graph { "node1", "node2" } + > = Graph.nodes (g) + 2 + > = g.nodes + nil + + When making your own prototypes, start from @{std.container} if you + want to access the contents of your objects with the `[]` operator, or + @{std.object} if you want to access the functionality of your objects + with named object methods. + + @classmod std.container +]] + + +local base = require "std.base" + + +--- Return the named entry from x's metatable. +-- @param x anything +-- @tparam string n name of entry +-- @return value associate with `n` in `x`'s metatable, else nil +local function metaentry (x, n) + local ok, f = pcall (function (x) + return getmetatable (x)[n] + end, + x) + if not ok then f = nil end + return f +end + + +--- Filter a table with a function. +-- @tparam table t source table +-- @tparam function f a function that takes key and value arguments +-- from calling `pairs` on `t`, and returns non-`nil` for elements +-- that should be in the returned table +-- @treturn table a shallow copy of `t`, with elements removed according +-- to `f` +local function filter (t, f) + local r = {} + for k, v in pairs (t) do + if f (k, v) then + r[k] = v + end + end + return r +end + + +local functions = { + -- Type of this container. + -- @static + -- @tparam std.container o an container + -- @treturn string type of the container + -- @see std.object.prototype + prototype = function (o) + local _type = metaentry (o, "_type") + if type (o) == "table" and _type ~= nil then + return _type + end + return type (o) + end, +} + + +--- Container prototype. +-- @table std.container +-- @string[opt="Container"] _type type of Container, returned by +-- @{std.object.prototype} +-- @tfield table|function _init a table of field names, or +-- initialisation function, used by @{__call} +-- @tfield nil|table|std.set _functions a table whose keys are the names +-- of module functions not copied by @{__call} +local metatable = { + _type = "Container", + _init = {}, + _functions = functions, + + + --- Return a clone of this container. + -- @function __call + -- @param ... arguments for `_init` + -- @treturn std.container a clone of the called container. + -- @see std.object:__call + __call = function (self, ...) + local mt = getmetatable (self) + local fn = mt._functions or {} + + -- Make a shallow copy of prototype, skipping _functions. + local obj = filter (self, function (e) return not fn[e] end) + + -- Map arguments according to _init metamethod. + local _init = metaentry (self, "_init") + if type (_init) == "table" then + base.merge (obj, base.clone_rename (_init, ...)) + else + obj = _init (obj, ...) + end + + -- Extract any new fields beginning with "_". + local obj_mt = {} + for k, v in pairs (obj) do + if type (k) == "string" and k:sub (1, 1) == "_" then + obj_mt[k], obj[k] = v, nil + end + end + + -- _functions is not propagated from prototype to clone. + if next (obj_mt) == nil and mt._functions == nil then + -- Reuse metatable if possible + obj_mt = getmetatable (self) + else + + -- Otherwise copy the prototype metatable... + local t = filter (mt, function (e) return e ~= "_functions" end) + -- ...but give preference to "_" prefixed keys from init table + obj_mt = base.merge (t, obj_mt) + + -- ...and merge container methods from prototype too. + if mt then + if type (obj_mt.__index) == "table" and type (mt.__index) == "table" then + local methods = base.clone (obj_mt.__index) + for k, v in pairs (mt.__index) do + methods[k] = methods[k] or v + end + obj_mt.__index = methods + end + end + end + + return setmetatable (obj, obj_mt) + end, + + + --- Return a string representation of this container. + -- @function __tostring + -- @treturn string stringified container representation + -- @see std.object.__tostring + __tostring = function (self) + local totable = getmetatable (self).__totable + local array = base.clone (totable (self), "nometa") + local other = base.clone (array, "nometa") + local s = "" + if #other > 0 then + for i in ipairs (other) do other[i] = nil end + end + for k in pairs (other) do array[k] = nil end + for i, v in ipairs (array) do array[i] = tostring (v) end + + local keys, dict = {}, {} + for k in pairs (other) do table.insert (keys, k) end + table.sort (keys, function (a, b) return tostring (a) < tostring (b) end) + for _, k in ipairs (keys) do + table.insert (dict, tostring (k) .. "=" .. tostring (other[k])) + end + + if #array > 0 then + s = s .. table.concat (array, ", ") + if next (dict) ~= nil then s = s .. "; " end + end + if #dict > 0 then + s = s .. table.concat (dict, ", ") + end + + return metaentry (self, "_type") .. " {" .. s .. "}" + end, + + + --- Return a table representation of this container. + -- @function __totable + -- @treturn table a shallow copy of non-private container fields + -- @see std.object:__totable + __totable = function (self) + return filter (self, function (e) + return type (e) ~= "string" or e:sub (1, 1) ~= "_" + end) + end, +} + +return setmetatable (functions, metatable) diff --git a/lib/std/object.lua b/lib/std/object.lua index c759932..6172cae 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -3,21 +3,21 @@ This module creates the root prototype object from which every other object is descended. There are no classes as such, rather new objects - are created by cloning an existing prototype object, and then changing - or adding to it. Further objects can then be made by cloning the changed + are created by cloning an existing object, and then changing or adding + to the clone. Further objects can then be made by cloning the changed object, and so on. - Objects are cloned by simply calling an existing object which then - serves as a prototype, from which the new object is copied. + Objects are cloned by simply calling an existing object, which then + serves as a prototype from which the new object is copied. All valid objects contain a field `_init`, which determines the syntax required to execute the cloning process: 1. `_init` can be a list of keys; then the unnamed `init_1` through - `init_n` values from the argument table are assigned to the + `init_m` values from the argument table are assigned to the corresponding keys in `new_object`; - new_object = prototype { + new_object = proto_object { init_1, ..., init_m; field_1 = value_1, ... @@ -27,235 +27,127 @@ 2. Or it can be a function, in which the arguments passed to the prototype during cloning are simply handed to the `_init` function: - new_object = prototype (value, ...) - - Field names beginning with "_" are *private*, and moved into the object - metatable during cloning. Unless `new_object` changes the metatable this - way, then it will share a metatable with `prototype` for efficiency. + new_object = proto_object (arg, ...) Objects, then, are essentially tables of `field\_n = value\_n` pairs: - * Access an object field: `object.field` - * Call an object method: `object:method (...)` - * Call a "class" method: `Class.method (object, ...)` - * Add a field: `object.field = x` - * Add a method: `function object:method (...) ... end` + > o = Object { + >> field_1 = "value_1", + >> method_1 = function (self) return self.field_1 end, + >> } + > = o.field_1 + value_1 + > o.field_2 = 2 + > function o:method_2 (n) return self.field_2 + n end + > = o:method_2 (2) + 4 + + Normally `new_object` automatically shares a metatable with + `proto_object`. However, field names beginning with "_" are *private*, + and moved into the object metatable during cloning. So, adding new + private fields to an object during cloning will result in a new + metatable for `new_object` that also contains a copy of all the entries + in the `proto_object` metatable. + + Note that Object methods are stored in the `\_\_index` field of their + metatable, and so cannot also use `\_\_index` to lookup references with + square brackets. See @{std.container} objects if you want to do that. @classmod std.object ]] -local base = require "std.base" - --- Return the named entry from x's metatable, if any, else nil. -local function metaentry (x, n) - local ok, f = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if not ok then f = nil end - return f -end - - ---- Return the extended object type, if any, else primitive type. --- --- It's conventional to organise similar objects according to a string --- valued `_type` field, which can then be queried using this function. --- --- Stack = Object { --- _type = "Stack", --- --- __tostring = function (self) ... end, --- --- __index = { --- push = function (self) ... end, --- pop = function (self) ... end, --- }, --- } --- stack = Stack {} --- --- stack:type () --> "Stack" --- --- @function type --- @tparam std.object o an object --- @treturn string type of the object -local function object_type (o) - local _type = metaentry (o, "_type") - if type (o) == "table" and _type ~= nil then - return _type - end - return type (o) -end - - ---- Clone an object. --- --- Objects are usually cloned by calling a prototype directly, so this --- method is rarely used explicitly. --- @param ... arguments for _init --- @treturn std.object a clone of `prototype`, adjusted --- according to the rules above, and sharing a metatable where possible --- @see __call -local function clone (self, ...) - local mt = getmetatable (self) - - -- Make a shallow copy of prototype. - local object = {} - for k, v in pairs (self) do - object[k] = v - end - - -- Map arguments according to _init metamethod. - local _init = metaentry (self, "_init") - if type (_init) == "table" then - base.merge (object, base.clone_rename (_init, ...)) - else - object = _init (object, ...) - end - -- Extract any new fields beginning with "_". - local object_mt = {} - for k, v in pairs (object) do - if type (k) == "string" and k:sub (1, 1) == "_" then - object_mt[k], object[k] = v, nil - end - end +local Container = require "std.container" +local metamethod = (require "std.functional").metamethod - if next (object_mt) == nil then - -- Reuse metatable if possible - object_mt = getmetatable (self) - else - -- Otherwise copy the prototype metatable... - local t = {} - for k, v in pairs (mt) do - t[k] = v - end - -- ...but give preference to "_" prefixed keys from init table - object_mt = base.merge (t, object_mt) - - -- ...and merge object methods from prototype too. - if mt then - if type (object_mt.__index) == "table" and type (mt.__index) == "table" then - local methods = base.clone (object_mt.__index) - for k, v in pairs (mt.__index) do - methods[k] = methods[k] or v - end - object_mt.__index = methods - end - end - end - - return setmetatable (object, object_mt) -end - - ---- Return a stringified version of the contents of object. --- --- First the object type, and then between { and } a list of the array --- part of the object table (without numeric keys) followed by the --- remaining key-value pairs. +--- Root object. -- --- This function doesn't recurse explicity, but relies upon suitable --- `__tostring` metamethods in contained objects. --- --- @function tostring --- @treturn string stringified object representation -local function stringify (self) - local totable = getmetatable (self).__totable - local array = base.clone (totable (self), "nometa") - local other = base.clone (array, "nometa") - local s = "" - if #other > 0 then - for i in ipairs (other) do other[i] = nil end - end - for k in pairs (other) do array[k] = nil end - for i, v in ipairs (array) do array[i] = tostring (v) end - - local keys, dict = {}, {} - for k in pairs (other) do table.insert (keys, k) end - table.sort (keys, function (a, b) return tostring (a) < tostring (b) end) - for _, k in ipairs (keys) do - table.insert (dict, tostring (k) .. "=" .. tostring (other[k])) - end - - if #array > 0 then - s = s .. table.concat (array, ", ") - if next (dict) ~= nil then s = s .. "; " end - end - if #dict > 0 then - s = s .. table.concat (dict, ", ") - end - - return metaentry (self, "_type") .. " {" .. s .. "}" -end +-- Changing the values of these fields in a new object will change the +-- corresponding behaviour. +-- @table std.object +-- @string[opt="Object"] _type type of Object, returned by @{prototype} +-- @tfield table|function _init a table of field names, or +-- initialisation function, used by @{clone} +-- @tfield nil|table|std.set _functions a table whose keys are the names +-- of module functions not copied by @{clone} +return Container { + _type = "Object", + -- No need for explicit module functions here, because calls to, e.g. + -- `Object.prototype` will automatically fall back metamethods in + -- `\_\_index`. + + __index = { + --- Clone this Object. + -- @function clone + -- @tparam std.object o an object + -- @param ... a list of arguments if `o._init` is a function, or a + -- single table if `o._init` is a table. + -- @treturn std.object a clone of `o` + -- @see __call + clone = metamethod (Container, "__call"), + + + --- Type of an object, or primitive. + -- + -- It's conventional to organise similar objects according to a + -- string valued `_type` field, which can then be queried using this + -- function. + -- + -- Stack = Object { + -- _type = "Stack", + -- + -- __tostring = function (self) ... end, + -- + -- __index = { + -- push = function (self) ... end, + -- pop = function (self) ... end, + -- }, + -- } + -- stack = Stack {} + -- + -- stack:prototype () --> "Stack" + -- + -- @function prototype + -- @param x anything + -- @treturn string type of `x` + prototype = Container.prototype, + + + -- Backwards compatibility: + type = Container.prototype, + }, ---- Return a new table with a shallow copy of all non-private fields --- in object, where private fields have keys prefixed with "_". --- @treturn table raw (non-object) table of object fields -local function totable (self) - local t = {} - for k, v in pairs (self) do - if type (k) ~= "string" or k:sub (1, 1) ~= "_" then - t[k] = v - end - end - return t -end + --- Return a @{clone} of this object, and its metatable. + -- + -- Private fields are stored in the metatable. + -- @function __call + -- @param ... arguments for `\_init` + -- @treturn std.object a clone of the this object. + -- @see clone ---- Metatable for objects. --- --- This can't and shouldn't be set directly, because the Object class --- manages it transparently during cloning. --- @table _metatable --- @tfield string _type derived objects can override this for objects --- intended to be a prototype for further specialised objects. --- @tfield table|function _init Derived objects can override this to be --- a set of keys to use for assigning unnamed arguments, or a function --- to that will be called with unnamed arguments durict cloning. --- @see type -local metatable = { - _type = "Object", - _init = {}, - ------ - -- Return a shallow copy of non-private object fields. + --- Return a string representation of this object. -- - -- This pseudo-metamethod is used during object cloning to make the - -- intial new object table, and can be overridden in other objects - -- for greater control of which fields are considered non-private. - -- @function __totable - -- @see totable - __totable = totable, - - ------ - -- Return a string representation of *object*. + -- First the object type, and then between { and } a list of the + -- array part of the object table (without numeric keys) followed + -- by the remaining key-value pairs. + -- + -- This function doesn't recurse explicity, but relies upon suitable + -- `__tostring` metamethods in field values. -- @function __tostring + -- @treturn string stringified container representation -- @see tostring - __tostring = stringify, - --- Return a clone of *object*. - -- - -- Normally a cloned object will share its metatable with its prototype, - -- unless some new fields for the cloned object begin with '_', in which - -- case they are merged into a copy of the prototype metatable to form - -- a new metatable for the cloned object (and its clones). - -- @function __call - -- @param ... arguments for `_init` - -- @treturn std.object a clone of the called object. - __call = function (self, ...) - return self:clone (...) - end, - --- @export - __index = { - clone = clone, - tostring = stringify, - totable = totable, - type = object_type, - }, + --- Return a shallow copy of non-private object fields. + -- + -- Used by @{clone} to get the base contents of the new object. Can + -- be overridden in other objects for greater control of which fields + -- are considered non-private. + -- @function __totable + -- @treturn table a shallow copy of non-private object fields + -- @see std.table.totable } - -return setmetatable ({}, metatable) diff --git a/local.mk b/local.mk index 373124d..468e37b 100644 --- a/local.mk +++ b/local.mk @@ -41,6 +41,7 @@ luastddir = $(luadir)/std dist_luastd_DATA = \ lib/std/base.lua \ + lib/std/container.lua \ lib/std/debug.lua \ lib/std/debug_init.lua \ lib/std/functional.lua \ diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml new file mode 100644 index 0000000..34a8ef0 --- /dev/null +++ b/specs/container_spec.yaml @@ -0,0 +1,108 @@ +before: + Container = require "std.container" + prototype = (require "std.object").prototype + +specify Container: +- describe construction: + - context from Container prototype: + - before: + things = Container {"foo", "bar", baz="quux"} + - it constructs a new container: + expect (things).should_not_be (Container) + expect (type (things)).should_be "table" + expect (prototype (things)).should_be "Container" + - it reuses the container metatable: + o, p = things {"o"}, things {"p"} + expect (getmetatable (o)).should_be (getmetatable (p)) + - it sets container fields from arguments: + o = Container {"foo", "bar", baz="quux"} + expect (o).should_equal (things) + - it serves as a prototype for new instances: + o = things {} + expect (prototype (o)).should_be "Container" + expect (o).should_not_be (things) + expect (o).should_equal (things) + expect (getmetatable (o)).should_be (getmetatable (things)) + - it separates '_' prefixed fields: + expect (Container {foo="bar", _baz="quux"}). + should_equal (Container {foo="bar"}) + - it puts '_' prefixed fields in a new metatable: + things = Container {foo="bar", _baz="quux"} + expect (getmetatable (things)).should_not_be (getmetatable (Container)) + expect (getmetatable (things)._baz).should_be "quux" + - context with module functions: + - before: + fold = (require "std.functional").fold + functions = { + count = function (bag) + return fold (function (r, k) return r + bag[k] end, 0, pairs, bag) + end, + } + Bag = Container { + _type = "Bag", _functions = functions, count = functions.count, + } + - it does not propagate _functions: + things = Bag {} + expect (things.count).should_be (nil) + - it does not provide object methods: | + things = Bag {} + expect (things:count ()).should_error "attempt to call method 'count'" + - it does retain module functions: + things = Bag { apples = 1, oranges = 3 } + expect (Bag.count (things)).should_be (4) + - it does allow elements named after module functions: + things = Bag { count = 1337 } + expect (Bag.count (things)).should_be (1337) + + +- describe field access: + - before: + things = Container {"foo", "bar", baz="quux"} + - context with bracket notation: + - it provides access to existing contents: + expect (things[1]).should_be "foo" + expect (things["baz"]).should_be "quux" + - it assigns new contents: + things["new"] = "value" + expect (things["new"]).should_be "value" + - context with dot notation: + - it provides access to existing contents: + expect (things.baz).should_be "quux" + - it assigns new contents: + things.new = "value" + expect (things.new).should_be "value" + + +- describe stringification: + - before: + things = Container {_type = "Derived", "one", "two", "three"} + - it returns a string: + expect (type (tostring (things))).should_be "string" + - it contains the type: + expect (tostring (Container {})).should_contain "Container" + expect (tostring (things)).should_contain (prototype (things)) + - it contains the ordered array part elements: + expect (tostring (things)).should_contain "one, two, three" + - it contains the ordered dictionary part elements: + expect (tostring (Container {one = true, two = true, three = true})). + should_contain "one=true, three=true, two=true" + expect (tostring (things {one = true, two = true, three = true})). + should_contain "one=true, three=true, two=true" + - it contains a ';' separator only when container has array and dictionary parts: + expect (tostring (things)).should_not_contain ";" + expect (tostring (Container {one = true, two = true, three = true})). + should_not_contain ";" + expect (tostring (things {one = true, two = true, three = true})). + should_contain ";" + + +- describe tablification: + - before: + totable = (require "std.table").totable + Derived = Container {_type = "Derived", "one", "two", three = true} + - it returns a table: + expect (prototype (totable (Derived))).should_be "table" + - it contains all non-hidden fields of container: + expect (totable (Derived)).should_contain.all_of {"one", "two", "three"} + - it does not contain any hidden fields of container: + expect (totable (Derived)).should_equal {"one", "two", three = true} diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 0b36dd8..2a07af2 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -1,6 +1,7 @@ before: - Object = require "std.object" - obj = Object {"foo", "bar", baz="quux"} + Object = require "std.object" + obj = Object {"foo", "bar", baz="quux"} + prototype = Object.prototype specify Object: - describe construction: @@ -9,7 +10,7 @@ specify Object: obj = Object:clone {} expect (obj).should_not_be (Object) expect (type (obj)).should_be "table" - expect (Object.type (obj)).should_be "Object" + expect (prototype (obj)).should_be "Object" - it reuses the Object metatable: o = obj:clone {"o"} p = o:clone {"p"} @@ -21,7 +22,7 @@ specify Object: expect (o).should_equal (obj) - it serves as a prototype for new instances: o = obj:clone {} - expect (Object.type (o)).should_be "Object" + expect (prototype (o)).should_be "Object" expect (o).should_not_be (obj) expect (o).should_equal (obj) expect (getmetatable (o)).should_be (getmetatable (obj)) @@ -33,62 +34,40 @@ specify Object: expect (getmetatable (obj)).should_not_be (getmetatable (Object)) expect (getmetatable (obj)._baz).should_be "quux" - # Object {args} is just syntactic sugar for Object:clone {args} - - context from Object prototype: - - it constructs a new object: - obj = Object {} - expect (obj).should_not_be (Object) - expect (type (obj)).should_be "table" - expect (Object.type (obj)).should_be "Object" - - it reuses the object metatable: - o, p = Object {"o"}, Object {"p"} - expect (getmetatable (o)).should_be (getmetatable (p)) - - it sets object fields from arguments: - o = Object {"foo", "bar", baz="quux"} - expect (o).should_equal (obj) - - it serves as a prototype for new instances: - o = obj {} - expect (Object.type (o)).should_be "Object" - expect (o).should_not_be (obj) - expect (o).should_equal (obj) - expect (getmetatable (o)).should_be (getmetatable (obj)) - - it separates '_' prefixed fields: - expect (Object {foo="bar", _baz="quux"}). - should_equal (Object {foo="bar"}) - - it puts '_' prefixed fields in a new metatable: - obj = Object {foo="bar", _baz="quux"} - expect (getmetatable (obj)).should_not_be (getmetatable (Object)) - expect (getmetatable (obj)._baz).should_be "quux" - -- describe type: +- describe prototype: - before: o = Object {} - context when called from the object module: - - it reports the type stored in the object's metatable: - expect (Object.type (o)).should_be "Object" + - it reports the prototype stored in the object's metatable: + expect (prototype (o)).should_be "Object" - it reports the type of a cloned object: - expect (Object.type (o {})).should_be "Object" - - it reports the type of a subclassed object: + expect (prototype (o {})).should_be "Object" + - it reports the type of a derived object: Example = Object {_type = "Example"} - expect (Object.type (Example)).should_be "Example" - - it reports the type of a cloned subclassed object: + expect (prototype (Example)).should_be "Example" + - it reports the type of a cloned derived object: Portal = Object {_type = "Demon"} p = Portal {} - expect (Object.type (p)).should_be "Demon" - expect (Object.type (p {})).should_be "Demon" + expect (prototype (p)).should_be "Demon" + expect (prototype (p {})).should_be "Demon" - context when called as an object method: - it reports the type stored in the object's metatable: - expect (o:type ()).should_be "Object" + expect (o:prototype ()).should_be "Object" - it reports the type of a cloned object: - expect ((o {}):type ()).should_be "Object" + expect ((o {}):prototype ()).should_be "Object" - it reports the type of a subclassed object: Example = Object {_type = "Example"} - expect (Example:type ()).should_be "Example" + expect (Example:prototype ()).should_be "Example" - it reports the type of a cloned subclassed object: Portal = Object {_type = "Demon"} p = Portal {} - expect (p:type ()).should_be "Demon" - expect ((p {}):type ()).should_be "Demon" + expect (p:prototype ()).should_be "Demon" + expect ((p {}):prototype ()).should_be "Demon" + - context backwards compatibility: + - it reports the prototype stored in the object's metatable: + expect (Object.type (o)).should_be "Object" + - it reports the type stored in the object's metatable: + expect (o:type ()).should_be "Object" - describe instantiation from a prototype: @@ -182,12 +161,12 @@ specify Object: _init = { "field", "method"}, field = "in prototype", method = function (self, ...) - return Object.type (self) .. " class, " .. + return prototype (self) .. " class, " .. table.concat ({...}, ", ") end, } instance = Prototype {"in object", function (self, ...) - return Object.type (self) .. " instance, " .. + return prototype (self) .. " instance, " .. table.concat ({...}, ", ") end, } @@ -207,12 +186,12 @@ specify Object: expect (instance.newfield).should_be "new" - it allows new instance methods to be added: instance.newmethod = function (self) - return Object.type (self) .. ", new instance method" + return prototype (self) .. ", new instance method" end expect (instance:newmethod ()).should_be "Prototype, new instance method" - it allows new class methods to be added: Prototype.newmethod = function (self) - return Object.type (self) .. ", new class method" + return prototype (self) .. ", new class method" end expect (Prototype.newmethod (instance)). should_be "Prototype, new class method" @@ -220,14 +199,14 @@ specify Object: - describe object method propagation: - context with no custom instance methods: - # :type is a method defined by the root object + # :prototype is a method defined by the root object - it inherits prototype object methods: instance = Object {} - expect (instance:type ()).should_be "Object" + expect (instance:prototype ()).should_be "Object" - it propagates prototype methods to derived instances: Derived = Object {_type = "Derived"} instance = Derived {} - expect (instance:type ()).should_be "Derived" + expect (instance:prototype ()).should_be "Derived" - context with custom object methods: - before: bag = Object { @@ -239,12 +218,12 @@ specify Object: end, }, } - # :type is a method defined by the root object + # :prototype is a method defined by the root object - it inherits prototype object methods: - expect (bag:type ()).should_be "bag" + expect (bag:prototype ()).should_be "bag" - it propagates prototype methods to derived instances: instance = bag {} - expect (instance:type ()).should_be "bag" + expect (instance:prototype ()).should_be "bag" - it supports method calls: expect (bag:add "foo").should_be (bag) expect (bag.foo).should_be (1) @@ -292,7 +271,7 @@ specify Object: Derived = Object {_type = "Derived", "one", "two", three = true} - it returns a table: - expect (Object.type (totable (Derived))).should_be "table" + expect (prototype (totable (Derived))).should_be "table" - it contains all non-hidden fields of object: expect (totable (Derived)).should_contain.all_of {"one", "two", "three"} - it does not contain any hidden fields of object: @@ -306,7 +285,7 @@ specify Object: expect (type (tostring (obj))).should_be "string" - it contains the type: expect (tostring (Object {})).should_contain "Object" - expect (tostring (obj)).should_contain (Object.type (obj)) + expect (tostring (obj)).should_contain (prototype (obj)) - it contains the ordered array part elements: expect (tostring (obj)).should_contain "one, two, three" - it contains the ordered dictionary part elements: diff --git a/specs/specs.mk b/specs/specs.mk index 5ffbf30..99ae87a 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -13,6 +13,7 @@ SPECL_ENV = $(LUA_ENV) ## ------ ## specl_SPECS = \ + $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ $(srcdir)/specs/getopt_spec.yaml \ $(srcdir)/specs/io_spec.yaml \ From f19080bbc6600ad67b337626513d7100eb2a802c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 10 Dec 2013 17:53:03 +1300 Subject: [PATCH 028/703] list: remove trailing whitespace. * lib/std/list.lua: Remove trailing whitespace. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index ee6d21d..5cb0ea8 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -12,7 +12,7 @@ => 3 => 2 => 1 - + ...they can also be called as module functions with an explicit argument: local List = require "std.list" From 60bd04d58d18abf5cd361c54e597a88ddd396dc4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Dec 2013 00:07:13 +1300 Subject: [PATCH 029/703] set: derive from std.container, keys no longer clash with method names. * specs/set_spec.yaml: Replace object methods with prototype functions through out. Check that set elements with the same name as a prototype function behave properly. * lib/std/set.lua: Rewrite as a derivative of std.container. Change object methods to Container style prototype functions. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 21 +++- lib/std/set.lua | 274 +++++++++++++++++++++++++++----------------- specs/set_spec.yaml | 200 ++++++++++++++++---------------- 3 files changed, 287 insertions(+), 208 deletions(-) diff --git a/NEWS b/NEWS index 4d5bd4c..d470483 100644 --- a/NEWS +++ b/NEWS @@ -17,20 +17,24 @@ Stdlib NEWS - User visible changes Prototype = Object { _type = "Prototype", } - Objects derived from the `std.object` prototype return a new table - with a shallow copy of all non-internal fields (keys that do not + 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. - - list, set and strbuf are now derived from std.object, which means that - they respond to `object.type` with appropriate type names ("set", - "strbuf", etc.) and can be used as prototypes for further derived - objects or clones; support object:type (); respond to totable etc. + - set is now derived from `std.container`, so there are no object + methods. Instead there are a full complement of equivalent prototype + functions. Metamethods continue to work as before. - `string.prettytostring` always displays table elements in the same order, as provided by `table.sort`. @@ -74,7 +78,7 @@ Stdlib NEWS - User visible changes 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 + - 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 @@ -84,6 +88,11 @@ Stdlib NEWS - User visible changes 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: + + local union = Set.union (set1, set2) + * Noteworthy changes in release 35 (2013-05-06) [stable] diff --git a/lib/std/set.lua b/lib/std/set.lua index 77d4a5b..dbd2395 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -1,10 +1,20 @@ --[[-- - Set object. + Set container. + + Derived from @{std.container}, and inherits Container's metamethods. + + Note that Functions listed below are available only available from the + Set prototype returned by requiring this module, because Container + objects cannot have object methods. + @classmod std.set + @see std.container ]] -local list = require "std.base" -local Object = require "std.object" +local base = require "std.base" +local Container = require "std.container" +local prototype = (require "std.object").prototype + local Set -- forward declaration @@ -12,34 +22,42 @@ local Set -- forward declaration -- The representation is a table whose tags are the elements, and -- whose values are true. + --- Say whether an element is in a set. +-- @tparam set set a set -- @param e element --- @return `true` if e is in set, `false` +-- @return `true` if `e` is in `set`, otherwise `false` -- otherwise -local function member (self, e) - return rawget (self, e) == true +local function member (set, e) + return rawget (set, e) == true end + --- Insert an element into a set. +-- @tparam set set a set -- @param e element -- @return the modified set -local function insert (self, e) - rawset (self, e, true) - return self +local function insert (set, e) + rawset (set, e, true) + return set end + --- Delete an element from a set. +-- @tparam set set a set -- @param e element -- @return the modified set -local function delete (self, e) - rawset (self, e, nil) - return self +local function delete (set, e) + rawset (set, e, nil) + return set end + --- Iterator for sets. +-- @tparam set set a set -- @todo Make the iterator return only the key -local function elems (self) - return pairs (self) +local function elems (set) + return pairs (set) end @@ -47,168 +65,211 @@ end local difference, symmetric_difference, intersection, union, subset, equal + --- Find the difference of two sets. --- @param s set --- @return `self` with elements of s removed -function difference (self, s) - if Object.type (s) == "table" then - s = Set (s) +-- @tparam set set1 a set +-- @tparam table|set set2 another set, or table +-- @return `set1` with elements of s removed +function difference (set1, set2) + if prototype (set2) == "table" then + set2 = Set (set2) end local t = Set {} - for e in elems (self) do - if not member (s, e) then + for e in elems (set1) do + if not member (set2, e) then insert (t, e) end end return t end + --- Find the symmetric difference of two sets. --- @param s set --- @return elements of `self` and `s` that are in `self` or `s` but not both -function symmetric_difference (self, s) - if Object.type (s) == "table" then - s = Set (s) +-- @tparam set set1 a set +-- @tparam table|set set2 another set, or table +-- @return elements of `set1` and `set2` that are in `set1` or `set2` but not both +function symmetric_difference (set1, set2) + if prototype (set2) == "table" then + set2 = Set (set2) end - return difference (union (self, s), intersection (s, self)) + return difference (union (set1, set2), intersection (set2, set1)) end + --- Find the intersection of two sets. --- @param s set --- @return set intersection of `self` and `s` -function intersection (self, s) - if Object.type (s) == "table" then - s = Set (s) +-- @tparam set set1 a set +-- @tparam table|set set2 another set, or table +-- @return set intersection of `set1` and `set2` +function intersection (set1, set2) + if prototype (set2) == "table" then + set2 = Set (set2) end local t = Set {} - for e in elems (self) do - if member (s, e) then + for e in elems (set1) do + if member (set2, e) then insert (t, e) end end return t end + --- Find the union of two sets. --- @param s set or set-like table --- @return set union of `self` and `s` -function union (self, s) - if Object.type (s) == "table" then - s = Set (s) +-- @tparam set set1 a set +-- @tparam table|set set2 another set, or table +-- @return set union of `set1` and `set2` +function union (set1, set2) + if prototype (set2) == "table" then + set2 = Set (set2) end local t = Set {} - for e in elems (self) do + for e in elems (set1) do insert (t, e) end - for e in elems (s) do + for e in elems (set2) do insert (t, e) end return t end + --- Find whether one set is a subset of another. --- @param s set --- @return `true` if `self` is a subset of `s`, `false` otherwise -function subset (self, s) - if Object.type (s) == "table" then - s = Set (s) +-- @tparam set set1 a set +-- @tparam table|set set2 another set, or table +-- @return `true` if `set1` is a subset of `set2`, `false` otherwise +function subset (set1, set2) + if prototype (set2) == "table" then + set2 = Set (set2) end - for e in elems (self) do - if not member (s, e) then + for e in elems (set1) do + if not member (set2, e) then return false end end return true end + --- Find whether one set is a proper subset of another. --- @param s set --- @return `true` if `self` is a proper subset of `s`, `false` otherwise -function propersubset (self, s) - if Object.type (s) == "table" then - t = Set (s) +-- @tparam set set1 a set +-- @tparam table|set set2 another set, or table +-- @return `true` if `set1` is a proper subset of `set2`, `false` otherwise +function proper_subset (set1, set2) + if prototype (set2) == "table" then + t = Set (set2) end - return subset (self, s) and not subset (s, self) + return subset (set1, set2) and not subset (set2, set1) end + --- Find whether two sets are equal. --- @param s set --- @return `true` if `self` and `s` are equal, `false` otherwise -function equal (self, s) - return subset (self, s) and subset (s, self) +-- @tparam set set1 a set +-- @tparam table|set set2 another set, or table +-- @return `true` if `set1` and `set2` are equal, `false` otherwise +function equal (set1, set2) + return subset (set1, set2) and subset (set2, set1) end -Set = Object { - -- Derived object type. - _type = "Set", +--- Set prototype object. +-- @table std.set +-- @string[opt="Set"] _type type of Set, returned by +-- @{std.object.prototype} +-- @tfield table|function _init a table of field names, or +-- initialisation function, see @{std.object.__call} +-- @tfield nil|table|set _functions a table whose keys are the names +-- of module functions not copied by @{std.object.__call} +Set = Container { + _type = "Set", - -- Initialise. - _init = function (self, t) - for e in list.elems (t) do - insert (self, e) - end - return self - end, + _init = function (self, t) + for e in base.elems (t) do + insert (self, e) + end + return self + end, + delete = delete, + difference = difference, + elems = elems, + equal = equal, + insert = insert, + intersection = intersection, + member = member, + proper_subset = proper_subset, + subset = subset, + symmetric_difference = symmetric_difference, + union = union, - ------ - -- Union operator. - -- set + table = union + --- Union operator. + -- union = set + table -- @function __add - -- @param set set - -- @param table set or set-like table + -- @static + -- @tparam set set set + -- @tparam table|set table another set or table + -- @treturn set union -- @see union __add = union, - ------ - -- Difference operator. - -- set - table = set difference + + --- Difference operator. + -- difference = set - table -- @function __sub - -- @param set set - -- @param table set or set-like table + -- @static + -- @tparam set set set + -- @tparam table|set table another set or table + -- @treturn set difference -- @see difference __sub = difference, - ------ - -- Intersection operator. - -- set * table = intersection + + --- Intersection operator. + -- intersection = set * table -- @function __mul - -- @param set set - -- @param table set or set-like table + -- @static + -- @tparam set set set + -- @tparam table|set table another set or table + -- @treturn set intersection -- @see intersection __mul = intersection, - ------ - -- Symmetric difference operator. - -- set / table = symmetric difference + + --- Symmetric difference operator. + -- symmetric_difference = set / table -- @function __div - -- @param set set - -- @param table set or set-like table + -- @static + -- @tparam set set set + -- @tparam table|set table another set or table + -- @treturn set symmetric_difference -- @see symmetric_difference __div = symmetric_difference, - ------ - -- Subset operator. - -- set <= table = subset + + --- Subset operator. + -- set = set <= table -- @function __le - -- @param set set - -- @param table set or set-like table + -- @static + -- @tparam set set set + -- @tparam table|set table another set or table + -- @treturn set subset -- @see subset __le = subset, - ------ - -- Proper subset operator. - -- set < table = proper subset + + --- Proper subset operator. + -- proper_subset = set < table -- @function __lt - -- @param set set - -- @param table set or set-like table - -- @see propersubset - __lt = propersubset, - - ------ - -- Object to table conversion. - -- table = set:totable () + -- @static + -- @tparam set set set + -- @tparam table|set table another set or table + -- @treturn set proper_subset + -- @see proper_subset + __lt = proper_subset, + + + -- Set to table conversion. + -- @treturn table table representation of a set. + -- @see std.table.totable __totable = function (self) local t = {} for e in elems (self) do @@ -218,8 +279,9 @@ Set = Object { return t end, + --- @export - __index = { + _functions = { delete = delete, difference = difference, elems = elems, @@ -227,7 +289,7 @@ Set = Object { insert = insert, intersection = intersection, member = member, - propersubset = propersubset, + proper_subset = proper_subset, subset = subset, symmetric_difference = symmetric_difference, union = union, diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index 3f674cf..bfc22b0 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -1,65 +1,43 @@ before: require "spec_helper" - Object = require "std.object" - Set = require "std.set" - totable = (require "std.table").totable - s = Set {"foo", "bar", "bar"} + Set = require "std.set" + prototype = (require "std.object").prototype + totable = (require "std.table").totable + s = Set {"foo", "bar", "bar"} specify Set: - describe construction: - - context from Set clone method: - - it constructs a new set: - s = Set:clone {} - expect (s).should_not_be (Set) - expect (Object.type (s)).should_be "Set" - - it reuses the Set metatable: - s, t = Set:clone {"s"}, Set:clone {"t"} - expect (getmetatable (s)).should_be (getmetatable (t)) - - it initialises set with constructor parameters: - t = Set:clone {"foo", "bar", "bar"} - expect (t).should_equal (s) - - it serves as a prototype for new instances: - obj = s:clone {} - expect (Object.type (obj)).should_be "Set" - expect (obj).should_equal (s) - expect (getmetatable (obj)).should_be (getmetatable (s)) - - # Set {args} is just syntactic sugar for Set:clone {args} - - context from Set object prototype: - - it constructs a new set: - s = Set {} - expect (s).should_not_be (Set) - expect (Object.type (s)).should_be "Set" - - it reuses the Set metatable: - s, t = Set {"s"}, Set {"t"} - expect (getmetatable (s)).should_be (getmetatable (t)) - - it initialises set with constructor parameters: - t = Set:clone {"foo", "bar", "bar"} - expect (t).should_equal (s) - - it serves as a prototype for new instances: - obj = s {} - expect (Object.type (obj)).should_be "Set" - expect (obj).should_equal (s) - expect (getmetatable (obj)).should_be (getmetatable (s)) + - it constructs a new set: + s = Set {} + expect (s).should_not_be (Set) + expect (prototype (s)).should_be "Set" + - it initialises set with constructor parameters: + t = Set {"foo", "bar", "bar"} + expect (t).should_equal (s) + - it serves as a prototype for new instances: + obj = s {} + expect (prototype (obj)).should_be "Set" + expect (obj).should_equal (s) + expect (getmetatable (obj)).should_be (getmetatable (s)) - describe delete: - - context when called as a set object method: + - context when called as a Set module function: - before: s = Set {"foo", "bar", "baz"} - it returns a set object: - expect (Object.type (s:delete ("foo"))).should_be "Set" + expect (prototype (Set.delete (s, "foo"))).should_be "Set" - it is destructive: - s:delete ("bar") + Set.delete (s, "bar") expect (s).should_not_have_member "bar" - it returns the modified set: - expect (s:delete ("baz")).should_not_have_member "baz" + expect (Set.delete (s, "baz")).should_not_have_member "baz" - it ignores removal of non-members: | clone = s {} - expect (s:delete ("quux")).should_equal (clone) + expect (Set.delete (s, "quux")).should_equal (clone) - it deletes a member from the set: expect (s).should_have_member "bar" - s:delete ("bar") + Set.delete (s, "bar") expect (s).should_not_have_member "bar" - it works with an empty set: expect (Set.delete (Set {}, "quux")).should_equal (Set {}) @@ -70,20 +48,20 @@ specify Set: r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz", "quux"} - - context when called as a set object method: + - context when called as a Set module function: - it returns a set object: - expect (Object.type (r:difference (s))).should_be "Set" + expect (prototype (Set.difference (r, s))).should_be "Set" - it is non-destructive: - r:difference (s) + Set.difference (r, s) expect (r).should_equal (Set {"foo", "bar", "baz"}) expect (s).should_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members of the first that are not in the second: - expect (r:difference (s)).should_equal (Set {"foo"}) + expect (Set.difference (r, s)).should_equal (Set {"foo"}) - it coerces a table argument to a set: - expect (r:difference {"bar"}).should_equal (Set {"baz", "foo"}) + expect (Set.difference (r, {"bar"})).should_equal (Set {"baz", "foo"}) - context when called as a set metamethod: - it returns a set object: - expect (Object.type (r - s)).should_be "Set" + expect (prototype (r - s)).should_be "Set" - it is non-destructive: q = r - s expect (r).should_equal (Set {"foo", "bar", "baz"}) @@ -98,37 +76,57 @@ specify Set: - before: s = Set {"foo", "bar", "baz"} - - context when called as a set object method: + - context when called as a Set module function: - it is an iterator over set members: t = {} - for e in s:elems () do table.insert (t, e) end + for e in Set.elems (s) do table.insert (t, e) end table.sort (t) expect (t).should_equal {"bar", "baz", "foo"} - it works for an empty set: t = {} - for e in (Set {}):elems () do table.insert (t, e) end + for e in Set.elems (Set {}) do table.insert (t, e) end expect (t).should_equal {} - describe insert: - - context when called as a set object method: + - context when called as a Set module function: - before: s = Set {"foo"} - it returns a set object: - expect (Object.type (s:insert ("bar"))).should_be "Set" + expect (prototype (Set.insert (s, "bar"))).should_be "Set" - it is destructive: - s:insert ("bar") + Set.insert (s, "bar") expect (s).should_have_member "bar" - it returns the modified set: - expect (s:insert ("baz")).should_have_member "baz" + expect (Set.insert (s, "baz")).should_have_member "baz" + - it ignores insertion of existing members: + expect (Set.insert (s, "foo")).should_equal (Set {"foo"}) + - it inserts a new member into the set: + expect (s).should_not_have_member "bar" + Set.insert (s, "bar") + expect (s).should_have_member "bar" + - it works with an empty set: + expect (Set.insert (Set {}, "foo")).should_equal (s) + - context when called as a set metamethod: + - before: + s = Set {"foo"} + - it returns a set object: + s["bar"] = true + expect (prototype (s)).should_be "Set" + - it is destructive: + s["bar"] = true + expect (s).should_have_member "bar" - it ignores insertion of existing members: - expect (s:insert ("foo")).should_equal (Set {"foo"}) + s["foo"] = true + expect (s).should_equal (Set {"foo"}) - it inserts a new member into the set: expect (s).should_not_have_member "bar" - s:insert ("bar") + s["bar"] = true expect (s).should_have_member "bar" - it works with an empty set: - expect ((Set {}):insert ("foo")).should_equal (s) + s = Set {} + s.foo = true + expect (s).should_equal (s) - describe intersection: @@ -136,22 +134,23 @@ specify Set: r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz", "quux"} - - context when called as a set object method: + - context when called as a Set module function: - it returns a set object: - expect (Object.type (r:intersection (s))).should_be "Set" + expect (prototype (Set.intersection (r, s))).should_be "Set" - it is non-destructive: - r:intersection (s) + Set.intersection (r, s) expect (r).should_equal (Set {"foo", "bar", "baz"}) expect (s).should_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members common to both arguments: - expect (r:intersection (s)). + expect (Set.intersection (r, s)). should_equal (Set {"bar", "baz"}) - it coerces a table argument to a set: - expect (r:intersection ({"bar", "quux"})). + expect (Set.intersection (r, {"bar", "quux"})). should_equal (Set {"bar"}) - context when called as a set metamethod: - it returns a set object: - expect (Object.type (r:intersection (r))).should_be "Set" + q = r * s + expect (prototype (q)).should_be "Set" - it is non-destructive: q = r * s expect (r).should_equal (Set {"foo", "bar", "baz"}) @@ -165,32 +164,41 @@ specify Set: - describe member: - before: s = Set {"foo", "bar"} - - context when called as a set object method: + - context when called as a Set module function: + - it succeeds when set contains the given member: + expect (Set.member (s, "foo")).should_be (true) + - it fails when set does not contain the given member: + expect (Set.member (s, "baz")).should_not_be (true) + - it works with the empty set: + s = Set {} + expect (Set.member (s, "foo")).should_not_be (true) + - context when called as a set metamethod: - it succeeds when set contains the given member: - expect (s:member ("foo")).should_be (true) + expect (s["foo"]).should_be (true) - it fails when set does not contain the given member: - expect (s:member ("baz")).should_be (false) + expect (s["baz"]).should_not_be (true) - it works with the empty set: - expect ((Set {}):member ("foo")).should_be (false) + s = Set {} + expect (s["foo"]).should_not_be (true) -- describe propersubset: +- describe proper_subset: - before: r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz"} - - context when called as a set object method: + - context when called as a Set module function: - it succeeds when set contains all elements of another: - expect (s:propersubset (r)).should_be (true) + expect (Set.proper_subset (s, r)).should_be (true) - it fails when two sets are equal: r = s {} - expect (s:propersubset (r)).should_be (false) + expect (Set.proper_subset (s, r)).should_be (false) - it fails when set does not contain all elements of another: s = s + Set {"quux"} - expect (r:propersubset (s)).should_be (false) + expect (Set.proper_subset (r, s)).should_be (false) - it coerces a table argument to a set: - expect (s:propersubset {"foo", "bar", "baz"}).should_be (true) - expect (s:propersubset {"foo"}).should_be (false) + expect (Set.proper_subset (s, {"foo", "bar", "baz"})).should_be (true) + expect (Set.proper_subset (s, {"foo"})).should_be (false) - context when called as a set metamethod: - it succeeds when set contains all elements of another: expect (s < r).should_be (true) @@ -207,18 +215,18 @@ specify Set: r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz"} - - context when called as a set object method: + - context when called as a Set module function: - it succeeds when set contains all elements of another: - expect (s:subset (r)).should_be (true) + expect (Set.subset (s, r)).should_be (true) - it succeeds when two sets are equal: r = s {} - expect (s:subset (r)).should_be (true) + expect (Set.subset (s, r)).should_be (true) - it fails when set does not contain all elements of another: s = s + Set {"quux"} - expect (r:subset (s)).should_be (false) + expect (Set.subset (r, s)).should_be (false) - it coerces a table argument to a set: - expect (s:subset {"foo", "bar", "baz"}).should_be (true) - expect (s:subset {"foo"}).should_be (false) + expect (Set.subset (s, {"foo", "bar", "baz"})).should_be (true) + expect (Set.subset (s, {"foo"})).should_be (false) - context when called as a set metamethod: - it succeeds when set contains all elements of another: expect (s <= r).should_be (true) @@ -235,23 +243,23 @@ specify Set: r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz", "quux"} - - context when called as a set object method: + - context when called as a Set module function: - it returns a set object: - expect (Object.type (r:symmetric_difference (s))). + expect (prototype (Set.symmetric_difference (r, s))). should_be "Set" - it is non-destructive: - r:symmetric_difference (s) + Set.symmetric_difference (r, s) expect (r).should_equal (Set {"foo", "bar", "baz"}) expect (s).should_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: - expect (r:symmetric_difference (s)). + expect (Set.symmetric_difference (r, s)). should_equal (Set {"foo", "quux"}) - it coerces a table argument to a set: - expect (r:symmetric_difference {"bar"}). + expect (Set.symmetric_difference (r, {"bar"})). should_equal (Set {"baz", "foo"}) - context when called as a set metamethod: - it returns a set object: - expect (Object.type (r / s)).should_be "Set" + expect (prototype (r / s)).should_be "Set" - it is non-destructive: q = r / s expect (r).should_equal (Set {"foo", "bar", "baz"}) @@ -267,22 +275,22 @@ specify Set: r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz", "quux"} - - context when called as a set object method: + - context when called as a Set module function: - it returns a set object: - expect (Object.type (r:union (s))).should_be "Set" + expect (prototype (Set.union (r, s))).should_be "Set" - it is non-destructive: - r:union (s) + Set.union (r, s) expect (r).should_equal (Set {"foo", "bar", "baz"}) expect (s).should_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: - expect (r:union (s)). + expect (Set.union (r, s)). should_equal (Set {"foo", "bar", "baz", "quux"}) - it coerces a table argument to a set: - expect (r:union {"quux"}). + expect (Set.union (r, {"quux"})). should_equal (Set {"foo", "bar", "baz", "quux"}) - context when called as a set metamethod: - it returns a set object: - expect (Object.type (r + s)).should_be "Set" + expect (prototype (r + s)).should_be "Set" - it is non-destructive: q = r + s expect (r).should_equal (Set {"foo", "bar", "baz"}) @@ -299,7 +307,7 @@ specify Set: s = Set {"foo", "bar", "baz"} - it returns a table: - expect (Object.type (totable (s))).should_be "table" + expect (prototype (totable (s))).should_be "table" - it contains all non-hidden fields of object: expect (totable (s)).should_contain.all_of {"foo", "bar", "baz"} - it contains fields of set in order: From d861bc8954cc47ecedfbfec4709a32706541a951 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Dec 2013 01:22:21 +1300 Subject: [PATCH 030/703] refactor: automatically fill method functions form _functions table. * lib/std/container.lua (metatable.__call): Copy entries from newly passed _functions table to object. * lib/std/set.lua (Set): Delete manual copy of method functions. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 11 +++++++++-- lib/std/set.lua | 14 +------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 627d0cb..ea46bf1 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -57,6 +57,7 @@ local base = require "std.base" +local func = require "std.functional" --- Return the named entry from x's metatable. @@ -128,9 +129,10 @@ local metatable = { -- @see std.object:__call __call = function (self, ...) local mt = getmetatable (self) - local fn = mt._functions or {} - -- Make a shallow copy of prototype, skipping _functions. + -- Make a shallow copy of prototype, skipping metatable + -- _functions. + local fn = mt._functions or {} local obj = filter (self, function (e) return not fn[e] end) -- Map arguments according to _init metamethod. @@ -149,6 +151,11 @@ local metatable = { end end + -- However, newly passed _functions from _init arguments are + -- copied as prototype functions into the object. + func.map (function (k) obj[k] = obj_mt._functions[k] end, + pairs, obj_mt._functions or {}) + -- _functions is not propagated from prototype to clone. if next (obj_mt) == nil and mt._functions == nil then -- Reuse metatable if possible diff --git a/lib/std/set.lua b/lib/std/set.lua index dbd2395..96bacfc 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -2,7 +2,7 @@ Set container. Derived from @{std.container}, and inherits Container's metamethods. - + Note that Functions listed below are available only available from the Set prototype returned by requiring this module, because Container objects cannot have object methods. @@ -170,7 +170,6 @@ function equal (set1, set2) return subset (set1, set2) and subset (set2, set1) end - --- Set prototype object. -- @table std.set -- @string[opt="Set"] _type type of Set, returned by @@ -189,17 +188,6 @@ Set = Container { return self end, - delete = delete, - difference = difference, - elems = elems, - equal = equal, - insert = insert, - intersection = intersection, - member = member, - proper_subset = proper_subset, - subset = subset, - symmetric_difference = symmetric_difference, - union = union, --- Union operator. -- union = set + table From 2c35d2c90bd721ef402c83811739835873bf30b9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Dec 2013 14:05:48 +1300 Subject: [PATCH 031/703] specs: improve expectation description. * specs/set_spec.yaml: Write "shows the type name" instead of "contains the type". Signed-off-by: Gary V. Vaughan --- specs/set_spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index bfc22b0..4e7f931 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -322,7 +322,7 @@ specify Set: - it returns a string: expect (type (tostring (s))).should_be "string" - - it contains the type: + - it shows the type name: expect (tostring (s)).should_contain "Set" - it contains the ordered set elements: expect (tostring (s)).should_contain "bar, baz, foo" From 80d9b64917d4d34750c147e32c1ad170abe5c74c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Dec 2013 14:12:56 +1300 Subject: [PATCH 032/703] doc: update object constructor _function documentation. * lib/std/container.lua: Update module doc-comment to reflect simplification of passing module functions in _functions table. (std.container): Update _function description. * lib/std/object.lua (std.object): Likewise. * lib/std/set.lua (std.set): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 26 ++++++++++++++------------ lib/std/object.lua | 4 ++-- lib/std/set.lua | 4 ++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index ea46bf1..5104569 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -29,18 +29,20 @@ ... To add functions like this to your own prototype objects, pass a table - whose keys are the names of the module functions, to the `_functions` - private field before cloning, and the named functions will not be - inherited by clones. + of the module functions in the `_functions` private field before + cloning, and those functions will not be inherited by clones. > Container = require "std.container" - > Graph = Container { _type = "Graph" } - > Graph.nodes = function (graph) - >> local n = 0 - >> for _ in pairs (graph) do n = n + 1 end - >> return n - >> end - > Graph._functions = { nodes = Graph.nodes } + > Graph = Container { + >> _type = "Graph", + >> _functions = { + >> nodes = function (graph) + >> local n = 0 + >> for _ in pairs (graph) do n = n + 1 end + >> return n + >> end, + >> }, + >> } > g = Graph { "node1", "node2" } > = Graph.nodes (g) 2 @@ -114,8 +116,8 @@ local functions = { -- @{std.object.prototype} -- @tfield table|function _init a table of field names, or -- initialisation function, used by @{__call} --- @tfield nil|table|std.set _functions a table whose keys are the names --- of module functions not copied by @{__call} +-- @tfield nil|table _functions a table of module functions not copied +-- by @{std.object.__call} local metatable = { _type = "Container", _init = {}, diff --git a/lib/std/object.lua b/lib/std/object.lua index 6172cae..355f2b7 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -69,8 +69,8 @@ local metamethod = (require "std.functional").metamethod -- @string[opt="Object"] _type type of Object, returned by @{prototype} -- @tfield table|function _init a table of field names, or -- initialisation function, used by @{clone} --- @tfield nil|table|std.set _functions a table whose keys are the names --- of module functions not copied by @{clone} +-- @tfield nil|table _functions a table of module functions not copied +-- by @{std.object.__call} return Container { _type = "Object", diff --git a/lib/std/set.lua b/lib/std/set.lua index 96bacfc..2eb032c 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -176,8 +176,8 @@ end -- @{std.object.prototype} -- @tfield table|function _init a table of field names, or -- initialisation function, see @{std.object.__call} --- @tfield nil|table|set _functions a table whose keys are the names --- of module functions not copied by @{std.object.__call} +-- @tfield nil|table _functions a table of module functions not copied +-- by @{std.object.__call} Set = Container { _type = "Set", From a1b7ad44003247604680e325a153eddee20b784e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Dec 2013 14:32:50 +1300 Subject: [PATCH 033/703] tree: derive from std.container. * specs/tree_spec.yaml: Replace object methods with module functions throughout. Add specs for inheritted functionality. * lib/std/tree.lua: Rewrite as a derivative of std.container. Change object methods to Container style module functions. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 6 +- lib/std/tree.lua | 179 ++++++++++++++++++++----------------- specs/tree_spec.yaml | 204 +++++++++++++++++++++++++------------------ 3 files changed, 218 insertions(+), 171 deletions(-) diff --git a/NEWS b/NEWS index d470483..0e04037 100644 --- a/NEWS +++ b/NEWS @@ -32,9 +32,9 @@ Stdlib NEWS - User visible changes (which do have object methods, but) which cannot use the __index metamethod for "[]" access to object contents. - - set is now derived from `std.container`, so there are no object - methods. Instead there are a full complement of equivalent prototype - functions. Metamethods continue to work as before. + - 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`. diff --git a/lib/std/tree.lua b/lib/std/tree.lua index d9792f5..f1c2d28 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -1,81 +1,50 @@ --[[-- - Tables as trees. - @module std.tree + Tree container. + + Derived from @{std.container}, and inherits Container's metamethods. + + Note that Functions listed below are only available from the Tree + prototype return by requiring this module, because Container objects + cannot have object methods. + + @classmod std.tree + @see std.container ]] -local base = require "std.base" -local list = require "std.list" -local func = require "std.functional" +local base = require "std.base" +local Container = require "std.container" +local list = require "std.list" +local func = require "std.functional" + +local prototype = (require "std.object").prototype -local metatable = {} +local Tree -- forward declaration --- Tree iterator which returns just numbered leaves, in order. -- @function ileaves --- @tparam std.tree tr tree table --- @treturn function iterator function --- @treturn std.tree the tree `tr` +-- @static +-- @tparam tree|table tr tree or tree-like table +-- @treturn function iterator function +-- @treturn tree|table the tree `tr` local ileaves = base.ileaves --- Tree iterator which returns just leaves. -- @function leaves --- @tparam std.tree tr tree table --- @treturn function iterator function --- @treturn std.tree the tree, `tr` +-- @static +-- @tparam tree|table tr tree or tree-like table +-- @treturn function iterator function +-- @treturn tree|table the tree, `tr` local leaves = base.leaves ---- Make a table into a tree. --- @tparam table t any table --- @treturn std.tree a new tree table -local function new (t) - return setmetatable (t or {}, metatable) -end - - ---- Tree `__index` metamethod. --- @function __index --- @param i non-table, or list of keys `{i\_1 ... i\_n}` --- @return `tr[i]...[i\_n]` if i is a table, or `tr[i]` otherwise --- @todo the following doesn't treat list keys correctly --- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? -function metatable.__index (self, i) - if type (i) == "table" and #i > 0 then - return list.foldl (i, func.op["[]"], self) - else - return rawget (self, i) - end -end - - ---- Tree `__newindex` metamethod. --- --- Sets `tr[i\_1]...[i\_n] = v` if i is a table, or `tr[i] = v` otherwise --- @function __newindex --- @param i non-table, or list of keys `{i\_1 ... i\_n}` --- @param v value -function metatable.__newindex (self, i, v) - if type (i) == "table" then - for n = 1, #i - 1 do - if getmetatable (self[i[n]]) ~= metatable then - rawset (self, i[n], new ()) - end - self = self[i[n]] - end - rawset (self, i[#i], v) - else - rawset (self, i, v) - end -end - - --- Make a deep copy of a tree, including any metatables. -- -- To make fast shallow copies, use @{std.table.clone}. --- @tparam table t table to be cloned +-- @tparam table|tree t table or tree to be cloned -- @tparam boolean nometa if non-nil don't copy metatables --- @treturn table a deep copy of `t` +-- @treturn table|tree a deep copy of `t` local function clone (t, nometa) assert (type (t) == "table", "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") @@ -108,7 +77,7 @@ end --- Tree iterator. -- @tparam function it iterator function --- @tparam std.tree tr tree +-- @tparam tree|table tr tree or tree-like table -- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order)) -- @treturn table path to node ({i\_1...i\_k}) -- @return node @@ -139,7 +108,7 @@ end -- list of keys used to reach this node, and `tree-node` is the current -- node. -- --- Given a `std.tree` to represent: +-- Given a `tree` to represent: -- -- + root -- +-- node1 @@ -163,9 +132,9 @@ end -- you must `table.clone` a copy if you want to take a snap-shot of the -- current state of the `tree-path` list before the next iteration -- changes it. --- @tparam std.tree tr tree to iterate over --- @treturn function iterator function --- @treturn std.tree the tree, `tr` +-- @tparam tree|table tr tree or tree-like table to iterate over +-- @treturn function iterator function +-- @treturn tree|table the tree, `tr` -- @see inodes local function nodes (tr) assert (type (tr) == "table", @@ -178,9 +147,9 @@ end -- -- The iterator function behaves like @{nodes}, but only traverses the -- array part of the nodes of `tr`, ignoring any others. --- @tparam std.tree tr tree to iterate over --- @treturn function iterator function --- @treturn std.tree the tree, `t` +-- @tparam tree|table tr tree to iterate over +-- @treturn function iterator function +-- @treturn tree|table the tree, `tr` -- @see nodes local function inodes (tr) assert (type (tr) == "table", @@ -190,15 +159,15 @@ end --- Destructively deep-merge one tree into another. --- @tparam std.tree t destination tree --- @tparam std.tree u tree with nodes to merge --- @treturn std.tree `t` with nodes from `u` merged in +-- @tparam tree|table t destination tree or table +-- @tparam tree|table u tree or table with nodes to merge +-- @treturn tree|table `t` with nodes from `u` merged in -- @see std.table.merge local function merge (t, u) - assert (getmetatable (t) == metatable, - "bad argument #1 to 'merge' (tree table expected, got " .. type (t) .. ")") - assert (getmetatable (u) == metatable, - "bad argument #2 to 'merge' (tree table expected, got " .. type (u) .. ")") + assert (type (t) == "table", + "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") + assert (type (u) == "table", + "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") for ty, p, n in nodes (u) do if ty == "leaf" then t[p] = n @@ -208,15 +177,61 @@ local function merge (t, u) end ---- @export -local Tree = { - clone = clone, - ileaves = ileaves, - inodes = inodes, - leaves = leaves, - merge = merge, - new = new, - nodes = nodes, +--- Tree prototype object. +-- @table std.tree +-- @string[opt="Tree"] _type type of Tree, returned by +-- @{std.object.prototype} +-- @tfield[opt={}] table|function _init a table of field names, or +-- initialisation function, see @{std.object.__call} +-- @tfield nil|table _functions a table of module functions not copied +-- by @{std.object.__call} +Tree = Container { + -- Derived object type. + _type = "Tree", + + --- Tree `__index` metamethod. + -- @function __index + -- @param i non-table, or list of keys `{i\_1 ... i\_n}` + -- @return `self[i]...[i\_n]` if i is a table, or `self[i]` otherwise + -- @todo the following doesn't treat list keys correctly + -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? + __index = function (self, i) + if type (i) == "table" and #i > 0 then + return list.foldl (i, func.op["[]"], self) + else + return rawget (self, i) + end + end, + + --- Tree `__newindex` metamethod. + -- + -- Sets `self[i\_1]...[i\_n] = v` if i is a table, or `self[i] = v` otherwise + -- @function __newindex + -- @param i non-table, or list of keys `{i\_1 ... i\_n}` + -- @param v value + __newindex = function (self, i, v) + if type (i) == "table" then + for n = 1, #i - 1 do + if prototype (self[i[n]]) ~= "Tree" then + rawset (self, i[n], Tree {}) + end + self = self[i[n]] + end + rawset (self, i[#i], v) + else + rawset (self, i, v) + end + end, + + --- @export + _functions = { + clone = clone, + ileaves = ileaves, + inodes = inodes, + leaves = leaves, + merge = merge, + nodes = nodes, + }, } return Tree diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 4b3e664..c0520da 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -1,69 +1,35 @@ -specify tree: -- before: | - M = require "std.tree" - - tr = M.new {foo="foo", fnord=M.new {branch=M.new {bar="bar", baz="baz"}}, quux="quux"} - mt = getmetatable (tr) +before: + Tree = require "std.tree" + prototype = (require "std.object").prototype + totable = (require "std.table").totable + t = {foo="foo", fnord={branch={bar="bar", baz="baz"}}, quux="quux"} + tr = Tree (t) -- describe new: - - before: - f = M.new - - it returns a new tree when nil is passed: - expect (f (nil)).should_equal {} - - it turns any table passed into a tree: - t = { "unique table" } - expect (f (t)).should_be (t) - expect (getmetatable (f (t))).should_be (mt) +specify tree: +- describe construction: + - it constructs a new tree: + tr = Tree {} + expect (tr).should_not_be (Tree) + expect (prototype (tr)).should_be "Tree" + - it turns a table argument into a tree: + expect (prototype (Tree (tr))).should_be "Tree" + - it does not turn table argument values into sub-Trees: + expect (prototype (tr["fnord"])).should_be "table" - it understands branched nodes: - t = f {foo=f {branch=f {bar="leaf"}}} - expect (t).should_equal {foo={branch={bar="leaf"}}} - expect (t["foo"]).should_equal {branch={bar="leaf"}} - expect (getmetatable(t["foo"])).should_be (mt) - expect (t[{"foo", "branch", "bar"}]).should_equal "leaf" - - -- describe __index: - - it returns nil for a missing key: - expect (tr["no such key"]).should_be (nil) - - it returns nil for missing single element key lists: - expect (tr[{"no such key"}]).should_be (nil) - - it returns nil for missing multi-element key lists: | - expect (tr[{"fnord", "foo"}]).should_be (nil) - pending "see issue #39" - expect (tr[{"no", "such", "key"}]).should_be (nil) - - it returns a value for the given key: - expect (tr["foo"]).should_be "foo" - expect (tr["quux"]).should_be "quux" - - it returns values for single element key lists: - expect (tr[{"foo"}]).should_be "foo" - expect (tr[{"quux"}]).should_be "quux" - - it returns values for multi-element key lists: - expect (tr[{"fnord", "branch", "bar"}]).should_be "bar" - expect (tr[{"fnord", "branch", "baz"}]).should_be "baz" - - -- describe __newindex: - - before: - tr = M.new {} - - it stores values for simple keys: - tr["foo"] = "foo" - expect (tr).should_equal {foo="foo"} - - it stores values for single element key lists: - tr[{"foo"}] = "foo" - expect (tr).should_equal {foo="foo"} - - it stores values for multi-element key lists: - tr[{"foo", "bar"}] = "baz" - expect (tr).should_equal {foo={bar="baz"}} - - it separates branches for diverging key lists: - tr[{"foo", "branch", "bar"}] = "leaf1" - tr[{"foo", "branch", "baz"}] = "leaf2" - expect (tr).should_equal {foo={branch={bar="leaf1", baz="leaf2"}}} + expect (tr).should_equal (Tree (t)) + expect (tr[{"fnord"}]).should_equal (t.fnord) + expect (tr[{"fnord", "branch", "bar"}]).should_equal (t.fnord.branch.bar) + - it serves as a prototype for new instances: + obj = tr {} + expect (prototype (obj)).should_be "Tree" + expect (obj).should_equal (tr) + expect (getmetatable (obj)).should_be (getmetatable (tr)) - describe clone: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } - f = M.clone + f = Tree.clone - it does not just return the subject: expect (f (subject)).should_not_be (subject) - it does copy the subject: @@ -82,7 +48,7 @@ specify tree: - describe ileaves: - before: - f = M.ileaves + f = Tree.ileaves l = {} - it iterates over array part of a table argument: for v in f {"first", "second", "3rd"} do l[1+#l]=v end @@ -101,9 +67,9 @@ specify tree: end expect (l).should_equal {"one", "three", "five"} - it works on trees too: - for v in f (M.new {M.new {"one", - M.new {two=2}, - M.new {M.new {"three"}, four=4} + for v in f (Tree {Tree {"one", + Tree {two=2}, + Tree {Tree {"three"}, four=4} }, foo="bar", "five"}) do @@ -117,13 +83,13 @@ specify tree: - describe inodes: - before: | - f = M.inodes + f = Tree.inodes local tostring = (require "std.string").tostring function traverse (subject) l = {} for ty, p, n in f (subject) do - l[1+#l]={ty, M.clone (p), n} + l[1+#l]={ty, Tree.clone (p), n} end return l end @@ -177,9 +143,9 @@ specify tree: {"leaf", {2}, subject[2]}, -- five, {"join", {}, subject}} -- } - it works on trees too: | - subject = M.new {M.new {"one", - M.new {two=2}, - M.new {M.new {"three"}, four=4}}, + subject = Tree {Tree {"one", + Tree {two=2}, + Tree {Tree {"three"}, four=4}}, foo="bar", "five"} expect (traverse (subject)). @@ -203,7 +169,7 @@ specify tree: - describe leaves: - before: - f = M.leaves + f = Tree.leaves l = {} - it iterates over elements of a table argument: for v in f {"first", "second", "3rd"} do l[1+#l]=v end @@ -222,9 +188,9 @@ specify tree: end expect (l).should_contain.all_of {"one", 2, "three", 4, "bar", "five"} - it works on trees too: - for v in f (M.new {M.new {"one", - M.new {two=2}, - M.new {M.new {"three"}, four=4} + for v in f (Tree {Tree {"one", + Tree {two=2}, + Tree {Tree {"three"}, four=4} }, foo="bar", "five"}) do @@ -238,27 +204,27 @@ specify tree: - describe merge: - before: | - f = M.merge + f = Tree.merge -- Additional merge keys which are moderately unusual - t1 = M.new { k1 = "v1", k2 = "if", k3 = M.new {"?"} } - t2 = M.new { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = "v2" } + t1 = Tree { k1 = "v1", k2 = "if", k3 = Tree {"?"} } + t2 = Tree { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = "v2" } - target = M.clone (t1) - for ty, p, n in M.nodes (t2) do + target = Tree.clone (t1) + for ty, p, n in Tree.nodes (t2) do if ty == "leaf" then target[p] = n end end - it does not create a whole new table: expect (f (t1, t2)).should_be (t1) - it does not change t1 when t2 is empty: - expect (f (t1, M.new {})).should_be (t1) + expect (f (t1, Tree {})).should_be (t1) - it copies t2 when t1 is empty: | - expect (f (M.new {}, t1)).should_not_be (t1) - expect (f (M.new {}, t1)).should_equal (t1) + expect (f (Tree {}, t1)).should_not_be (t1) + expect (f (Tree {}, t1)).should_equal (t1) - it merges keys from t2 into t1: | expect (f (t1, t2)).should_equal (target) - it gives precedence to values from t2: - original = M.clone (t1) + original = Tree.clone (t1) m = f (t1, t2) -- Merge is destructive, do it once only. expect (m.k3).should_be (t2.k3) expect (m.k3).should_not_be (original.k3) @@ -269,11 +235,11 @@ specify tree: - describe nodes: - before: - f = M.nodes + f = Tree.nodes function traverse (subject) l = {} - for ty, p, n in f (subject) do l[1+#l]={ty, M.clone (p), n} end + for ty, p, n in f (subject) do l[1+#l]={ty, Tree.clone (p), n} end return l end - it iterates over the elements of a table argument: | @@ -339,9 +305,9 @@ specify tree: -- like `pairs`, `nodes` can visit elements in any order, so we cannot -- guarantee the array part is always visited before the hash part, or -- even that the array elements are visited in order! - subject = M.new {M.new {"one", - M.new {two=2}, - M.new {M.new {"three"}, four=4}}, + subject = Tree {Tree {"one", + Tree {two=2}, + Tree {Tree {"three"}, four=4}}, foo="bar", "five"} expect (traverse (subject)).should_contain. @@ -361,6 +327,72 @@ specify tree: {"leaf", {2}, subject[2]}, -- five, {"leaf", {"foo"}, subject["foo"]}, -- bar, {"join", {}, subject}} -- } + - it generates path key-lists that are valid __index arguments: | + pending "std.tree.__index handling empty list keys" + subject = Tree {"first", Tree {"second"}, "3rd"} + expect (traverse (subject)). + should_equal {{"branch", {}, subject[{}]}, -- { + {"leaf", {1}, subject[{1}]}, -- first, + {"branch", {2}, subject[{2}]}, -- { + {"leaf", {2,1}, subject[{2,1}]}, -- second + {"join", {2}, subject[{2}]}, -- } + {"leaf", {3}, subject[{3}]}, -- 3rd, + {"join", {}, subject[{}]}} -- } - it diagnoses non-table arguments: expect (f ()).should_error ("table expected") expect (f "string").should_error ("table expected") + + +- describe __index: + - it returns nil for a missing key: + expect (tr["no such key"]).should_be (nil) + - it returns nil for missing single element key lists: + expect (tr[{"no such key"}]).should_be (nil) + - it returns nil for missing multi-element key lists: | + expect (tr[{"fnord", "foo"}]).should_be (nil) + pending "see issue #39" + expect (tr[{"no", "such", "key"}]).should_be (nil) + - it returns a value for the given key: + expect (tr["foo"]).should_be "foo" + expect (tr["quux"]).should_be "quux" + - it returns values for single element key lists: + expect (tr[{"foo"}]).should_be "foo" + expect (tr[{"quux"}]).should_be "quux" + - it returns values for multi-element key lists: + expect (tr[{"fnord", "branch", "bar"}]).should_be "bar" + expect (tr[{"fnord", "branch", "baz"}]).should_be "baz" + + +- describe __newindex: + - before: + tr = Tree {} + - it stores values for simple keys: + tr["foo"] = "foo" + expect (tr).should_equal (Tree {foo="foo"}) + - it stores values for single element key lists: + tr[{"foo"}] = "foo" + expect (tr).should_equal (Tree {foo="foo"}) + - it stores values for multi-element key lists: + tr[{"foo", "bar"}] = "baz" + expect (tr).should_equal (Tree {foo=Tree {bar="baz"}}) + - it separates branches for diverging key lists: + tr[{"foo", "branch", "bar"}] = "leaf1" + tr[{"foo", "branch", "baz"}] = "leaf2" + expect (tr).should_equal (Tree {foo=Tree {branch=Tree {bar="leaf1", baz="leaf2"}}}) + +- describe __totable: + - it returns a table: + expect (prototype (totable (tr))).should_be "table" + - it contains all non-hidden fields of object: + expect (totable (tr)).should_contain. + all_of {"foo", branch={bar="bar", baz="baz"}, "quux"} + +- describe __tostring: + - it returns a string: + expect (prototype (tostring (tr))).should_be "string" + - it shows the type name: + expect (tostring (tr)).should_contain "Tree" + - it shows the contents in order: | + pending "see issue #44" + expect (tostring (tr)). + should_contain 'fnord={branch={bar=bar, baz=baz}}, foo=foo, quux=quux' From 244be0a53b235c223e37bb59a67dc77df9faf22c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 1 Jan 2014 16:11:07 +1300 Subject: [PATCH 034/703] slingshot: sync with upstream. * slingshot: Sync with latest upstream, particularly for Travis fixes. * .travis.yml: Regenerate. Plus temporary patch to run gvvaughan/next branch of LDoc during integration until stevedonovan/master is fixed. Signed-off-by: Gary V. Vaughan --- .travis.yml | 21 +++++++++++---------- slingshot | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4aa54a0..1c9e043 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,14 +6,14 @@ env: - PACKAGE=stdlib - ROCKSPEC=$PACKAGE-git-1.rockspec - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - - LUAROCKS_BASE=luarocks-2.0.13 + - LUAROCKS_BASE=luarocks-2.1.1 - LUAROCKS="$LUA $HOME/bin/luarocks" - GENDOC=luarocks/bin/ldoc - SPECL=bin/specl matrix: - - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 - - LUA=lua5.2 LUA_INCDIR=/usr/include/lua5.2 - - LUA=luajit-2.0.0-beta9 LUA_INCDIR=/usr/include/luajit-2.0 + - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 LUA_SUFFIX=5.1 + - LUA=lua5.2 LUA_INCDIR=/usr/include/lua5.2 LUA_SUFFIX=5.2 + - LUA=luajit-2.0.0-beta9 LUA_INCDIR=/usr/include/luajit-2.0 LUA_SUFFIX=5.1 # Tool setup. install: @@ -24,11 +24,12 @@ install: - sudo apt-get install liblua5.1-dev - sudo apt-get install lua5.2 - sudo apt-get install liblua5.2-dev - # Put back the missing links for libyaml, which are recently missing otherwise - - sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; + # Put back the links for libyaml, which missing on recent Travis VMs + - test -f /usr/lib/libyaml.so || + sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; # Luadoc and Ldoc work best on Travis with Lua 5.1. - sudo apt-get install luarocks - - sudo luarocks install http://raw.github.com/stevedonovan/LDoc/master/ldoc-scm-2.rockspec + - sudo luarocks install http://raw.github.com/gvvaughan/LDoc/next/ldoc-next-1.rockspec - mkdir -p luarocks/bin - sed 's|^exec "[^"]*"|exec lua5.1|' `which ldoc` > $GENDOC - chmod a+rx $GENDOC @@ -37,8 +38,8 @@ install: - tar zxvpf $LUAROCKS_BASE.tar.gz - cd $LUAROCKS_BASE - ./configure - --prefix=$HOME --lua-version=5.1 --lua-suffix=5.1 - --with-lua-include="/usr/include/lua5.1" + --prefix=$HOME --lua-version=$LUA_SUFFIX --lua-suffix=$LUA_SUFFIX + --with-lua-include=$LUA_INCDIR - make all install - cd .. @@ -59,4 +60,4 @@ script: - $LUAROCKS make $ROCKSPEC LUA="$LUA" # Use bin/specl if we built it, or else the specl rock we just installed. - test -f "$SPECL" || SPECL=luarocks/bin/specl; - make check SPECL="$SPECL" V=1 + LUA_PATH=`pwd`'/lib/?.lua;'"${LUA_PATH-;}" make check SPECL="$SPECL" V=1 diff --git a/slingshot b/slingshot index 78bc2a8..9136047 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 78bc2a8173910b0d8bd4ecdff1acff612629b335 +Subproject commit 91360471c71db1ec923b0c9bdc3bd78bc07ca84e From 2384ced1347c8cadcda1e187308638f777d93779 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 2 Jan 2014 15:42:43 +1300 Subject: [PATCH 035/703] slingshot: sync with upstream, and update copyright years. * slingshot: Update, particularly for update-copyright support. * bootstrap, bootstrap.slingshot: Update from slingshot. Signed-off-by: Gary V. Vaughan --- bootstrap | 434 +++++++++++++++++++++++++++++++++----------- bootstrap.slingshot | 2 +- slingshot | 2 +- 3 files changed, 333 insertions(+), 105 deletions(-) diff --git a/bootstrap b/bootstrap index fec6f33..18323eb 100755 --- a/bootstrap +++ b/bootstrap @@ -3,18 +3,18 @@ # Bootstrap an Autotooled package from checked-out sources. # Written by Gary V. Vaughan, 2010 -# Copyright (C) 2010-2013 Free Software Foundation, Inc. +# Copyright (C) 2010-2014 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # Source required external libraries: # Set a version string for this script. -scriptversion=2013-08-23.20; # UTC +scriptversion=2013-10-28.05; # UTC # General shell script boiler plate, and helper functions. # Written by Gary V. Vaughan, 2004 -# Copyright (C) 2004-2013 Free Software Foundation, Inc. +# Copyright (C) 2004-2014 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -94,6 +94,75 @@ nl=' ' IFS="$sp $nl" +# There are apparently some retarded systems that use ';' as a PATH separator! +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + + +## ------------------------- ## +## Locate command utilities. ## +## ------------------------- ## + + +# func_executable_p FILE +# ---------------------- +# Check that FILE is an executable regular file. +func_executable_p () +{ + test -f "$1" && test -x "$1" +} + + +# func_path_progs PROGS_LIST CHECK_FUNC [PATH] +# -------------------------------------------- +# Search for either a program that responds to --version with output +# containing "GNU", or else returned by CHECK_FUNC otherwise, by +# trying all the directories in PATH with each of the elements of +# PROGS_LIST. +# +# CHECK_FUNC should accept the path to a candidate program, and +# set $func_check_prog_result if it truncates its output less than +# $_G_path_prog_max characters. +func_path_progs () +{ + _G_progs_list=$1 + _G_check_func=$2 + _G_PATH=${3-"$PATH"} + + _G_path_prog_max=0 + _G_path_prog_found=false + _G_save_IFS=$IFS; IFS=$PATH_SEPARATOR + for _G_dir in $_G_PATH; do + IFS=$_G_save_IFS + test -z "$_G_dir" && _G_dir=. + for _G_prog_name in $_G_progs_list; do + for _exeext in '' .EXE; do + _G_path_prog=$_G_dir/$_G_prog_name$_exeext + func_executable_p "$_G_path_prog" || continue + case `"$_G_path_prog" --version 2>&1` in + *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;; + *) $_G_check_func $_G_path_prog + func_path_progs_result=$func_check_prog_result + ;; + esac + $_G_path_prog_found && break 3 + done + done + done + IFS=$_G_save_IFS + test -z "$func_path_progs_result" && { + echo "no acceptable sed could be found in \$PATH" >&2 + exit 1 + } +} + + # There are still modern systems that have problems with 'echo' mis- # handling backslashes, among others, so make sure $bs_echo is set to a # command that correctly interprets backslashes. @@ -135,6 +204,87 @@ else fi +# We want to be able to use the functions in this file before configure +# has figured out where the best binaries are kept, which means we have +# to search for them ourselves - except when the results are already set +# where we skip the searches. + +# Unless the user overrides by setting SED, search the path for either GNU +# sed, or the sed that truncates its output the least. +test -z "$SED" && { + _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for _G_i in 1 2 3 4 5 6 7; do + _G_sed_script=$_G_sed_script$nl$_G_sed_script + done + echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed + _G_sed_script= + + func_check_prog_sed () + { + _G_path_prog=$1 + + _G_count=0 + $bs_echo_n 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + $bs_echo '' >> conftest.nl + "$_G_path_prog" -f conftest.sed conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "sed gsed" func_check_prog_sed $PATH:/usr/xpg4/bin + SED=$func_path_progs_result +} + + +# Unless the user overrides by setting GREP, search the path for either GNU +# grep, or the grep that truncates its output the least. +test -z "$GREP" && { + func_check_prog_grep () + { + _G_path_prog=$1 + + _G_count=0 + _G_path_prog_max=0 + $bs_echo_n 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + $bs_echo 'GREP' >> conftest.nl + "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "grep ggrep" func_check_prog_grep $PATH:/usr/xpg4/bin + GREP=$func_path_progs_result +} + + ## ------------------------------- ## ## User overridable command paths. ## ## ------------------------------- ## @@ -146,15 +296,13 @@ fi : ${CP="cp -f"} : ${ECHO="$bs_echo"} -: ${EGREP="grep -E"} -: ${FGREP="grep -F"} -: ${GREP="grep"} +: ${EGREP="$GREP -E"} +: ${FGREP="$GREP -F"} : ${LN_S="ln -s"} : ${MAKE="make"} : ${MKDIR="mkdir"} : ${MV="mv -f"} : ${RM="rm -f"} -: ${SED="sed"} : ${SHELL="${CONFIG_SHELL-/bin/sh}"} @@ -592,11 +740,11 @@ func_echo_infix_1 () for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" do test -n "$_G_tc" && { - _G_esc_tc=`$bs_echo "$_G_tc" | sed "$sed_make_literal_regex"` - _G_indent=`$bs_echo "$_G_indent" | sed "s|$_G_esc_tc||g"` + _G_esc_tc=`$bs_echo "$_G_tc" | $SED "$sed_make_literal_regex"` + _G_indent=`$bs_echo "$_G_indent" | $SED "s|$_G_esc_tc||g"` } done - _G_indent="$progname: "`echo "$_G_indent" | sed 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes + _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes func_echo_infix_1_IFS=$IFS IFS=$nl @@ -1166,6 +1314,37 @@ func_warning () } +# func_sort_ver VER1 VER2 +# ----------------------- +# 'sort -V' is not generally available. +# Note this deviates from the version comparison in automake +# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a +# but this should suffice as we won't be specifying old +# version formats or redundant trailing .0 in bootstrap.conf. +# If we did want full compatibility then we should probably +# use m4_version_compare from autoconf. +func_sort_ver () +{ + $debug_cmd + + printf '%s\n%s\n' "$1" "$2" \ + | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n +} + +# func_lt_ver PREV CURR +# --------------------- +# Return true if PREV and CURR are in the correct order according to +# func_sort_ver, otherwise false. Use it like this: +# +# func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..." +func_lt_ver () +{ + $debug_cmd + + test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q` +} + + # Local variables: # mode: shell-script # sh-indentation: 2 @@ -1181,7 +1360,7 @@ scriptversion=2012-10-21.11; # UTC # A portable, pluggable option parser for Bourne shell. # Written by Gary V. Vaughan, 2010 -# Copyright (C) 2010-2013 Free Software Foundation, Inc. +# Copyright (C) 2010-2014 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -1786,7 +1965,7 @@ func_version () # Extract macro arguments from autotools input with GNU M4. # Written by Gary V. Vaughan, 2010 # -# Copyright (C) 2010-2013 Free Software Foundation, Inc. +# Copyright (C) 2010-2014 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -1851,7 +2030,7 @@ func_autoconf_configure () # If we were passed a genuine file, make sure it calls AC_INIT. test -f "$1" \ - && _G_ac_init=`$SED "$_G_sed_no_comment" "$1" |grep AC_INIT` + && _G_ac_init=`$SED "$_G_sed_no_comment" "$1" |$GREP AC_INIT` # Otherwise it is not a genuine Autoconf input file. test -n "$_G_ac_init" @@ -2203,7 +2382,7 @@ test extract-trace = "$progname" && func_main "$@" # End: # Set a version string for *this* script. -scriptversion=2013-08-29.21; # UTC +scriptversion=2014-01-01.22; # UTC # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -2290,13 +2469,9 @@ scriptversion=2013-08-29.21; # UTC : ${CMP="cmp"} : ${CONFIG_SHELL="/bin/sh"} : ${DIFF="diff"} -: ${EGREP="grep -E"} -: ${FGREP="grep -F"} : ${GIT="git"} -: ${GREP="grep"} : ${LN_S="ln -s"} : ${RM="rm"} -: ${SED="sed"} export ACLOCAL export AUTOCONF @@ -2461,10 +2636,6 @@ func_bootstrap () # Post-option preparation. func_prep - # Ensure ChangeLog presence. - func_ifcontains "$gnulib_modules" gitlog-to-changelog \ - func_ensure_changelog - # Reconfigure the package. func_reconfigure @@ -2509,6 +2680,10 @@ func_prep () $require_gnulib_merge_changelog + # Report the results of SED and GREP searches from funclib.sh. + func_verbose "GREP='$GREP'" + func_verbose "SED='$SED'" + # fetch update files from the translation project func_update_translations @@ -2545,6 +2720,19 @@ func_reconfigure () { $debug_cmd + # Ensure ChangeLog presence. + if test -n "$gnulib_modules"; then + func_ifcontains "$gnulib_modules" gitlog-to-changelog \ + func_ensure_changelog + else + $require_gnulib_cache + if $SED -n '/^gl_MODULES(\[/,/^])$/p' $gnulib_cache 2>/dev/null | + func_grep_q gitlog-to-changelog + then + func_ensure_changelog + fi + fi + # Released 'autopoint' has the tendency to install macros that have # been obsoleted in current 'gnulib., so run this before 'gnulib-tool'. func_autopoint @@ -2766,13 +2954,9 @@ func_gnulib_tool_copy_file () { $debug_cmd - $require_gnulib_path $require_gnulib_tool $require_patch - gnulib_copy_cmd="$gnulib_tool --copy-file" - $opt_copy || func_append gnulib_copy_cmd " --symlink" - if test true = "$gnulib_tool"; then # If gnulib-tool is not available (e.g. bootstrapping in a # distribution tarball), make sure that at least we have some @@ -2786,12 +2970,14 @@ or else specify the location of your 'git' binary by setting 'GIT' in the environment so that a fresh 'gnulib' submodule can be cloned." else - test -f "$gnulib_path/$1" || { + $require_gnulib_copy_cmd + + $gnulib_copy_cmd $1 $2 2>/dev/null || { + $require_gnulib_path + func_error "'$gnulib_path/$1' does not exist" return 1 } - - $gnulib_copy_cmd $1 $2 fi } @@ -2957,7 +3143,7 @@ func_clean_unused_macros () # We use 'ls|grep' instead of 'ls *.m4' to avoid exceeding # command line length limits in some shells. - for file in `cd "$macro_dir" && ls -1 |grep '\.m4$'`; do + for file in `cd "$macro_dir" && ls -1 |$GREP '\.m4$'`; do # Remove a macro file when aclocal.m4 does not m4_include it... func_grep_q 'm4_include([[]'$macro_dir/$file'])' $aclocal_m4s \ @@ -3275,7 +3461,7 @@ for tool in autoconf libtoolize autopoint; do '$tool' $_G_version http://www.gnu.org/s/'$b' " func_verbose \ - "auto-adding '\'$tool'-'$_G_version\'' to build requirements" + "auto-adding '\'$tool'-$_G_version'\'' to build requirements" } } @@ -3325,6 +3511,8 @@ func_require_buildreq_patch () { $debug_cmd + $require_local_gl_dir + # This ensures PATCH is set appropriately by the time # func_check_versions enforces $buildreq. $require_patch @@ -3400,6 +3588,28 @@ defaulting to '$copyright_holder'." } +# require_doc_base +# ---------------- +# Ensure doc_base has a sensible value, extracted from 'gnulib-cache.m4' +# if possible, otherwise letting 'gnulib-tool' pick a default. +require_doc_base=func_require_doc_base +func_require_doc_base () +{ + $debug_cmd + + $require_gnulib_cache + + test -f "$gnulib_cache" && test -z "$doc_base" && { + func_extract_trace_first "gl_DOC_BASE" "$gnulib_cache" + doc_base=$func_extract_trace_first_result + + test -n "$doc_base" && func_verbose "doc_base='$doc_base'" + } + + require_doc_base=: +} + + # require_dotgitmodules # --------------------- # Ensure we have a '.gitmodules' file, with appropriate 'gnulib' settings. @@ -3495,6 +3705,25 @@ func_require_gnulib_cache () } +# require_gnulib_copy_cmd +# ----------------------- +# Only calculate the options for copying files with gnulib once. +require_gnulib_copy_cmd=func_require_gnulib_copy_cmd +func_require_gnulib_copy_cmd () +{ + $debug_cmd + + $require_gnulib_tool + $require_gnulib_tool_base_options + + gnulib_copy_cmd="$gnulib_tool $gnulib_tool_base_options --copy-file" + $opt_copy || func_append gnulib_copy_cmd " --symlink" + $opt_quiet || func_append gnulib_copy_cmd " --verbose" + + require_gnulib_copy_cmd=: +} + + # require_gnulib_merge_changelog # ------------------------------ # See if we can use gnulib's git-merge-changelog merge driver. @@ -3533,10 +3762,9 @@ func_require_gnulib_mk () { $debug_cmd - test -f "$gnulib_cache" && test -z "$gnulib_mk" && { - $require_gnulib_cache - $require_macro_dir + $require_gnulib_cache + test -f "$gnulib_cache" && test -z "$gnulib_mk" && { func_extract_trace_first "gl_MAKEFILE_NAME" "$gnulib_cache" gnulib_mk=$func_extract_trace_first_result @@ -3547,6 +3775,28 @@ func_require_gnulib_mk () } +# require_gnulib_name +# ------------------- +# Ensure gnulib_name has a sensible value, extracted from 'gnulib-cache.m4' +# if possible, otherwise letting 'gnulib-tool' pick a default. +require_gnulib_name=func_require_gnulib_name +func_require_gnulib_name () +{ + $debug_cmd + + $require_gnulib_cache + + test -f "$gnulib_cache" && test -z "$gnulib_name" && { + func_extract_trace_first "gl_LIB" "$gnulib_cache" + gnulib_name=$func_extract_trace_first_result + + test -n "$gnulib_name" && func_verbose "gnulib_name='$gnulib_name'" + } + + require_gnulib_name=: +} + + # require_gnulib_path # require_gnulib_url # ------------------- @@ -3696,21 +3946,24 @@ func_require_gnulib_tool_base_options () gnulib_tool_base_options= test true = "$gnulib_tool" || { - $require_build_aux - $require_macro_dir - # 'gnulib_modules' and others are maintained in 'bootstrap.conf': # Use 'gnulib --import' to fetch gnulib modules. + $require_build_aux test -n "$build_aux" \ && func_append_uniq gnulib_tool_base_options " --aux-dir=$build_aux" + $require_macro_dir test -n "$macro_dir" \ && func_append_uniq gnulib_tool_base_options " --m4-base=$macro_dir" + $require_doc_base test -n "$doc_base" \ && func_append_uniq gnulib_tool_base_options " --doc-base=$doc_base" + $require_gnulib_name test -n "$gnulib_name" \ && func_append_uniq gnulib_tool_base_options " --lib=$gnulib_name" + $require_local_gl_dir test -n "$local_gl_dir" \ && func_append_uniq gnulib_tool_base_options " --local-dir=$local_gl_dir" + $require_source_base test -n "$source_base" \ && func_append_uniq gnulib_tool_base_options " --source-base=$source_base" } @@ -3753,6 +4006,28 @@ func_require_libtoolize () } +# require_local_gl_dir +# -------------------- +# Ensure local_gl_dir has a sensible value, extracted from 'gnulib-cache.m4' +# if possible, otherwise letting 'gnulib-tool' pick a default. +require_local_gl_dir=func_require_local_gl_dir +func_require_local_gl_dir () +{ + $debug_cmd + + $require_gnulib_cache + + test -f "$gnulib_cache" && test -z "$local_gl_dir" && { + func_extract_trace_first "gl_LOCAL_DIR" "$gnulib_cache" + local_gl_dir=$func_extract_trace_first_result + + test -n "$local_gl_dir" && func_verbose "local_gl_dir='$local_gl_dir'" + } + + require_local_gl_dir=: +} + + # require_macro_dir # ----------------- # Ensure that '$macro_dir' is set, and if it doesn't already point to an @@ -3981,8 +4256,6 @@ func_require_source_base () $require_gnulib_cache test -f "$gnulib_cache" && test -z "$source_base" && { - $require_macro_dir - func_extract_trace_first "gl_SOURCE_BASE" "$gnulib_cache" source_base=$func_extract_trace_first_result @@ -4094,27 +4367,30 @@ func_grep_q () # func_ifcontains LIST MEMBER YES-CMD [NO-CMD] # -------------------------------------------- # If whitespace-separated LIST contains MEMBER then execute YES-CMD, -# otherwise if NO-CMD was give, execute that. +# otherwise if NO-CMD was given, execute that. func_ifcontains () { $debug_cmd - # The embedded echo is to squash whitespace before globbing. - _G_wslist=`$bs_echo " "$1" "` + _G_wslist=$1 _G_member=$2 _G_yes_cmd=$3 _G_no_cmd=${4-":"} - case $_G_wslist in - *" $_G_member "*) - eval "$_G_yes_cmd" - _G_status=$? - ;; - *) - eval "$_G_no_cmd" - _G_status=$? - ;; - esac + _G_found=false + for _G_item in $_G_wslist; do + test "x$_G_item" = "x$_G_member" && { + _G_found=: + break + } + done + if $_G_found; then + eval "$_G_yes_cmd" + _G_status=$? + else + eval "$_G_no_cmd" + _G_status=$? + fi test 0 -eq "$_G_status" || exit $_G_status } @@ -4357,7 +4633,7 @@ func_gitignore_entries () { $debug_cmd - sed -e '/^#/d' -e '/^$/d' "$@" + $SED -e '/^#/d' -e '/^$/d' "$@" } @@ -4390,59 +4666,12 @@ func_insert_if_absent () linesnew=`{ $bs_echo "$str"; cat "$file"; } \ |func_gitignore_entries |sort -u |wc -l` test "$linesold" -eq "$linesnew" \ - || { sed "1i\\$nl$str$nl" "$file" >"$file"T && mv "$file"T "$file"; } \ + || { $SED "1i\\$nl$str$nl" "$file" >"$file"T && mv "$file"T "$file"; } \ || func_permissions_error "$file" done } -# func_sort_ver VER1 VER2 -# ----------------------- -# 'sort -V' is not generally available. -# Note this deviates from the version comparison in automake -# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a -# but this should suffice as we won't be specifying old -# version formats or redundant trailing .0 in bootstrap.conf. -# If we did want full compatibility then we should probably -# use m4_version_compare from autoconf. -func_sort_ver () -{ - $debug_cmd - - ver1=$1 - ver2=$2 - - # Split on '.' and compare each component. - i=1 - while :; do - p1=`echo "$ver1" |cut -d. -f$i` - p2=`echo "$ver2" |cut -d. -f$i` - if test ! "$p1"; then - echo "$1 $2" - break - elif test ! "$p2"; then - echo "$2 $1" - break - elif test ! "$p1" = "$p2"; then - if test "$p1" -gt "$p2" 2>/dev/null; then # numeric comparison - echo "$2 $1" - elif test "$p2" -gt "$p1" 2>/dev/null; then # numeric comparison - echo "$1 $2" - else # numeric, then lexicographic comparison - lp=`printf "$p1\n$p2\n" |sort -n |tail -n1` - if test "$lp" = "$p2"; then - echo "$1 $2" - else - echo "$2 $1" - fi - fi - break - fi - i=`expr $i + 1` - done -} - - # func_get_version APP # -------------------- # echo the version number (if any) of APP, which is looked up along your @@ -4564,8 +4793,7 @@ delimited list of triples; 'program min-version url'." # Fail if a newer version than what we have is required. else - _G_newer=`func_sort_ver $_G_reqver $_G_instver |cut -d' ' -f2` - test "$_G_newer" != "$_G_instver" && { + func_lt_ver "$_G_reqver" "$_G_instver" || { func_error "\ '$_G_app' version == $_G_instver is too old '$_G_app' version >= $_G_reqver is required" diff --git a/bootstrap.slingshot b/bootstrap.slingshot index 20be0e7..9e79586 100644 --- a/bootstrap.slingshot +++ b/bootstrap.slingshot @@ -1,6 +1,6 @@ # bootstrap.slingshot (Slingshot) version 2013-05-06 # -# Copyright (C) 2013 Gary V. Vaughan +# Copyright (C) 2013-2014 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 # This is free software; see the source for copying conditions. There is NO diff --git a/slingshot b/slingshot index 9136047..79b7de2 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 91360471c71db1ec923b0c9bdc3bd78bc07ca84e +Subproject commit 79b7de2c47a41a4f2f7a7d1b2c1a59ffbeb8f923 From 18713d4d45069d070a47158fd859cf3ad96b3c32 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 4 Jan 2014 15:04:22 +1300 Subject: [PATCH 036/703] slingshot: sync with upstream and simplify accordingly. * slingshot: Sync with upstream. * bootstrap: Update from slingshot. * configure.ac (AM_INIT_AUTOMAKE): Remove 'foreign'. Slingshot now handles missing README automatically. * bootstrap.slingshot: Remove. No longer required. * bootstrap.conf: Remove bootstrap.slingshot source boilerplate. (stdlib_force_changelog): Remove. Automated by slingshot now. * INSTALL: Remove. Autotools copies in the canonical version automatically. * COPYING: New file, with MIT License, to avoid Autotools copying over a standard GPLv3 COPYING boilerplate. * .gitignore: Update. Signed-off-by: Gary V. Vaughan --- .gitignore | 4 +- COPYING | 27 ++ INSTALL | 364 --------------- bootstrap | 1040 +++++++++++++++++++++++++++---------------- bootstrap.conf | 33 +- bootstrap.slingshot | 282 ------------ configure.ac | 2 +- slingshot | 2 +- 8 files changed, 688 insertions(+), 1066 deletions(-) create mode 100644 COPYING delete mode 100644 INSTALL delete mode 100644 bootstrap.slingshot diff --git a/.gitignore b/.gitignore index a31876f..37d0166 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,14 @@ /*.rockspec /.autom4te.cfg /.gitmodules -/COPYING /ChangeLog +/ChangeLog~ /GNUmakefile +/INSTALL /Makefile /Makefile.am /Makefile.in +/README /aclocal.m4 /autom4te.cache /build-aux diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3db866f --- /dev/null +++ b/COPYING @@ -0,0 +1,27 @@ +This software comprises files that are copyright their respective +authors (see the AUTHORS file for details), and distributed under +the terms of the MIT license (the same license as Lua itself), +unless noted otherwise in the body of that file. + +==================================================================== +Copyright (C) 2013-2014 Gary V. Vaughan + +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/INSTALL b/INSTALL deleted file mode 100644 index 64c6937..0000000 --- a/INSTALL +++ /dev/null @@ -1,364 +0,0 @@ -Installation Instructions -************************* - -Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, -2006, 2007, 2008, 2009 Free Software Foundation, Inc. - - Copying and distribution of this file, with or without modification, -are permitted in any medium without royalty provided the copyright -notice and this notice are preserved. This file is offered as-is, -without warranty of any kind. - -Basic Installation -================== - - Briefly, the shell commands `./configure; make; make install' should -configure, build, and install this package. The following -more-detailed instructions are generic; see the `README' file for -instructions specific to this package. Some packages provide this -`INSTALL' file but do not implement all of the features documented -below. The lack of an optional feature in a given package is not -necessarily a bug. More recommendations for GNU packages can be found -in *note Makefile Conventions: (standards)Makefile Conventions. - - The `configure' shell script attempts to guess correct values for -various system-dependent variables used during compilation. It uses -those values to create a `Makefile' in each directory of the package. -It may also create one or more `.h' files containing system-dependent -definitions. Finally, it creates a shell script `config.status' that -you can run in the future to recreate the current configuration, and a -file `config.log' containing compiler output (useful mainly for -debugging `configure'). - - It can also use an optional file (typically called `config.cache' -and enabled with `--cache-file=config.cache' or simply `-C') that saves -the results of its tests to speed up reconfiguring. Caching is -disabled by default to prevent problems with accidental use of stale -cache files. - - If you need to do unusual things to compile the package, please try -to figure out how `configure' could check whether to do them, and mail -diffs or instructions to the address given in the `README' so they can -be considered for the next release. If you are using the cache, and at -some point `config.cache' contains results you don't want to keep, you -may remove or edit it. - - The file `configure.ac' (or `configure.in') is used to create -`configure' by a program called `autoconf'. You need `configure.ac' if -you want to change it or regenerate `configure' using a newer version -of `autoconf'. - - The simplest way to compile this package is: - - 1. `cd' to the directory containing the package's source code and type - `./configure' to configure the package for your system. - - Running `configure' might take a while. While running, it prints - some messages telling which features it is checking for. - - 2. Type `make' to compile the package. - - 3. Optionally, type `make check' to run any self-tests that come with - the package, generally using the just-built uninstalled binaries. - - 4. Type `make install' to install the programs and any data files and - documentation. When installing into a prefix owned by root, it is - recommended that the package be configured and built as a regular - user, and only the `make install' phase executed with root - privileges. - - 5. Optionally, type `make installcheck' to repeat any self-tests, but - this time using the binaries in their final installed location. - This target does not install anything. Running this target as a - regular user, particularly if the prior `make install' required - root privileges, verifies that the installation completed - correctly. - - 6. You can remove the program binaries and object files from the - source code directory by typing `make clean'. To also remove the - files that `configure' created (so you can compile the package for - a different kind of computer), type `make distclean'. There is - also a `make maintainer-clean' target, but that is intended mainly - for the package's developers. If you use it, you may have to get - all sorts of other programs in order to regenerate files that came - with the distribution. - - 7. Often, you can also type `make uninstall' to remove the installed - files again. In practice, not all packages have tested that - uninstallation works correctly, even though it is required by the - GNU Coding Standards. - - 8. Some packages, particularly those that use Automake, provide `make - distcheck', which can by used by developers to test that all other - targets like `make install' and `make uninstall' work correctly. - This target is generally not run by end users. - -Compilers and Options -===================== - - Some systems require unusual options for compilation or linking that -the `configure' script does not know about. Run `./configure --help' -for details on some of the pertinent environment variables. - - You can give `configure' initial values for configuration parameters -by setting variables in the command line or in the environment. Here -is an example: - - ./configure CC=c99 CFLAGS=-g LIBS=-lposix - - *Note Defining Variables::, for more details. - -Compiling For Multiple Architectures -==================================== - - You can compile the package for more than one kind of computer at the -same time, by placing the object files for each architecture in their -own directory. To do this, you can use GNU `make'. `cd' to the -directory where you want the object files and executables to go and run -the `configure' script. `configure' automatically checks for the -source code in the directory that `configure' is in and in `..'. This -is known as a "VPATH" build. - - With a non-GNU `make', it is safer to compile the package for one -architecture at a time in the source code directory. After you have -installed the package for one architecture, use `make distclean' before -reconfiguring for another architecture. - - On MacOS X 10.5 and later systems, you can create libraries and -executables that work on multiple system types--known as "fat" or -"universal" binaries--by specifying multiple `-arch' options to the -compiler but only a single `-arch' option to the preprocessor. Like -this: - - ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ - CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ - CPP="gcc -E" CXXCPP="g++ -E" - - This is not guaranteed to produce working output in all cases, you -may have to build one architecture at a time and combine the results -using the `lipo' tool if you have problems. - -Installation Names -================== - - By default, `make install' installs the package's commands under -`/usr/local/bin', include files under `/usr/local/include', etc. You -can specify an installation prefix other than `/usr/local' by giving -`configure' the option `--prefix=PREFIX', where PREFIX must be an -absolute file name. - - You can specify separate installation prefixes for -architecture-specific files and architecture-independent files. If you -pass the option `--exec-prefix=PREFIX' to `configure', the package uses -PREFIX as the prefix for installing programs and libraries. -Documentation and other data files still use the regular prefix. - - In addition, if you use an unusual directory layout you can give -options like `--bindir=DIR' to specify different values for particular -kinds of files. Run `configure --help' for a list of the directories -you can set and what kinds of files go in them. In general, the -default for these options is expressed in terms of `${prefix}', so that -specifying just `--prefix' will affect all of the other directory -specifications that were not explicitly provided. - - The most portable way to affect installation locations is to pass the -correct locations to `configure'; however, many packages provide one or -both of the following shortcuts of passing variable assignments to the -`make install' command line to change installation locations without -having to reconfigure or recompile. - - The first method involves providing an override variable for each -affected directory. For example, `make install -prefix=/alternate/directory' will choose an alternate location for all -directory configuration variables that were expressed in terms of -`${prefix}'. Any directories that were specified during `configure', -but not in terms of `${prefix}', must each be overridden at install -time for the entire installation to be relocated. The approach of -makefile variable overrides for each directory variable is required by -the GNU Coding Standards, and ideally causes no recompilation. -However, some platforms have known limitations with the semantics of -shared libraries that end up requiring recompilation when using this -method, particularly noticeable in packages that use GNU Libtool. - - The second method involves providing the `DESTDIR' variable. For -example, `make install DESTDIR=/alternate/directory' will prepend -`/alternate/directory' before all installation names. The approach of -`DESTDIR' overrides is not required by the GNU Coding Standards, and -does not work on platforms that have drive letters. On the other hand, -it does better at avoiding recompilation issues, and works well even -when some directory options were not specified in terms of `${prefix}' -at `configure' time. - -Optional Features -================= - - If the package supports it, you can cause programs to be installed -with an extra prefix or suffix on their names by giving `configure' the -option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. - - Some packages pay attention to `--enable-FEATURE' options to -`configure', where FEATURE indicates an optional part of the package. -They may also pay attention to `--with-PACKAGE' options, where PACKAGE -is something like `gnu-as' or `x' (for the X Window System). The -`README' should mention any `--enable-' and `--with-' options that the -package recognizes. - - For packages that use the X Window System, `configure' can usually -find the X include and library files automatically, but if it doesn't, -you can use the `configure' options `--x-includes=DIR' and -`--x-libraries=DIR' to specify their locations. - - Some packages offer the ability to configure how verbose the -execution of `make' will be. For these packages, running `./configure ---enable-silent-rules' sets the default to minimal output, which can be -overridden with `make V=1'; while running `./configure ---disable-silent-rules' sets the default to verbose, which can be -overridden with `make V=0'. - -Particular systems -================== - - On HP-UX, the default C compiler is not ANSI C compatible. If GNU -CC is not installed, it is recommended to use the following options in -order to use an ANSI C compiler: - - ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" - -and if that doesn't work, install pre-built binaries of GCC for HP-UX. - - On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot -parse its `' header file. The option `-nodtk' can be used as -a workaround. If GNU CC is not installed, it is therefore recommended -to try - - ./configure CC="cc" - -and if that doesn't work, try - - ./configure CC="cc -nodtk" - - On Solaris, don't put `/usr/ucb' early in your `PATH'. This -directory contains several dysfunctional programs; working variants of -these programs are available in `/usr/bin'. So, if you need `/usr/ucb' -in your `PATH', put it _after_ `/usr/bin'. - - On Haiku, software installed for all users goes in `/boot/common', -not `/usr/local'. It is recommended to use the following options: - - ./configure --prefix=/boot/common - -Specifying the System Type -========================== - - There may be some features `configure' cannot figure out -automatically, but needs to determine by the type of machine the package -will run on. Usually, assuming the package is built to be run on the -_same_ architectures, `configure' can figure that out, but if it prints -a message saying it cannot guess the machine type, give it the -`--build=TYPE' option. TYPE can either be a short name for the system -type, such as `sun4', or a canonical name which has the form: - - CPU-COMPANY-SYSTEM - -where SYSTEM can have one of these forms: - - OS - KERNEL-OS - - See the file `config.sub' for the possible values of each field. If -`config.sub' isn't included in this package, then this package doesn't -need to know the machine type. - - If you are _building_ compiler tools for cross-compiling, you should -use the option `--target=TYPE' to select the type of system they will -produce code for. - - If you want to _use_ a cross compiler, that generates code for a -platform different from the build platform, you should specify the -"host" platform (i.e., that on which the generated programs will -eventually be run) with `--host=TYPE'. - -Sharing Defaults -================ - - If you want to set default values for `configure' scripts to share, -you can create a site shell script called `config.site' that gives -default values for variables like `CC', `cache_file', and `prefix'. -`configure' looks for `PREFIX/share/config.site' if it exists, then -`PREFIX/etc/config.site' if it exists. Or, you can set the -`CONFIG_SITE' environment variable to the location of the site script. -A warning: not all `configure' scripts look for a site script. - -Defining Variables -================== - - Variables not defined in a site shell script can be set in the -environment passed to `configure'. However, some packages may run -configure again during the build, and the customized values of these -variables may be lost. In order to avoid this problem, you should set -them in the `configure' command line, using `VAR=value'. For example: - - ./configure CC=/usr/local2/bin/gcc - -causes the specified `gcc' to be used as the C compiler (unless it is -overridden in the site shell script). - -Unfortunately, this technique does not work for `CONFIG_SHELL' due to -an Autoconf bug. Until the bug is fixed you can use this workaround: - - CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash - -`configure' Invocation -====================== - - `configure' recognizes the following options to control how it -operates. - -`--help' -`-h' - Print a summary of all of the options to `configure', and exit. - -`--help=short' -`--help=recursive' - Print a summary of the options unique to this package's - `configure', and exit. The `short' variant lists options used - only in the top level, while the `recursive' variant lists options - also present in any nested packages. - -`--version' -`-V' - Print the version of Autoconf used to generate the `configure' - script, and exit. - -`--cache-file=FILE' - Enable the cache: use and save the results of the tests in FILE, - traditionally `config.cache'. FILE defaults to `/dev/null' to - disable caching. - -`--config-cache' -`-C' - Alias for `--cache-file=config.cache'. - -`--quiet' -`--silent' -`-q' - Do not print messages saying which checks are being made. To - suppress all normal output, redirect it to `/dev/null' (any error - messages will still be shown). - -`--srcdir=DIR' - Look for the package's source code in directory DIR. Usually - `configure' can determine that directory automatically. - -`--prefix=DIR' - Use DIR as the installation prefix. *note Installation Names:: - for more details, including other options available for fine-tuning - the installation locations. - -`--no-create' -`-n' - Run the configure checks, but stop before creating any output - files. - -`configure' also accepts some other, not widely useful, options. Run -`configure --help' for more details. diff --git a/bootstrap b/bootstrap index 18323eb..048200f 100755 --- a/bootstrap +++ b/bootstrap @@ -1,4 +1,6 @@ #! /bin/sh +## DO NOT EDIT - This file generated from build-aux/bootstrap.in +## by inline-source v2014-01-03.01 # Bootstrap an Autotooled package from checked-out sources. # Written by Gary V. Vaughan, 2010 @@ -7,9 +9,238 @@ # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Originally written by Paul Eggert. The canonical version of this +# script is maintained as build-aux/bootstrap in gnulib, however, to +# be useful to your project, you should place a copy of it under +# version control in the top-level directory of your project. The +# intent is that all customization can be done with a bootstrap.conf +# file also maintained in your version control; gnulib comes with a +# template build-aux/bootstrap.conf to get you started. + +# Please report bugs or propose patches to bug-gnulib@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# Most GNUish projects do not keep all of the generated Autotool +# files under version control, but running all of the right tools +# with the right arguments, in the correct order to regenerate +# all of those files in readiness for configuration and building +# can be surprisingly involved! Many projects have a 'bootstrap' +# script under version control to invoke Autotools and perform +# other assorted book-keeping with version numbers and the like. +# +# This bootstrap script aims to probe the configure.ac and top +# Makefile.am of your project to automatically determine what +# the correct ordering and arguments are and then run the tools for +# you. In order to use it, you can generate an initial standalone +# script with: +# +# gl/build-aux/inline-source gl/build-aux/bootstrap.in > bootstrap +# +# You should then store than script in version control for other +# developers in you project. It will give you instructions about +# how to keep it up to date if the sources change. +# +# See gl/doc/bootstrap.texi for documentation on how to write +# a bootstrap.conf to customize it for your project's +# idiosyncracies. + + +## ================================================================== ## +## ## +## DO NOT EDIT THIS FILE, CUSTOMIZE IT USING A BOOTSTRAP.CONF ## +## ## +## ================================================================== ## + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# All uppercase denotes values stored in the environment. These +# variables should generally be overridden by the user - however, we do +# set them to 'true' in some parts of this script to prevent them being +# called at the wrong time by other tools that we call ('autoreconf', +# for example). +# +# We also allow 'LIBTOOLIZE', 'M4', 'SHA1SUM' and some others to be +# overridden, and export the result for child processes, but they are +# handled by the function 'func_find_tool' and not defaulted in this +# section. + +: ${ACLOCAL="aclocal"} +: ${AUTOCONF="autoconf"} +: ${AUTOHEADER="autoheader"} +: ${AUTOM4TE="autom4te"} +: ${AUTOHEADER="autoheader"} +: ${AUTOMAKE="automake"} +: ${AUTOPOINT="autopoint"} +: ${AUTORECONF="autoreconf"} +: ${CMP="cmp"} +: ${CONFIG_SHELL="/bin/sh"} +: ${DIFF="diff"} +: ${GIT="git"} +: ${LN_S="ln -s"} +: ${RM="rm"} + +export ACLOCAL +export AUTOCONF +export AUTOHEADER +export AUTOM4TE +export AUTOHEADER +export AUTOMAKE +export AUTOPOINT +export AUTORECONF +export CONFIG_SHELL + + +## -------------- ## +## Configuration. ## +## -------------- ## + +# A newline delimited list of triples of programs (that respond to +# --version), the minimum version numbers required (or just '-' in the +# version field if any version will be sufficient) and homepage URLs +# to help locate missing packages. +buildreq= + +# Name of a file containing instructions on installing missing packages +# required in 'buildreq'. +buildreq_readme=README-hacking + +# These are extracted from AC_INIT in configure.ac, though you can +# override those values in 'bootstrap.conf' if you prefer. +build_aux= +macro_dir= +package= +package_name= +package_version= +package_bugreport= + +# These are extracted from 'gnulib-cache.m4', or else fall-back +# automatically on the gnulib defaults; unless you set the values +# manually in 'bootstrap.conf'. +doc_base= +gnulib_mk= +gnulib_name= +local_gl_dir= +source_base= +tests_base= + +# The list of gnulib modules required at 'gnulib-tool' time. If you +# check 'gnulib-cache.m4' into your repository, then this list will be +# extracted automatically. +gnulib_modules= + +# Extra gnulib files that are not in modules, which override files of +# the same name installed by other bootstrap tools. +gnulib_non_module_files=" + build-aux/compile + build-aux/install-sh + build-aux/mdate-sh + build-aux/texinfo.tex + build-aux/depcomp + build-aux/config.guess + build-aux/config.sub + doc/INSTALL +" + +# Relative path to the local gnulib submodule, and url to the upstream +# git repository. If you have a gnulib entry in your .gitmodules file, +# these values are ignored. +gnulib_path= +gnulib_url= + +# Additional gnulib-tool options to use. +gnulib_tool_options=" + --no-changelog +" + +# bootstrap removes any macro-files that are not included by aclocal.m4, +# except for files listed in this variable that are always kept. +gnulib_precious=" + gnulib-tool.m4 +" + +# When truncating long commands for display, always allow at least this +# many characters before truncating. +min_cmd_len=160 + +# The command to download all .po files for a specified domain into +# a specified directory. Fill in the first %s is the domain name, and +# the second with the destination directory. Use rsync's -L and -r +# options because the latest/%s directory and the .po files within are +# all symlinks. +po_download_command_format=\ +"rsync --delete --exclude '*.s1' -Lrtvz \ +'translationproject.org::tp/latest/%s/' '%s'" + +# Other locale categories that need message catalogs. +extra_locale_categories= + +# Additional xgettext options to use. Gnulib might provide you with an +# extensive list of additional options to append to this, but gettext +# 0.16.1 and newer appends them automaticaly, so you can safely ignore +# the complaints from 'gnulib-tool' if your $configure_ac states: +# +# AM_GNU_GETTEXT_VERSION([0.16.1]) +xgettext_options=" + --flag=_:1:pass-c-format + --flag=N_:1:pass-c-format +" + +# Package copyright holder for gettext files. Defaults to FSF if unset. +copyright_holder= + +# File that should exist in the top directory of a checked out hierarchy, +# but not in a distribution tarball. +checkout_only_file= + +# Whether to use copies instead of symlinks by default (if set to true, +# the --copy option has no effect). +copy=false + +# Set this to ".cvsignore .gitignore" in 'bootstrap.conf' if you want +# those files to be generated in directories like 'lib/', 'm4/', and 'po/', +# or set it to "auto" to make this script select what to use based +# on what version control system (if any) is used in the source directory. +# Or set it to "none" to ignore VCS ignore files entirely. Default is +# "auto". +vc_ignore= + + +# List of slingshot files to link into stdlib tree before autotooling. +slingshot_files=$slingshot_files + +# Relative path to the local slingshot submodule, and url to the upsream +# git repository. If you have a slingshot entry in your .gitmodules file, +# these values are ignored. +slingshot_path=$slingshot_path +slingshot_url=$slingshot_url + + +## ------------------- ## +## External Libraries. ## +## ------------------- ## + # Source required external libraries: # Set a version string for this script. -scriptversion=2013-10-28.05; # UTC +scriptversion=2014-01-03.01; # UTC # General shell script boiler plate, and helper functions. # Written by Gary V. Vaughan, 2004 @@ -163,47 +394,6 @@ func_path_progs () } -# There are still modern systems that have problems with 'echo' mis- -# handling backslashes, among others, so make sure $bs_echo is set to a -# command that correctly interprets backslashes. -# (this code from Autoconf 2.68) - -# Printing a long string crashes Solaris 7 /usr/bin/printf. -bs_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' -bs_echo=$bs_echo$bs_echo$bs_echo$bs_echo$bs_echo -bs_echo=$bs_echo$bs_echo$bs_echo$bs_echo$bs_echo$bs_echo -# Prefer a ksh shell builtin over an external printf program on Solaris, -# but without wasting forks for bash or zsh. -if test -z "$BASH_VERSION$ZSH_VERSION" \ - && (test "X`print -r -- $bs_echo`" = "X$bs_echo") 2>/dev/null; then - bs_echo='print -r --' - bs_echo_n='print -rn --' -elif (test "X`printf %s $bs_echo`" = "X$bs_echo") 2>/dev/null; then - bs_echo='printf %s\n' - bs_echo_n='printf %s' -else - if test "X`(/usr/ucb/echo -n -n $bs_echo) 2>/dev/null`" = "X-n $bs_echo"; then - bs_echo_body='eval /usr/ucb/echo -n "$1$nl"' - bs_echo_n='/usr/ucb/echo -n' - else - bs_echo_body='eval expr "X$1" : "X\\(.*\\)"' - bs_echo_n_body='eval - arg=$1; - case $arg in #( - *"$nl"*) - expr "X$arg" : "X\\(.*\\)$nl"; - arg=`expr "X$arg" : ".*$nl\\(.*\\)"`;; - esac; - expr "X$arg" : "X\\(.*\\)" | tr -d "$nl" - ' - export bs_echo_n_body - bs_echo_n='sh -c $bs_echo_n_body bs_echo' - fi - export bs_echo_body - bs_echo='sh -c $bs_echo_body bs_echo' -fi - - # We want to be able to use the functions in this file before configure # has figured out where the best binaries are kept, which means we have # to search for them ourselves - except when the results are already set @@ -224,13 +414,13 @@ test -z "$SED" && { _G_path_prog=$1 _G_count=0 - $bs_echo_n 0123456789 >conftest.in + printf 0123456789 >conftest.in while : do cat conftest.in conftest.in >conftest.tmp mv conftest.tmp conftest.in cp conftest.in conftest.nl - $bs_echo '' >> conftest.nl + echo '' >> conftest.nl "$_G_path_prog" -f conftest.sed conftest.out 2>/dev/null || break diff conftest.out conftest.nl >/dev/null 2>&1 || break _G_count=`expr $_G_count + 1` @@ -259,13 +449,13 @@ test -z "$GREP" && { _G_count=0 _G_path_prog_max=0 - $bs_echo_n 0123456789 >conftest.in + printf 0123456789 >conftest.in while : do cat conftest.in conftest.in >conftest.tmp mv conftest.tmp conftest.in cp conftest.in conftest.nl - $bs_echo 'GREP' >> conftest.nl + echo 'GREP' >> conftest.nl "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' conftest.out 2>/dev/null || break diff conftest.out conftest.nl >/dev/null 2>&1 || break _G_count=`expr $_G_count + 1` @@ -295,7 +485,7 @@ test -z "$GREP" && { # in the command search PATH. : ${CP="cp -f"} -: ${ECHO="$bs_echo"} +: ${ECHO="printf %s\n"} : ${EGREP="$GREP -E"} : ${FGREP="$GREP -F"} : ${LN_S="ln -s"} @@ -393,13 +583,13 @@ exit_status=$EXIT_SUCCESS progpath=$0 # The name of this program. -progname=`$bs_echo "$progpath" |$SED "$sed_basename"` +progname=`$ECHO "$progpath" |$SED "$sed_basename"` # Make sure we have an absolute progpath for reexecution: case $progpath in [\\/]*|[A-Za-z]:\\*) ;; *[\\/]*) - progdir=`$bs_echo "$progpath" |$SED "$sed_dirname"` + progdir=`$ECHO "$progpath" |$SED "$sed_dirname"` progdir=`cd "$progdir" && pwd` progpath=$progdir/$progname ;; @@ -500,6 +690,135 @@ func_require_term_colors () } +# require_slingshot_dotgitmodules +# ------------------------------- +# Ensure we have a '.gitmodules' file, with appropriate 'slingshot' settings. +require_slingshot_dotgitmodules=slingshot_require_slingshot_dotgitmodules +slingshot_require_slingshot_dotgitmodules () +{ + $debug_cmd + + $require_git + + test true = "$GIT" || { + # A slingshot entry in .gitmodules always takes precedence. + _G_path=`$GIT config --file .gitmodules submodule.slingshot.path 2>/dev/null` + + test -n "$_G_path" || { + $require_vc_ignore_files + + func_verbose "adding slingshot entries to '.gitmodules'" + + test -n "$slingshot_path" || slingshot_path=slingshot + test -n "$slingshot_url" || slingshot_url=git://github.com/gvvaughan/slingshot.git + + { + echo '[submodule "slingshot"]' + echo " path=$slingshot_path" + echo " url=$slingshot_url" + } >> .gitmodules + + test -n "$vc_ignore_files" \ + || func_insert_if_absent ".gitmodules" $vc_ignore_files + } + } + + require_slingshot_dotgitmodules=: +} + + +# require_slingshot_path +# require_slingshot_url +# ---------------------- +# Ensure 'slingshot_path' and 'slingshot_url' are set. +require_slingshot_path=slingshot_require_slingshot_dotgitmodules_parameters +require_slingshot_url=slingshot_require_slingshot_dotgitmodules_parameters +slingshot_require_slingshot_dotgitmodules_parameters () +{ + $debug_cmd + + $require_git + $require_slingshot_dotgitmodules + + test -f .gitmodules \ + || func_fatal_error "Unable to update '.gitmodules' with slingshot submodule" + + test true = "$GIT" || { + slingshot_path=`$GIT config --file=.gitmodules --get submodule.slingshot.path` + slingshot_url=`$GIT config --file=.gitmodules --get submodule.slingshot.url` + + func_verbose "slingshot_path='$slingshot_path'" + func_verbose "slingshot_url='$slingshot_url'" + } + + require_slingshot_path=: + require_slingshot_url=: +} + + +# require_slingshot_submodule +# --------------------------- +# Ensure that there is a current slingshot submodule. +require_slingshot_submodule=slingshot_require_slingshot_submodule +slingshot_require_slingshot_submodule () +{ + $debug_cmd + + $require_git + + if test true = "$GIT"; then + func_warning recommend \ + "No 'git' found; imported slingshot modules may be missing." + else + $require_slingshot_dotgitmodules + + if test -f .gitmodules && test -f "slingshot/src/mkrockspecs.in" + then + : All present and correct. + + else + $require_slingshot_path + $require_slingshot_url + + trap slingshot_cleanup 1 2 13 15 + + shallow= + $GIT clone -h 2>&1 |func_grep_q -- --depth \ + && shallow='--depth 365' + + func_show_eval "$GIT clone $shallow '$slingshot_url' '$slingshot_path'" \ + slingshot_cleanup + + # FIXME: Solaris /bin/sh will try to execute '-' if any of + # these signals are caught after this. + trap - 1 2 13 15 + + # Make sure we've checked out the correct revision of slingshot. + func_show_eval "$GIT submodule init" \ + && func_show_eval "$GIT submodule update" \ + || func_fatal_error "Unable to update slingshot submodule." + fi + fi + + require_slingshot_submodule=: +} + + +# slingshot_cleanup +# ----------------- +# Recursively delete everything at $slingshot_path. +slingshot_cleanup () +{ + $debug_cmd + + $require_slingshot_path + + _G_status=$? + $RM -fr $slingshot_path + exit $_G_status +} + + ## ----------------- ## ## Function library. ## ## ----------------- ## @@ -593,7 +912,7 @@ func_append_uniq () { $debug_cmd - eval _G_current_value='`$bs_echo $'$1'`' + eval _G_current_value='`$ECHO $'$1'`' _G_delim=`expr "$2" : '\(.\)'` case $_G_delim$_G_current_value$_G_delim in @@ -706,7 +1025,7 @@ func_echo () IFS=$nl for _G_line in $_G_message; do IFS=$func_echo_IFS - $bs_echo "$progname: $_G_line" + $ECHO "$progname: $_G_line" done IFS=$func_echo_IFS } @@ -740,8 +1059,8 @@ func_echo_infix_1 () for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" do test -n "$_G_tc" && { - _G_esc_tc=`$bs_echo "$_G_tc" | $SED "$sed_make_literal_regex"` - _G_indent=`$bs_echo "$_G_indent" | $SED "s|$_G_esc_tc||g"` + _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"` + _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"` } done _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes @@ -750,7 +1069,7 @@ func_echo_infix_1 () IFS=$nl for _G_line in $_G_message; do IFS=$func_echo_infix_1_IFS - $bs_echo "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 + $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 _G_prefix=$_G_indent done IFS=$func_echo_infix_1_IFS @@ -1348,14 +1667,14 @@ func_lt_ver () # Local variables: # mode: shell-script # sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) +# eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" # time-stamp-time-zone: "UTC" # End: #! /bin/sh # Set a version string for this script. -scriptversion=2012-10-21.11; # UTC +scriptversion=2014-01-04.01; # UTC # A portable, pluggable option parser for Bourne shell. # Written by Gary V. Vaughan, 2010 @@ -1500,7 +1819,7 @@ func_remove_hook () { $debug_cmd - eval ${1}_hooks='`$bs_echo "\$'$1'_hooks" |$SED "s| '$2'||"`' + eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`' } @@ -1777,9 +2096,9 @@ func_validate_options () -## ------------------## +## ----------------- ## ## Helper functions. ## -## ------------------## +## ----------------- ## # This section contains the helper functions used by the rest of the # hookable option parser framework in ascii-betical order. @@ -1793,8 +2112,8 @@ func_fatal_help () { $debug_cmd - eval \$bs_echo \""Usage: $usage"\" - eval \$bs_echo \""$fatal_help"\" + eval \$ECHO \""Usage: $usage"\" + eval \$ECHO \""$fatal_help"\" func_error ${1+"$@"} exit $EXIT_FAILURE } @@ -1808,7 +2127,7 @@ func_help () $debug_cmd func_usage_message - $bs_echo "$long_help_message" + $ECHO "$long_help_message" exit 0 } @@ -1895,7 +2214,7 @@ func_usage () $debug_cmd func_usage_message - $bs_echo "Run '$progname --help |${PAGER-more}' for full usage" + $ECHO "Run '$progname --help |${PAGER-more}' for full usage" exit 0 } @@ -1907,7 +2226,7 @@ func_usage_message () { $debug_cmd - eval \$bs_echo \""Usage: $usage"\" + eval \$ECHO \""Usage: $usage"\" echo $SED -n 's|^# || /^Written by/{ @@ -1916,7 +2235,7 @@ func_usage_message () h /^Written by/q' < "$progpath" echo - eval \$bs_echo \""$usage_message"\" + eval \$ECHO \""$usage_message"\" } @@ -1956,7 +2275,7 @@ func_version () # Local variables: # mode: shell-script # sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) +# eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" # time-stamp-time-zone: "UTC" # End: @@ -1974,7 +2293,7 @@ test -z "$progpath" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/funclib.sh test extract-trace = "$progname" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/options-parser # Set a version string. -scriptversion=2013-08-22.10; # UTC +scriptversion=2014-01-04.01; # UTC # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -1992,6 +2311,67 @@ scriptversion=2013-08-22.10; # UTC # Please report bugs or propose patches to gary@gnu.org. +# slingshot_copy FILENAME SRCDIR DESTDIR +# -------------------------------------- +# If option '--copy' was specified, or soft-linking SRCFILE to DESTFILE +# fails, then try to copy SRCFILE to DESTFILE (making sure to update the +# timestamp so that a series of files with dependencies can be copied +# in the right order that their timestamps won't trigger rebuilds). +slingshot_copy () +{ + $debug_cmd + + slingshot_srcfile=`echo "$2/$1" |sed -e 's|/\./|/|g'` + slingshot_destfile=`echo "$3/$1" |sed -e 's|/\./|/|g'` + + $opt_force || { + # Nothing to do if the files are already identical. + if func_cmp_s "$slingshot_srcfile" "$slingshot_destfile"; then + func_verbose "'$slingshot_destfile' is up to date." + return 0 + fi + } + + # Require --force to remove existing $slingshot_destfile. + $opt_force && $RM "$slingshot_destfile" + test -f "$slingshot_destfile" && { + func_warn_and_continue "'$slingshot_destfile' exists: use '--force' to overwrite" + return 0 + } + + # Be careful to support 'func_copy dir/target srcbase destbase'. + func_dirname "$slingshot_destfile" + func_mkdir_p "$func_dirname_result" + + # Copy or link according to '--copy' option. + if $opt_copy; then + slingshot_copycmd=$CP + slingshot_copy_type=copying + else + slingshot_copycmd=$LN_S + slingshot_copy_type=linking + + func_relative_path "$3" "$2" + slingshot_srcfile=$func_relative_path_result/$1 + fi + slingshot_copy_msg="$slingshot_copy_type file '$slingshot_destfile'" + $opt_verbose && \ + slingshot_copy_msg="$slingshot_copy_type $slingshot_srcfile $3" + + if $opt_dry_run || { + ( umask 0 + $slingshot_copycmd "$slingshot_srcfile" "$slingshot_destfile" + ) >/dev/null 2>&1 + } + then + echo "$slingshot_copy_msg" + else + func_error "$slingshot_copy_type '$2/$1' to '$3/' failed" + return 1 + fi +} + + ## ------ ## ## Usage. ## ## ------ ## @@ -2006,9 +2386,9 @@ scriptversion=2013-08-22.10; # UTC -## ------------------## +## ----------------- ## ## Helper functions. ## -## ------------------## +## ----------------- ## # This section contains the helper functions used by the rest of # 'extract-trace'. @@ -2204,8 +2584,8 @@ func_extract_trace () $require_configure_ac $require_gnu_m4 - _G_m4_traces=`$bs_echo "--trace=$1" |$SED 's%,% --trace=%g'` - _G_re_macros=`$bs_echo "($1)" |$SED 's%,%|%g'` + _G_m4_traces=`$ECHO "--trace=$1" |$SED 's%,% --trace=%g'` + _G_re_macros=`$ECHO "($1)" |$SED 's%,%|%g'` _G_macros="$1"; shift test $# -gt 0 || { set dummy $configure_ac @@ -2277,326 +2657,112 @@ func_extract_trace () IFS=, for _G_macro in $_G_macros; do IFS=$_G_save - func_append _G_mini "AC_DEFUN([$_G_macro])$nl" - done - IFS=$_G_save - - # We discard M4's stdout, but the M4 trace output from reading our - # "autoconf.mini" followed by any other files passed to this - # function is then scanned by sed to transform it into a colon - # delimited argument list assigned to a shell variable. - _G_transform='s|#.*$||; s|^dnl .*$||; s| dnl .*$||;' - - # Unfortunately, alternation in regexp addresses doesn't work in at - # least BSD (and hence Mac OS X) sed, so we have to append a capture - # and print block for each traced macro to the sed transform script. - _G_save=$IFS - IFS=, - for _G_macro in $_G_macros; do - IFS=$_G_save - func_append _G_transform ' - /^m4trace: -1- '"$_G_macro"'/ { - s|^m4trace: -1- '"$_G_macro"'[([]*|| - s|], [[]|:|g - s|[])]*$|:| - s|\(.\):$|\1| - p - }' - done - IFS=$_G_save - - # Save the command pipeline results for further use by callers of - # this function. - func_extract_trace_result=`$bs_echo "$_G_mini" \ - |$M4 -daq --prefix $_G_m4_traces - "$@" 2>&1 1>/dev/null \ - |$SED -n -e "$_G_transform"` -} - - -# func_extract_trace_first MACRO_NAMES [FILENAME]... -# -------------------------------------------------- -# Exactly like func_extract_trace, except that only the first argument -# to the first invocation of one of the comma separated MACRO_NAMES is -# returned in '$func_extract_trace_first_result'. -func_extract_trace_first () -{ - $debug_cmd - - func_extract_trace ${1+"$@"} - func_extract_trace_first_result=`$bs_echo "$func_extract_trace_result" \ - |$SED -e 's|:.*$||g' -e 1q` -} - - -# func_main [ARG]... -# ------------------ -func_main () -{ - $debug_cmd - - # Configuration. - usage='$progname MACRO_NAME FILE [...]' - - long_help_message=' -The first argument to this program is the name of an autotools macro -whose arguments you want to extract by examining the files listed in the -remaining arguments using the same tool that Autoconf and Automake use, -GNU M4. - -The arguments are returned separated by colons, with each traced call -on a separate line.' - - # Option processing. - func_options "$@" - eval set dummy "$func_options_result"; shift - - # Validate remaining non-option arguments. - test $# -gt 1 \ - || func_fatal_help "not enough arguments" - - # Pass non-option arguments to extraction function. - func_extract_trace "$@" - - # Display results. - test -n "$func_extract_trace_result" \ - && $bs_echo "$func_extract_trace_result" - - # The End. - exit $EXIT_SUCCESS -} - - -## --------------------------- ## -## Actually perform the trace. ## -## --------------------------- ## - -# Only call 'func_main' if this script was called directly. -test extract-trace = "$progname" && func_main "$@" - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: - -# Set a version string for *this* script. -scriptversion=2014-01-01.22; # UTC - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Originally written by Paul Eggert. The canonical version of this -# script is maintained as build-aux/bootstrap in gnulib, however, to -# be useful to your project, you should place a copy of it under -# version control in the top-level directory of your project. The -# intent is that all customization can be done with a bootstrap.conf -# file also maintained in your version control; gnulib comes with a -# template build-aux/bootstrap.conf to get you started. - -# Please report bugs or propose patches to bug-gnulib@gnu.org. - - -## ------ ## -## Usage. ## -## ------ ## - -# Most GNUish projects do not keep all of the generated Autotool -# files under version control, but running all of the right tools -# with the right arguments, in the correct order to regenerate -# all of those files in readiness for configuration and building -# can be surprisingly involved! Many projects have a 'bootstrap' -# script under version control to invoke Autotools and perform -# other assorted book-keeping with version numbers and the like. -# -# This bootstrap script aims to probe the configure.ac and top -# Makefile.am of your project to automatically determine what -# the correct ordering and arguments are and then run the tools for -# you. In order to use it, you can generate an initial standalone -# script with: -# -# gl/build-aux/inline-source gl/build-aux/bootstrap.in > bootstrap -# -# You should then store than script in version control for other -# developers in you project. It will give you instructions about -# how to keep it up to date if the sources change. -# -# See gl/doc/bootstrap.texi for documentation on how to write -# a bootstrap.conf to customize it for your project's -# idiosyncracies. - - -## ================================================================== ## -## ## -## DO NOT EDIT THIS FILE, CUSTOMIZE IT USING A BOOTSTRAP.CONF ## -## ## -## ================================================================== ## - -## ------------------------------- ## -## User overridable command paths. ## -## ------------------------------- ## - -# All uppercase denotes values stored in the environment. These -# variables should generally be overridden by the user - however, we do -# set them to 'true' in some parts of this script to prevent them being -# called at the wrong time by other tools that we call ('autoreconf', -# for example). -# -# We also allow 'LIBTOOLIZE', 'M4', 'SHA1SUM' and some others to be -# overridden, and export the result for child processes, but they are -# handled by the function 'func_find_tool' and not defaulted in this -# section. + func_append _G_mini "AC_DEFUN([$_G_macro])$nl" + done + IFS=$_G_save -: ${ACLOCAL="aclocal"} -: ${AUTOCONF="autoconf"} -: ${AUTOHEADER="autoheader"} -: ${AUTOM4TE="autom4te"} -: ${AUTOHEADER="autoheader"} -: ${AUTOMAKE="automake"} -: ${AUTOPOINT="autopoint"} -: ${AUTORECONF="autoreconf"} -: ${CMP="cmp"} -: ${CONFIG_SHELL="/bin/sh"} -: ${DIFF="diff"} -: ${GIT="git"} -: ${LN_S="ln -s"} -: ${RM="rm"} + # We discard M4's stdout, but the M4 trace output from reading our + # "autoconf.mini" followed by any other files passed to this + # function is then scanned by sed to transform it into a colon + # delimited argument list assigned to a shell variable. + _G_transform='s|#.*$||; s|^dnl .*$||; s| dnl .*$||;' -export ACLOCAL -export AUTOCONF -export AUTOHEADER -export AUTOM4TE -export AUTOHEADER -export AUTOMAKE -export AUTOPOINT -export AUTORECONF -export CONFIG_SHELL + # Unfortunately, alternation in regexp addresses doesn't work in at + # least BSD (and hence Mac OS X) sed, so we have to append a capture + # and print block for each traced macro to the sed transform script. + _G_save=$IFS + IFS=, + for _G_macro in $_G_macros; do + IFS=$_G_save + func_append _G_transform ' + /^m4trace: -1- '"$_G_macro"'/ { + s|^m4trace: -1- '"$_G_macro"'[([]*|| + s|], [[]|:|g + s|[])]*$|:| + s|\(.\):$|\1| + p + }' + done + IFS=$_G_save + # Save the command pipeline results for further use by callers of + # this function. + func_extract_trace_result=`$ECHO "$_G_mini" \ + |$M4 -daq --prefix $_G_m4_traces - "$@" 2>&1 1>/dev/null \ + |$SED -n -e "$_G_transform"` +} -## -------------- ## -## Configuration. ## -## -------------- ## -# A newline delimited list of triples of programs (that respond to -# --version), the minimum version numbers required (or just '-' in the -# version field if any version will be sufficient) and homepage URLs -# to help locate missing packages. -buildreq= +# func_extract_trace_first MACRO_NAMES [FILENAME]... +# -------------------------------------------------- +# Exactly like func_extract_trace, except that only the first argument +# to the first invocation of one of the comma separated MACRO_NAMES is +# returned in '$func_extract_trace_first_result'. +func_extract_trace_first () +{ + $debug_cmd -# Name of a file containing instructions on installing missing packages -# required in 'buildreq'. -buildreq_readme=README-hacking + func_extract_trace ${1+"$@"} + func_extract_trace_first_result=`$ECHO "$func_extract_trace_result" \ + |$SED -e 's|:.*$||g' -e 1q` +} -# These are extracted from AC_INIT in configure.ac, though you can -# override those values in 'bootstrap.conf' if you prefer. -build_aux= -macro_dir= -package= -package_name= -package_version= -package_bugreport= -# These are extracted from 'gnulib-cache.m4', or else fall-back -# automatically on the gnulib defaults; unless you set the values -# manually in 'bootstrap.conf'. -doc_base= -gnulib_mk= -gnulib_name= -local_gl_dir= -source_base= -tests_base= +# func_main [ARG]... +# ------------------ +func_main () +{ + $debug_cmd -# The list of gnulib modules required at 'gnulib-tool' time. If you -# check 'gnulib-cache.m4' into your repository, then this list will be -# extracted automatically. -gnulib_modules= + # Configuration. + usage='$progname MACRO_NAME FILE [...]' -# Extra gnulib files that are not in modules, which override files of -# the same name installed by other bootstrap tools. -gnulib_non_module_files=" - build-aux/compile - build-aux/install-sh - build-aux/mdate-sh - build-aux/texinfo.tex - build-aux/depcomp - build-aux/config.guess - build-aux/config.sub - doc/INSTALL -" + long_help_message=' +The first argument to this program is the name of an autotools macro +whose arguments you want to extract by examining the files listed in the +remaining arguments using the same tool that Autoconf and Automake use, +GNU M4. -# Relative path to the local gnulib submodule, and url to the upstream -# git repository. If you have a gnulib entry in your .gitmodules file, -# these values are ignored. -gnulib_path= -gnulib_url= +The arguments are returned separated by colons, with each traced call +on a separate line.' -# Additional gnulib-tool options to use. -gnulib_tool_options=" - --no-changelog -" + # Option processing. + func_options "$@" + eval set dummy "$func_options_result"; shift -# bootstrap removes any macro-files that are not included by aclocal.m4, -# except for files listed in this variable that are always kept. -gnulib_precious=" - gnulib-tool.m4 -" + # Validate remaining non-option arguments. + test $# -gt 1 \ + || func_fatal_help "not enough arguments" -# When truncating long commands for display, always allow at least this -# many characters before truncating. -min_cmd_len=160 + # Pass non-option arguments to extraction function. + func_extract_trace "$@" -# The command to download all .po files for a specified domain into -# a specified directory. Fill in the first %s is the domain name, and -# the second with the destination directory. Use rsync's -L and -r -# options because the latest/%s directory and the .po files within are -# all symlinks. -po_download_command_format=\ -"rsync --delete --exclude '*.s1' -Lrtvz \ -'translationproject.org::tp/latest/%s/' '%s'" + # Display results. + test -n "$func_extract_trace_result" \ + && $ECHO "$func_extract_trace_result" -# Other locale categories that need message catalogs. -extra_locale_categories= + # The End. + exit $EXIT_SUCCESS +} -# Additional xgettext options to use. Gnulib might provide you with an -# extensive list of additional options to append to this, but gettext -# 0.16.1 and newer appends them automaticaly, so you can safely ignore -# the complaints from 'gnulib-tool' if your $configure_ac states: -# -# AM_GNU_GETTEXT_VERSION([0.16.1]) -xgettext_options=" - --flag=_:1:pass-c-format - --flag=N_:1:pass-c-format -" -# Package copyright holder for gettext files. Defaults to FSF if unset. -copyright_holder= +## --------------------------- ## +## Actually perform the trace. ## +## --------------------------- ## -# File that should exist in the top directory of a checked out hierarchy, -# but not in a distribution tarball. -checkout_only_file= +# Only call 'func_main' if this script was called directly. +test extract-trace = "$progname" && func_main "$@" -# Whether to use copies instead of symlinks by default (if set to true, -# the --copy option has no effect). -copy=false +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "20/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: -# Set this to ".cvsignore .gitignore" in 'bootstrap.conf' if you want -# those files to be generated in directories like 'lib/', 'm4/', and 'po/', -# or set it to "auto" to make this script select what to use based -# on what version control system (if any) is used in the source directory. -# Or set it to "none" to ignore VCS ignore files entirely. Default is -# "auto". -vc_ignore= +# Set a version string for *this* script. +scriptversion=2014-01-04.01; # UTC ## ------------------- ## @@ -2720,6 +2886,14 @@ func_reconfigure () { $debug_cmd + $require_automake_options + + # Automake (without 'foreign' option) requires that README exists. + case " $automake_options " in + " foreign ") ;; + *) func_ensure_README ;; + esac + # Ensure ChangeLog presence. if test -n "$gnulib_modules"; then func_ifcontains "$gnulib_modules" gitlog-to-changelog \ @@ -2734,7 +2908,7 @@ func_reconfigure () fi # Released 'autopoint' has the tendency to install macros that have - # been obsoleted in current 'gnulib., so run this before 'gnulib-tool'. + # been obsoleted in current 'gnulib', so run this before 'gnulib-tool'. func_autopoint # Autoreconf runs 'aclocal' before 'libtoolize', which causes spurious @@ -2903,6 +3077,52 @@ func_gettext_configuration () +# slingshot_copy_files +# -------------------- +# Update files from slingshot subproject. +slingshot_copy_files () +{ + $debug_cmd + + $require_package + + test slingshot = "$package" || { + func_check_configuration slingshot_files + + $require_slingshot_submodule + + # Make sure we have the latest mkrockspecs + make -C slingshot build-aux/mkrockspecs + + # Update in-tree links. + for file in $slingshot_files; do + func_dirname_and_basename "./$file" + slingshot_copy "$func_basename_result" \ + "slingshot/$func_dirname_result" "$func_dirname_result" + done + } +} +func_add_hook func_prep slingshot_copy_files + + +# slingshot_ensure_changelog +# -------------------------- +# Slingshot project probably won't have a gnulib_modules list. +# So we redo the ChangeLog check against slingshot_files. +slingshot_ensure_changelog () +{ + $debug_cmd + + if test -n "$slingshot_files"; then + func_ifcontains "$slingshot_files" build-aux/gitlog-to-changelog \ + func_ensure_changelog + fi + + return 0 +} +func_add_hook func_prep slingshot_ensure_changelog + + ## --------------- ## ## Core functions. ## ## --------------- ## @@ -3046,12 +3266,44 @@ EOT } -# func_autoreconf -# --------------- +# func_ensure_README +# ------------------ +# Without AM_INIT_AUTOMAKE([foreign]), automake will not run to +# completion with no README file, even though README.md or README.txt +# is often preferable. +func_ensure_README () +{ + $debug_cmd + + test -f README || { + _G_README= + for _G_readme in README.txt README.md README.rst; do + test -f "$_G_readme" && break + done + + test -f "$_G_readme" && $LN_S $_G_readme README + func_verbose "$LN_S $_G_readme README" + } + + return 0 +} + + +# func_autoreconf [SUBDIR] +# ------------------------ # Being careful not to re-run 'autopoint' or 'libtoolize', and not to # try to run 'autopoint', 'libtoolize' or 'autoheader' on packages that # don't use them, defer to 'autoreconf' for execution of the remaining # autotools to bootstrap this package. +# +# Projects with multiple trees to reconfigure can hook another call to +# this function onto func_reconfigure: +# +# my_autoreconf_foo () +# { +# func_autoreconf foo +# } +# func_add_hook func_reconfigure my_autoreconf_foo func_autoreconf () { $debug_cmd @@ -3068,7 +3320,7 @@ func_autoreconf () $opt_copy || func_append _G_autoreconf_options " --symlink" $opt_force && func_append _G_autoreconf_options " --force" $opt_verbose && func_append _G_autoreconf_options " --verbose" - func_show_eval "$AUTORECONF$_G_autoreconf_options --install" 'exit $?' + func_show_eval "$AUTORECONF$_G_autoreconf_options --install${1+ $1}" 'exit $?' AUTOPOINT=$save_AUTOPOINT LIBTOOLIZE=$save_LIBTOOLIZE @@ -3293,6 +3545,21 @@ func_require_autoheader () } +# require_automake_options +# ------------------------ +# Extract options from AM_AUTOMAKE_INIT. +require_automake_options=func_require_automake_options +func_require_automake_options () +{ + $debug_cmd + + func_extract_trace AM_INIT_AUTOMAKE + automake_options=$func_extract_trace_result + + require_automake_options=: +} + + # require_autopoint # ----------------- # Skip autopoint if it's not needed. @@ -3347,17 +3614,19 @@ Please add bootstrap to your gnulib_modules list in 'bootstrap.conf', so that I can tell you when there are updates available." else + rm -f bootstrap.new $build_aux/inline-source $build_aux/bootstrap.in > bootstrap.new if func_cmp_s "$progpath" bootstrap.new; then rm -f bootstrap.new func_verbose "bootstrap script up to date" else + chmod 555 bootstrap.new func_warning upgrade "\ An updated bootstrap script has been generated for you in 'bootstrap.new'. After you've verified that you want the changes, you can update with: - cat bootstrap.new > $progname + mv -f bootstrap.new $progname ./$progname Or you can disable this check permanently by adding the @@ -3486,7 +3755,7 @@ func_require_buildreq_automake () # ...and AM_INIT_AUTOMAKE is declared... test -n "$func_extract_trace_result" && { - automake_version=`$bs_echo "$func_extract_trace_result" \ + automake_version=`$ECHO "$func_extract_trace_result" \ |$SED -e 's|[^0-9]*||' -e 's| .*$||'` test -n "$automake_version" || automake_version=- @@ -4087,7 +4356,6 @@ func_require_macro_dir () # require_makefile_am # ------------------- # Ensure there is a 'Makefile.am' in the current directory. -# names an existing file. require_makefile_am=func_require_makefile_am func_require_makefile_am () { @@ -4292,9 +4560,9 @@ func_require_vc_ignore_files () } -## ------------------## +## ----------------- ## ## Helper functions. ## -## ------------------## +## ----------------- ## # This section contains the helper functions used by the rest of 'bootstrap'. @@ -4404,7 +4672,7 @@ func_strpad () $debug_cmd _G_width=`expr "$2" - 1` - func_strpad_result=`$bs_echo "$1" |$SED ' + func_strpad_result=`$ECHO "$1" |$SED ' :a s|^.\{0,'"$_G_width"'\}$|&'"$3"'| ta @@ -4421,7 +4689,7 @@ func_strrpad () $debug_cmd _G_width=`expr "$2" - 1` - func_strrpad_result=`$bs_echo "$1" |$SED ' + func_strrpad_result=`$ECHO "$1" |$SED ' :a s|^.\{0,'"$_G_width"'\}$|'"$3"'&| ta @@ -4506,7 +4774,7 @@ func_strtable () # Strip off the indent, and make a divider with '-' chars, then # reindent. - _G_divider=`$bs_echo "$func_strrow_result" \ + _G_divider=`$ECHO "$func_strrow_result" \ |$SED 's|[^ ]|-|g :a s|- |--|g @@ -4663,7 +4931,7 @@ func_insert_if_absent () || func_verbose "inserting '$str' into '$file'" linesold=`func_gitignore_entries "$file" |wc -l` - linesnew=`{ $bs_echo "$str"; cat "$file"; } \ + linesnew=`{ $ECHO "$str"; cat "$file"; } \ |func_gitignore_entries |sort -u |wc -l` test "$linesold" -eq "$linesnew" \ || { $SED "1i\\$nl$str$nl" "$file" >"$file"T && mv "$file"T "$file"; } \ @@ -4684,7 +4952,7 @@ func_get_version () # Rather than uncomment the sed script in-situ, strip the comments # programatically before passing the result to $SED for evaluation. - sed_get_version=`$bs_echo '# extract version within line + sed_get_version=`$ECHO '# extract version within line s|.*[v ]\{1,\}\([0-9]\{1,\}\.[.a-z0-9-]*\).*|\1| t done @@ -5048,7 +5316,7 @@ exit ${exit_status-$EXIT_SUCCESS} # Local variables: # mode: shell-script # sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-pattern: "20/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "500/scriptversion=%:y-%02m-%02d.%02H; # UTC" # time-stamp-time-zone: "UTC" # End: diff --git a/bootstrap.conf b/bootstrap.conf index 65cfaab..440f046 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -1,4 +1,4 @@ -# bootstrap.conf (Stdlib) version 2013-05-06 +# bootstrap.conf (Stdlib) version 2014-01-04 # # Copyright (C) 2013 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 @@ -61,39 +61,10 @@ gnulib_tool=true require_bootstrap_uptodate=: -## -------------------------------- ## -## Source Slingshot customisations. ## -## -------------------------------- ## - -# Integrate the Slingshot submodule bootstrap. -# Make sure that bootstrap.slingshot is sourced from the current -# directory if we were invoked with "sh bootstrap". -case $0 in - */*) . "$0.slingshot" ;; - *) . ./"$0.slingshot" ;; -esac - - -## --------------- ## -## Hook functions. ## -## --------------- ## - -# stdlib_force_changelog -# ---------------------- -# Automake requires that ChangeLog exist. -stdlib_force_changelog () -{ - $debug_cmd - - echo "Autogenerated by 'make dist'" > ChangeLog || exit 1 -} -func_add_hook func_gnulib_tool stdlib_force_changelog - - # Local variables: # mode: shell-script # sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) +# eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "# bootstrap.conf (Stdlib) version " # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "$" diff --git a/bootstrap.slingshot b/bootstrap.slingshot deleted file mode 100644 index 9e79586..0000000 --- a/bootstrap.slingshot +++ /dev/null @@ -1,282 +0,0 @@ -# bootstrap.slingshot (Slingshot) version 2013-05-06 -# -# Copyright (C) 2013-2014 Gary V. Vaughan -# Written by Gary V. Vaughan, 2013 - -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Libtool; see the file COPYING. If not, a copy -# can be downloaded from http://www.gnu.org/licenses/gpl.html, -# or obtained by writing to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -# For your project to work with subproject slingshot out of the box, you'll -# need to commit this file to your project's repository and source it from -# bootstrap.conf. -# -# case $0 in -# */*) . "$0.slingshot" ;; -# *) . ./"$0.slingshot" ;; -# esac - - -## -------------- ## -## Configuration. ## -## -------------- ## - -# List of slingshot files to link into stdlib tree before autotooling. -slingshot_files=$slingshot_files - -# Relative path to the local slingshot submodule, and url to the upsream -# git repository. If you have a slingshot entry in your .gitmodules file, -# these values are ignored. -slingshot_path=$slingshot_path -slingshot_url=$slingshot_url - - -## ------------------ ## -## Utility functions. ## -## ------------------ ## - -# slingshot_copy FILENAME SRCDIR DESTDIR -# -------------------------------------- -# If option '--copy' was specified, or soft-linking SRCFILE to DESTFILE -# fails, then try to copy SRCFILE to DESTFILE (making sure to update the -# timestamp so that a series of files with dependencies can be copied -# in the right order that their timestamps won't trigger rebuilds). -slingshot_copy () -{ - $debug_cmd - - slingshot_srcfile=`echo "$2/$1" |sed -e 's|/\./|/|g'` - slingshot_destfile=`echo "$3/$1" |sed -e 's|/\./|/|g'` - - $opt_force || { - # Nothing to do if the files are already identical. - if func_cmp_s "$slingshot_srcfile" "$slingshot_destfile"; then - func_verbose "'$slingshot_destfile' is up to date." - return 0 - fi - } - - # Require --force to remove existing $slingshot_destfile. - $opt_force && $RM "$slingshot_destfile" - test -f "$slingshot_destfile" && { - func_warn_and_continue "'$slingshot_destfile' exists: use '--force' to overwrite" - return 0 - } - - # Be careful to support 'func_copy dir/target srcbase destbase'. - func_dirname "$slingshot_destfile" - func_mkdir_p "$func_dirname_result" - - # Copy or link according to '--copy' option. - if $opt_copy; then - slingshot_copycmd=$CP - slingshot_copy_type=copying - else - slingshot_copycmd=$LN_S - slingshot_copy_type=linking - - func_relative_path "$3" "$2" - slingshot_srcfile=$func_relative_path_result/$1 - fi - slingshot_copy_msg="$slingshot_copy_type file '$slingshot_destfile'" - $opt_verbose && \ - slingshot_copy_msg="$slingshot_copy_type $slingshot_srcfile $3" - - if $opt_dry_run || { - ( umask 0 - $slingshot_copycmd "$slingshot_srcfile" "$slingshot_destfile" - ) >/dev/null 2>&1 - } - then - echo "$slingshot_copy_msg" - else - func_error "$slingshot_copy_type '$2/$1' to '$3/' failed" - return 1 - fi -} - - -## --------------- ## -## Hook functions. ## -## --------------- ## - -# slingshot_copy_files -# -------------------- -# Update files from slingshot subproject. -slingshot_copy_files () -{ - $debug_cmd - - func_check_configuration slingshot_files - - $require_slingshot_submodule - - # Make sure we have the latest mkrockspecs - make -C slingshot build-aux/mkrockspecs - - # Update in-tree links. - for file in $slingshot_files; do - func_dirname_and_basename "./$file" - slingshot_copy "$func_basename_result" \ - "slingshot/$func_dirname_result" "$func_dirname_result" - done -} -func_add_hook func_prep slingshot_copy_files - - -## -------------------- ## -## Resource management. ## -## -------------------- ## - -# require_slingshot_dotgitmodules -# ------------------------------- -# Ensure we have a '.gitmodules' file, with appropriate 'slingshot' settings. -require_slingshot_dotgitmodules=slingshot_require_slingshot_dotgitmodules -slingshot_require_slingshot_dotgitmodules () -{ - $debug_cmd - - $require_git - - test true = "$GIT" || { - # A slingshot entry in .gitmodules always takes precedence. - _G_path=`$GIT config --file .gitmodules submodule.slingshot.path 2>/dev/null` - - test -n "$_G_path" || { - $require_vc_ignore_files - - func_verbose "adding slingshot entries to '.gitmodules'" - - test -n "$slingshot_path" || slingshot_path=slingshot - test -n "$slingshot_url" || slingshot_url=git://github.com/gvvaughan/slingshot.git - - { - echo '[submodule "slingshot"]' - echo " path=$slingshot_path" - echo " url=$slingshot_url" - } >> .gitmodules - - test -n "$vc_ignore_files" \ - || func_insert_if_absent ".gitmodules" $vc_ignore_files - } - } - - require_slingshot_dotgitmodules=: -} - - -# require_slingshot_path -# require_slingshot_url -# ---------------------- -# Ensure 'slingshot_path' and 'slingshot_url' are set. -require_slingshot_path=slingshot_require_slingshot_dotgitmodules_parameters -require_slingshot_url=slingshot_require_slingshot_dotgitmodules_parameters -slingshot_require_slingshot_dotgitmodules_parameters () -{ - $debug_cmd - - $require_git - $require_slingshot_dotgitmodules - - test -f .gitmodules \ - || func_fatal_error "Unable to update '.gitmodules' with slingshot submodule" - - test true = "$GIT" || { - slingshot_path=`$GIT config --file=.gitmodules --get submodule.slingshot.path` - slingshot_url=`$GIT config --file=.gitmodules --get submodule.slingshot.url` - - func_verbose "slingshot_path='$slingshot_path'" - func_verbose "slingshot_url='$slingshot_url'" - } - - require_slingshot_path=: - require_slingshot_url=: -} - - -# require_slingshot_submodule -# --------------------------- -# Ensure that there is a current slingshot submodule. -require_slingshot_submodule=slingshot_require_slingshot_submodule -slingshot_require_slingshot_submodule () -{ - $debug_cmd - - $require_git - - if test true = "$GIT"; then - func_warning recommend \ - "No 'git' found; imported slingshot modules may be missing." - else - $require_slingshot_dotgitmodules - - if test -f .gitmodules && test -f "slingshot/src/mkrockspecs.in" - then - : All present and correct. - - else - $require_slingshot_path - $require_slingshot_url - - trap slingshot_cleanup 1 2 13 15 - - shallow= - $GIT clone -h 2>&1 |func_grep_q -- --depth \ - && shallow='--depth 365' - - func_show_eval "$GIT clone $shallow '$slingshot_url' '$slingshot_path'" \ - slingshot_cleanup - - # FIXME: Solaris /bin/sh will try to execute '-' if any of - # these signals are caught after this. - trap - 1 2 13 15 - - # Make sure we've checked out the correct revision of slingshot. - func_show_eval "$GIT submodule init" \ - && func_show_eval "$GIT submodule update" \ - || func_fatal_error "Unable to update slingshot submodule." - fi - fi - - require_slingshot_submodule=: -} - - -# slingshot_cleanup -# ----------------- -# Recursively delete everything at $slingshot_path. -slingshot_cleanup () -{ - $debug_cmd - - $require_slingshot_path - - _G_status=$? - $RM -fr $slingshot_path - exit $_G_status -} - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "# bootstrap.slingshot (Slingshot) version " -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-end: "$" -# End: diff --git a/configure.ac b/configure.ac index b3108b2..d27b467 100644 --- a/configure.ac +++ b/configure.ac @@ -8,7 +8,7 @@ AC_CONFIG_MACRO_DIR([m4]) AS_BOX([Configuring AC_PACKAGE_TARNAME AC_PACKAGE_VERSION]) echo -AM_INIT_AUTOMAKE([-Wall foreign]) +AM_INIT_AUTOMAKE([-Wall]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) dnl Check for programs diff --git a/slingshot b/slingshot index 79b7de2..235cd03 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 79b7de2c47a41a4f2f7a7d1b2c1a59ffbeb8f923 +Subproject commit 235cd03e808b8d5689962213c45e5fe904134e26 From e6b1cd992e9c89ad32f1bb4b9a3cb46f9f87a25d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 4 Jan 2014 15:14:37 +1300 Subject: [PATCH 037/703] maint: fix copyright attribution in COPYING. * COPYING: Correct year and authors in copyright statement. Signed-off-by: Gary V. Vaughan --- COPYING | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COPYING b/COPYING index 3db866f..8aecd8e 100644 --- a/COPYING +++ b/COPYING @@ -4,7 +4,7 @@ the terms of the MIT license (the same license as Lua itself), unless noted otherwise in the body of that file. ==================================================================== -Copyright (C) 2013-2014 Gary V. Vaughan +Copyright (C) 2002-2014 stdlib authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation From bdec6ec8eb85bd080457d14c907fd775e85bdfc6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 4 Jan 2014 21:07:22 +1300 Subject: [PATCH 038/703] travis: horrid workaround for crashy LDoc master vs apt-get luarocks. * slingshot: Sync for Travis fixes. * .travis.yml: Temporary code to fetch a custom stable version of LDoc that doesn't crash on stdlib doc-comments. Signed-off-by: Gary V. Vaughan --- .travis.yml | 6 +++++- slingshot | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1c9e043..cf6a650 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,12 @@ install: - test -f /usr/lib/libyaml.so || sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; # Luadoc and Ldoc work best on Travis with Lua 5.1. + # LDoc master is unstable and crashy at the moment, this snapshot + # works for me. But apt-get's luarocks doesn't support source.branch + # in rockspec files, so we have to clone manually :( - sudo apt-get install luarocks - - sudo luarocks install http://raw.github.com/gvvaughan/LDoc/next/ldoc-next-1.rockspec + - git clone -b next --depth 1 https://github.com/gvvaughan/LDoc.git + - ( cd LDoc && sudo luarocks make ldoc-next-1.rockspec ) - mkdir -p luarocks/bin - sed 's|^exec "[^"]*"|exec lua5.1|' `which ldoc` > $GENDOC - chmod a+rx $GENDOC diff --git a/slingshot b/slingshot index 235cd03..7367e3f 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 235cd03e808b8d5689962213c45e5fe904134e26 +Subproject commit 7367e3f361bd32f80ab0ac6e36837d0f7932a3bc From aae4084e4cdd362949da4cc78abaf81dd11d4601 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 4 Jan 2014 21:49:33 +1300 Subject: [PATCH 039/703] maint: update copyright notices to include 2014. * .x-update-copyright: New file. Exclude files not owned by this project from update-copyright rules. * boootstrap.conf, configure.ac, local.mk: Bump copyright year. Signed-off-by: Gary V. Vaughan --- .x-update-copyright | 1 + bootstrap.conf | 3 ++- configure.ac | 19 ++++++++++++++++++- local.mk | 22 ++++++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 .x-update-copyright diff --git a/.x-update-copyright b/.x-update-copyright new file mode 100644 index 0000000..fbb3c02 --- /dev/null +++ b/.x-update-copyright @@ -0,0 +1 @@ +^bootstrap$ diff --git a/bootstrap.conf b/bootstrap.conf index 440f046..6104419 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -1,6 +1,6 @@ # bootstrap.conf (Stdlib) version 2014-01-04 # -# Copyright (C) 2013 Gary V. Vaughan +# Copyright (C) 2013-2014 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 # This is free software; see the source for copying conditions. There is NO @@ -47,6 +47,7 @@ slingshot_files=' build-aux/rockspecs.mk build-aux/sanity.mk build-aux/specl.mk + build-aux/update-copyright m4/ax_compare_version.m4 m4/ax_lua.m4 m4/slingshot.m4 diff --git a/configure.ac b/configure.ac index d27b467..7d6a3ab 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,21 @@ -dnl Process this file with autoconf to produce a configure script +dnl configure.ac +dnl +dnl Copyright (c) 2012-2014 Free Software Foundation, Inc. +dnl Written by Gary V. Vaughan, 2012 +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published +dnl by the Free Software Foundation; either version 3, or (at your +dnl option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, but +dnl WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program. If not, see . + dnl Initialise autoconf and automake AC_INIT([stdlib], [36], [http://github.com/rrthomas/lua-stdlib/issues]) diff --git a/local.mk b/local.mk index 468e37b..187f87c 100644 --- a/local.mk +++ b/local.mk @@ -1,4 +1,21 @@ # Local Make rules. +# +# Copyright (C) 2013-2014 Gary V. Vaughan +# Written by Gary V. Vaughan, 2013 +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + ## ------------ ## ## Environment. ## @@ -14,6 +31,11 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" old_NEWS_hash = 7ef01dfb840329db3d8db218bfe9d075 +update_copyright_env = \ + UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ + UPDATE_COPYRIGHT_USE_INTERVALS=1 \ + UPDATE_COPYRIGHT_FORCE=1 + ## ------------- ## ## Declarations. ## From bb9e23b31ef5bd3e242b44ac110025c7747e5724 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 5 Jan 2014 15:26:58 +1300 Subject: [PATCH 040/703] doc: distribute ldoc classes and modules documentation. * local.mk (filesdir, dist_files_DATA): Rename from these... (classesdir, dist_classes_DATA): ...to these. (dist_classes_DATA, dist_modules_DATA): Use correct paths in gmake wildcard expression. Signed-off-by: Gary V. Vaughan --- local.mk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/local.mk b/local.mk index 187f87c..6cd2e00 100644 --- a/local.mk +++ b/local.mk @@ -41,11 +41,11 @@ update_copyright_env = \ ## Declarations. ## ## ------------- ## -filesdir = $(docdir)/files +classesdir = $(docdir)/classes modulesdir = $(docdir)/modules dist_doc_DATA = -dist_files_DATA = +dist_classes_DATA = dist_modules_DATA = include specs/specs.mk @@ -112,8 +112,8 @@ dist_doc_DATA += \ $(srcdir)/doc/index.html \ $(srcdir)/doc/ldoc.css -dist_files_DATA += $(wildcard $(srcdir)/lib/files/*.html) -dist_modules_DATA += $(wildcard $(srcdir)/lib/modules/*.html) +dist_classes_DATA += $(wildcard $(srcdir)/doc/classes/*.html) +dist_modules_DATA += $(wildcard $(srcdir)/doc/modules/*.html) $(dist_doc_DATA): $(dist_lua_DATA) $(dist_luastd_DATA) cd $(srcdir) && $(LDOC) -c doc/config.ld . From d9615682ed80b823e5a161bcd053af630c906766 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 5 Jan 2014 17:21:07 +1300 Subject: [PATCH 041/703] slingshot: sync with upstream. Fix the annoying contest.sed file dropping bug. * slingshot: Sync with upstream. * bootstrap: Sync with slingshot. Signed-off-by: Gary V. Vaughan --- bootstrap | 1 + slingshot | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bootstrap b/bootstrap index 048200f..59ad44a 100755 --- a/bootstrap +++ b/bootstrap @@ -436,6 +436,7 @@ test -z "$SED" && { } func_path_progs "sed gsed" func_check_prog_sed $PATH:/usr/xpg4/bin + rm -f conftest.sed SED=$func_path_progs_result } diff --git a/slingshot b/slingshot index 7367e3f..005c9a4 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 7367e3f361bd32f80ab0ac6e36837d0f7932a3bc +Subproject commit 005c9a4683f9a95492c5b5d62106a1a8910707b4 From 46d1f06b2de128d34a512ee09a8d61ae1ca7b8e0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 7 Jan 2014 16:12:31 +1300 Subject: [PATCH 042/703] slingshot: sync with upstream, for rockspecs in $buildreq. * slingshot: Sync with upstream. * bootstrap: Sync with slingshot. * configure.ac (SPECL_MIN): Remove. * bootstrap.conf (buildreq): Add ldoc and specl rockspecs. * .travis.yml: Regenerate, but manually splice in the LDoc-1.4.0 luarocks install while waiting for a luarocks release. Signed-off-by: Gary V. Vaughan --- .travis.yml | 48 ++++---- bootstrap | 292 +++++++++++++++++++++++++++++++++++++++++++++++-- bootstrap.conf | 4 +- configure.ac | 1 - slingshot | 2 +- 5 files changed, 316 insertions(+), 31 deletions(-) diff --git a/.travis.yml b/.travis.yml index cf6a650..6f3f7be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,6 @@ env: - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - LUAROCKS_BASE=luarocks-2.1.1 - LUAROCKS="$LUA $HOME/bin/luarocks" - - GENDOC=luarocks/bin/ldoc - - SPECL=bin/specl matrix: - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 LUA_SUFFIX=5.1 - LUA=lua5.2 LUA_INCDIR=/usr/include/lua5.2 LUA_SUFFIX=5.2 @@ -17,6 +15,10 @@ env: # Tool setup. install: + # Put back the links for libyaml, which are missing on recent Travis VMs + - test -f /usr/lib/libyaml.so || + sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; + - sudo apt-get install help2man - sudo apt-get install luajit - sudo apt-get install libluajit-5.1-dev @@ -24,19 +26,6 @@ install: - sudo apt-get install liblua5.1-dev - sudo apt-get install lua5.2 - sudo apt-get install liblua5.2-dev - # Put back the links for libyaml, which missing on recent Travis VMs - - test -f /usr/lib/libyaml.so || - sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; - # Luadoc and Ldoc work best on Travis with Lua 5.1. - # LDoc master is unstable and crashy at the moment, this snapshot - # works for me. But apt-get's luarocks doesn't support source.branch - # in rockspec files, so we have to clone manually :( - - sudo apt-get install luarocks - - git clone -b next --depth 1 https://github.com/gvvaughan/LDoc.git - - ( cd LDoc && sudo luarocks make ldoc-next-1.rockspec ) - - mkdir -p luarocks/bin - - sed 's|^exec "[^"]*"|exec lua5.1|' `which ldoc` > $GENDOC - - chmod a+rx $GENDOC # Install a recent luarocks release locally for everything else. - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz - tar zxvpf $LUAROCKS_BASE.tar.gz @@ -49,19 +38,38 @@ install: # Configure and build. script: - - ./bootstrap + # Initial bootstrap to build luarocks-config.lua, before we've + # installed our rocks. + - ./bootstrap --skip-rock-checks - ./configure LUA="$LUA" - make $LUAROCKS_CONFIG LUA="$LUA" LUA_INCDIR="$LUA_INCDIR" V=1 || cat $LUAROCKS_CONFIG config.log + # Set Lua and Shell paths up for local luarocks tree. + # this package depends on will be installed. - eval `$LUAROCKS path` - export PATH=`pwd`/luarocks/bin:$PATH + + # Install extra rocks into $LUAROCKS_CONFIG rocks tree. - $LUAROCKS install lyaml; $LUAROCKS install specl; + # LDoc 1.4.0 is not in luarocks yet, and LDoc master has no rockspec :( + - $LUAROCKS install http://raw.github.com/gvvaughan/LDoc/next/ldoc-next-1.rockspec + + # Make git rockspec for this package. - make rockspecs LUAROCKS="$LUAROCKS" V=1 || { $LUAROCKS path; cat $ROCKSPEC; } - # LuaRocks make will fail if dependencies are missing. + + # The git rockspec will rerun bootstrap, and check any rock versions + # in bootstrap.conf:buildreq this time. - $LUAROCKS make $ROCKSPEC LUA="$LUA" - # Use bin/specl if we built it, or else the specl rock we just installed. - - test -f "$SPECL" || SPECL=luarocks/bin/specl; - LUA_PATH=`pwd`'/lib/?.lua;'"${LUA_PATH-;}" make check SPECL="$SPECL" V=1 + + # Run self-tests in the `luarocks make` build tree. + # Use bin/specl if present, or else the specl rock from EXTRA_ROCKS + # above. + - if test -f luarocks/bin/specl; then export SPECL=luarocks/bin/specl; + elif test -f bin/specl; then export SPECL=bin/specl; fi + - export LUA_PATH=`pwd`'/lib/?.lua;'"${LUA_PATH-;}" + - export LUA_CPATH=`pwd`'/ext/?.so;'"${LUA_CPATH-;}" + - export LUA_INIT=; export LUA_INIT_5_2= + - make check SPECL="$SPECL" V=1 diff --git a/bootstrap b/bootstrap index 59ad44a..82ee636 100755 --- a/bootstrap +++ b/bootstrap @@ -109,6 +109,11 @@ export AUTORECONF export CONFIG_SHELL +: ${LUAROCKS="luarocks"} + +export LUAROCKS + + ## -------------- ## ## Configuration. ## ## -------------- ## @@ -233,6 +238,10 @@ slingshot_files=$slingshot_files slingshot_path=$slingshot_path slingshot_url=$slingshot_url +# NOTE: slingshot bootstrap will check rockspecs listed in $buildreq, +# according to the URL part of a specification triple ending in +# `.rockspec`. + ## ------------------- ## ## External Libraries. ## @@ -691,6 +700,44 @@ func_require_term_colors () } +# require_rockspecs_req +# --------------------- +# Remove rockspecs from $buildreq, and add them to $rockspecs_req. +require_rockspecs_req=slingshot_require_rockspecs_req +slingshot_require_rockspecs_req () +{ + $debug_cmd + + test -n "$rockspecs_req" || { + _G_non_rockspecs= + + set dummy $buildreq; shift + + while test $# -gt 2; do + case $3 in + *.rockspec) + func_append rockspecs_req " $1 $2 $3" + ;; + [a-z]*://*) + func_append _G_non_rockspecs " $1 $2 $3" + ;; + *) func_fatal_error "\ +'$3' from the buildreq table in +'bootstrap.conf' does not look like the URL for downloading +$1. Please ensure that buildreq is a strict newline +delimited list of triples; 'program min-version url'." + ;; + esac + shift; shift; shift + done + + buildreq=$_G_non_rockspecs + } + + require_rockspecs_req=: +} + + # require_slingshot_dotgitmodules # ------------------------------- # Ensure we have a '.gitmodules' file, with appropriate 'slingshot' settings. @@ -1675,7 +1722,7 @@ func_lt_ver () #! /bin/sh # Set a version string for this script. -scriptversion=2014-01-04.01; # UTC +scriptversion=2014-01-07.03; # UTC # A portable, pluggable option parser for Bourne shell. # Written by Gary V. Vaughan, 2010 @@ -2097,6 +2144,89 @@ func_validate_options () + +# slingshot_options_prep +# ---------------------- +# Preparation for additional slingshot option parsing. +slingshot_options_prep () +{ + $debug_cmd + + # Option defaults: + opt_skip_rock_checks=false + # opt_luarocks_tree default in *unset*! + + # Extend the existing usage message. + usage_message=$usage_message' +Slingshot Options: + + --luarocks-tree=DIR + check a non-default tree for prerequisite rocks + --skip-rock-checks + ignore Lua rocks in bootstrap.conf:buidreq' + + func_quote_for_eval ${1+"$@"} + slingshot_options_prep_result=$func_quote_for_eval_result +} +func_add_hook func_options_prep slingshot_options_prep + + +# slingshot_parse_options OPT... +# ------------------------------ +# Called at the end of each main option parse loop to process any +# additional slingshot options. +slingshot_parse_options () +{ + $debug_cmd + + # Perform our own loop to consume as many options as possible in + # each iteration. + while test $# -gt 0; do + _G_opt=$1 + shift + case $_G_opt in + --luarocks-tree) + test $# = 0 && func_missing_arg $_G_opt && break + opt_luarocks_tree=$1 + shift + ;; + + --skip-rock-checks) + opt_skip_rock_checks=: + ;; + + # Separate optargs to long options (plugins may need this): + --*=*) func_split_equals "$_G_opt" + set dummy "$func_split_equals_lhs" \ + "$func_split_equals_rhs" ${1+"$@"} + shift + ;; + + *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; + esac + done + + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + slingshot_parse_options_result=$func_quote_for_eval_result +} +func_add_hook func_parse_options slingshot_parse_options + + +# slingshot_option_validation +# --------------------------- +# Flag any inconsistencies in users' selection of slingshot options. +slingshot_option_validation () +{ + $debug_cmd + + test -z "$opt_luarocks_tree" \ + || test -d "$opt_luarocks_tree" \ + || func_fatal_help "$opt_luarocks_tree: not a directory" +} +func_add_hook func_validate_options slingshot_option_validation + + ## ----------------- ## ## Helper functions. ## ## ----------------- ## @@ -2248,7 +2378,7 @@ func_version () $debug_cmd printf '%s\n' "$progname $scriptversion" - $SED -n '/^##/q + $SED -n ' /(C)/!b go :more /\./!{ @@ -2373,6 +2503,21 @@ slingshot_copy () } +# slingshot_rockspec_error +# ------------------------ +# Called by zile_check_rockspecs for missing rocks. +slingshot_rockspec_error () +{ + $debug_cmd + + _G_strippedver=`expr "$_G_reqver" : '=*\(.*\)'` + func_error "\ +Prerequisite LuaRock '$_G_rock $_G_strippedver' not found. Please install it." + + rockspecs_uptodate_result=false +} + + ## ------ ## ## Usage. ## ## ------ ## @@ -3078,6 +3223,104 @@ func_gettext_configuration () +## The section title above is chosen for what section of bootstrap +## these functions will be merged to, so that the invocations of +## `func_add_hook` are guaranteed not to be executed until after +## the hook management functions are defined. + + +# slingshot_split_buildreq +# ------------------------ +# For convenience, let the user add rockspec requirements to $buildreq. +# Note that this is for *build-time* requirements (e.g. ldoc), so that +# make can complete without error. You should add *run-time* rockspec +# requirements (e.g. stdlib) to rockspec.conf. +slingshot_split_buildreq () +{ + $debug_cmd + + $require_rockspecs_req +} +func_add_hook func_init slingshot_split_buildreq + + +# slingshot_check_rockspecs +# ------------------------- +# Check build-time rockspecs from $buildreq are uptodate. +# It would be nice if we could rely on luarock binaries to respond to +# `--version` like GNU apps, but there is no reliable consensus, so we +# have to check installed luarock versions directly, and warn the user +# if the apps we're checking for are not somewhere along PATH. +slingshot_check_rockspecs () +{ + $debug_cmd + + $opt_skip_rock_checks && return + + $require_rockspecs_req + + _G_req= + rockspecs_uptodate_result=: + + set dummy $rockspecs_req; shift + while test $# -gt 0; do + _G_rock=$1; shift + _G_reqver=$1; shift + _G_url=$1; shift + + func_append _G_req " $_G_rock $_G_url" + + # Honor $APP variables ($LDOC, $SPECL, etc.) + _G_appvar=`echo $_G_rock |tr '[a-z]' '[A-Z]'` + eval "_G_rock=\${$_G_appvar-$_G_rock}" + + # Trust the user will ensure the binaries will arive at the + # specified location before they are needed if they set these. + if eval 'test -n "${'$_G_appvar'+set}"'; then + eval test -f '"${'$_G_appvar'}"' \ + || eval 'func_warning settings "\ +not checking whether $'$_G_appvar' has version $_G_reqver; +configure or make may fail because you set $_G_appvar, but +$'$_G_appvar' does not yet exist!"' + else + _G_instver=`$LUAROCKS ${opt_luarocks_tree+--tree=$opt_luarocks_tree} \ + show $_G_rock 2>/dev/null \ + |sed -n '/^'"$_G_rock"' .* - /{s/^'"$_G_rock"' \(.*\) - .*$/\1/p;}'` + + if test -z "$_G_instver"; then + slingshot_rockspec_error + else + func_verbose "found '$_G_rock' version $_G_instver." + + case $_G_reqver in + =*) + test "x$_G_reqver" = "x=$_G_instver" || slingshot_rockspec_error + ;; + *) + func_lt_ver "$_G_reqver" "$_G_instver" || slingshot_rockspec_error + ;; + esac + fi + fi + done + + $rockspecs_uptodate_result || { + func_strtable 0 10 48 \ + "Program" "Rockspec_URL" $_G_req + func_fatal_error "Missing rocks: +$func_strtable_result +Install missing rockspecs with: + $LUAROCKS ${opt_luarocks_tree+--tree=$opt_luarocks_tree }install \$Rockspec_URL +and then rerun bootstrap with the --luarocks-tree option set +appropriately, or if you're sure that the missing rocks will +be installed before running make by exporting: + APPNAME=/path/to/app. +" + } +} +func_add_hook func_prep slingshot_check_rockspecs + + # slingshot_copy_files # -------------------- # Update files from slingshot subproject. @@ -3124,6 +3367,25 @@ slingshot_ensure_changelog () func_add_hook func_prep slingshot_ensure_changelog +# slingshot_check_rockstree_path +# ------------------------------ +# Show a warning at the end of bootstrap if --luarocks-tree was passed +# set, but $opt_luarocks_tree/bin is not in the command PATH. +slingshot_check_rockstree_path () +{ + $debug_cmd + + test -z "$rockspecs_req" || { + case :$PATH: in + *:$opt_luarocks_tree/bin:*) ;; + *) func_warning recommend \ + "If configure or make fail, try adding $opt_luarocks_tree/bin to PATH" ;; + esac + } +} +func_add_hook func_fini slingshot_check_rockstree_path + + ## --------------- ## ## Core functions. ## ## --------------- ## @@ -5051,9 +5313,6 @@ delimited list of triples; 'program min-version url'." else _G_instver=`func_get_version $_G_app` - test -z "$_G_instver" \ - || func_verbose "found '$_G_app' version $_G_instver." - # Fail if --version didn't work. if test -z "$_G_instver"; then func_error "Prerequisite '$_G_app' not found. Please install it, or @@ -5062,12 +5321,29 @@ delimited list of triples; 'program min-version url'." # Fail if a newer version than what we have is required. else - func_lt_ver "$_G_reqver" "$_G_instver" || { - func_error "\ + func_verbose "found '$_G_app' version $_G_instver." + + case $_G_reqver in + =*) + # If $buildreq version starts with '=', version must + # match the installed program exactly. + test "x$_G_reqver" = "x=$_G_instver" || { + func_error "\ + '$_G_app' version == $_G_instver is too old + 'exactly $_G_app-$_G_reqver is required" + func_check_versions_result=false + } + ;; + *) + # Otherwise, anything that is not older is a match. + func_lt_ver "$_G_reqver" "$_G_instver" || { + func_error "\ '$_G_app' version == $_G_instver is too old '$_G_app' version >= $_G_reqver is required" func_check_versions_result=false - } + } + ;; + esac fi fi done diff --git a/bootstrap.conf b/bootstrap.conf index 6104419..d9b0e91 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -32,7 +32,9 @@ # Build prerequisites buildreq=' - git 1.5.5 http://git-scm.com + git 1.5.5 http://git-scm.com + ldoc =next-1 http://raw.github.com/gvvaughan/LDoc/next/ldoc-next-1.rockspec + specl 8 http://luarocks.org/repositories/rocks/specl-9-1.rockspec ' # List of slingshot files to link into stdlib tree before autotooling. diff --git a/configure.ac b/configure.ac index 7d6a3ab..60a4f26 100644 --- a/configure.ac +++ b/configure.ac @@ -36,7 +36,6 @@ AC_PROG_EGREP AC_PROG_SED dnl Generate output files -SPECL_MIN=8 SS_CONFIG_TRAVIS([ldoc specl]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/slingshot b/slingshot index 005c9a4..fee0f22 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 005c9a4683f9a95492c5b5d62106a1a8910707b4 +Subproject commit fee0f224fb94fd28b0cf6eca2ffb14d1553748d8 From 4fd6b6d471dc8fe9c34b4cb8f301bef21c4b1137 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 13 Jan 2014 18:05:57 +1300 Subject: [PATCH 043/703] slingshot: resync for subdirectory bootstrap fix. * slingshot: Sync with upstream. Signed-off-by: Gary V. Vaughan --- slingshot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slingshot b/slingshot index fee0f22..9b61cb9 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit fee0f224fb94fd28b0cf6eca2ffb14d1553748d8 +Subproject commit 9b61cb96d5070df3b6be7cd8726d0e6d44bc82fa From 34111b6a6e8212bc0014e5a1d665673a373fc990 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 13 Jan 2014 19:09:18 +1300 Subject: [PATCH 044/703] configury: use explicit lists instead of $(wildcard ...). * local.mk (dist_classes_DATA, dist_modules_DATA): Only GNU make supports $(wildcard ...) expressions, and Automake complains every time it sees one. List files explicitly. ($(dist_doc_DATA)): Add $(dist_classes_DATA) and $(dist_modules_DATA) to LHS of this rule so that make knows it has to run LDoc to create those files. Signed-off-by: Gary V. Vaughan --- local.mk | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/local.mk b/local.mk index 6cd2e00..23c0ad0 100644 --- a/local.mk +++ b/local.mk @@ -108,12 +108,34 @@ EXTRA_DIST += \ ## Documentation. ## ## -------------- ## + dist_doc_DATA += \ $(srcdir)/doc/index.html \ $(srcdir)/doc/ldoc.css -dist_classes_DATA += $(wildcard $(srcdir)/doc/classes/*.html) -dist_modules_DATA += $(wildcard $(srcdir)/doc/modules/*.html) +dist_classes_DATA += \ + $(srcdir)/doc/classes/std.container.html \ + $(srcdir)/doc/classes/std.list.html \ + $(srcdir)/doc/classes/std.object.html \ + $(srcdir)/doc/classes/std.set.html \ + $(srcdir)/doc/classes/std.strbuf.html \ + $(srcdir)/doc/classes/std.tree.html \ + $(NOTHING_ELSE) + +dist_modules_DATA += \ + $(srcdir)/doc/modules/std.html \ + $(srcdir)/doc/modules/std.debug.html \ + $(srcdir)/doc/modules/std.functional.html \ + $(srcdir)/doc/modules/std.getopt.html \ + $(srcdir)/doc/modules/std.io.html \ + $(srcdir)/doc/modules/std.math.html \ + $(srcdir)/doc/modules/std.package.html \ + $(srcdir)/doc/modules/std.strict.html \ + $(srcdir)/doc/modules/std.string.html \ + $(srcdir)/doc/modules/std.table.html \ + $(NOTHING_ELSE) + +ldoc_DEPS = $(dist_lua_DATA) $(dist_luastd_DATA) -$(dist_doc_DATA): $(dist_lua_DATA) $(dist_luastd_DATA) +$(dist_doc_DATA) $(dist_classes_DATA) $(dist_modules_DATA): $(ldoc_DEPS) cd $(srcdir) && $(LDOC) -c doc/config.ld . From c0c32a4b6bf52f59dd1b8626832bcd0f904c6f1c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 16 Jan 2014 10:48:14 +1300 Subject: [PATCH 045/703] Release version 36 * NEWS: Record release date. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 0e04037..b3fd9f0 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ Stdlib NEWS - User visible changes -* Noteworthy changes in release ?.? (????-??-??) [?] +* Noteworthy changes in release 36 (2014-01-16) [stable] ** New features: From 61c121d31f260bbe4cd8a1796aa35878714e2649 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 16 Jan 2014 10:48:38 +1300 Subject: [PATCH 046/703] maint: post-release administrivia. * NEWS: Add header line for next release. * configure.ac: Bump version number to 37. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS | 3 +++ configure.ac | 2 +- local.mk | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.prev-version b/.prev-version index 8f92bfd..7facc89 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -35 +36 diff --git a/NEWS b/NEWS index b3fd9f0..09a1b21 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Stdlib NEWS - User visible changes +* Noteworthy changes in release ?.? (????-??-??) [?] + + * Noteworthy changes in release 36 (2014-01-16) [stable] ** New features: diff --git a/configure.ac b/configure.ac index 60a4f26..8bde8c8 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [36], [http://github.com/rrthomas/lua-stdlib/issues]) +AC_INIT([stdlib], [37], [http://github.com/rrthomas/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/local.mk b/local.mk index 23c0ad0..b03daf6 100644 --- a/local.mk +++ b/local.mk @@ -29,7 +29,7 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" ## Bootstrap. ## ## ---------- ## -old_NEWS_hash = 7ef01dfb840329db3d8db218bfe9d075 +old_NEWS_hash = 7a7647cb5b2d5d886a18070e1f4ac5fd update_copyright_env = \ UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ From 858210b8eece70a8746acf3ee0c07f5160f45da7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 16 Jan 2014 12:13:03 +1300 Subject: [PATCH 047/703] slingshot: sync with upstream, for bootstrap git detection fix. * slingshot: Sync with upstream. * bootstrap: Sync with slingshot. Signed-off-by: Gary V. Vaughan --- bootstrap | 4 ++-- slingshot | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap b/bootstrap index 82ee636..82dcf5c 100755 --- a/bootstrap +++ b/bootstrap @@ -4206,8 +4206,8 @@ func_require_git () $opt_skip_git && GIT=true test true = "$GIT" || { - if test -f .gitignore && ($GIT --version) >/dev/null 2>&1; then :; else - GIT=true + if test -f .git; then + ($GIT --version) >/dev/null 2>&1 || GIT=true fi } diff --git a/slingshot b/slingshot index 9b61cb9..9818ac1 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 9b61cb96d5070df3b6be7cd8726d0e6d44bc82fa +Subproject commit 9818ac16049ab43c1c3cb2b1d10915a095dd7f1c From 797e39a88762e1bccf3c1865b3690a98076e4e6d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 16 Jan 2014 14:13:00 +1300 Subject: [PATCH 048/703] slingshot: resync for `mkrockspecs --repository` support. * slingshot: Sync with upstream. * local.mk (mkrockspecs_args): Add `--repository lua-stdlib`. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 28 ++++++++++++---------------- local.mk | 5 +++-- slingshot | 2 +- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f3f7be..cead315 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ env: - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - LUAROCKS_BASE=luarocks-2.1.1 - LUAROCKS="$LUA $HOME/bin/luarocks" + - LDOC_ROCKSPEC=http://raw.github.com/gvvaughan/LDoc/next/ldoc-next-1.rockspec matrix: - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 LUA_SUFFIX=5.1 - LUA=lua5.2 LUA_INCDIR=/usr/include/lua5.2 LUA_SUFFIX=5.2 @@ -29,12 +30,11 @@ install: # Install a recent luarocks release locally for everything else. - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz - tar zxvpf $LUAROCKS_BASE.tar.gz - - cd $LUAROCKS_BASE - - ./configure - --prefix=$HOME --lua-version=$LUA_SUFFIX --lua-suffix=$LUA_SUFFIX - --with-lua-include=$LUA_INCDIR - - make all install - - cd .. + - ( cd $LUAROCKS_BASE; + ./configure + --prefix=$HOME --lua-version=$LUA_SUFFIX --lua-suffix=$LUA_SUFFIX + --with-lua-include=$LUA_INCDIR; + make all install; ) # Configure and build. script: @@ -54,9 +54,9 @@ script: # Install extra rocks into $LUAROCKS_CONFIG rocks tree. - $LUAROCKS install lyaml; $LUAROCKS install specl; # LDoc 1.4.0 is not in luarocks yet, and LDoc master has no rockspec :( - - $LUAROCKS install http://raw.github.com/gvvaughan/LDoc/next/ldoc-next-1.rockspec + - $LUAROCKS install $LDOC_ROCKSPEC - # Make git rockspec for this package. + # Make git rockspec for this stdlib - make rockspecs LUAROCKS="$LUAROCKS" V=1 || { $LUAROCKS path; cat $ROCKSPEC; } @@ -65,11 +65,7 @@ script: - $LUAROCKS make $ROCKSPEC LUA="$LUA" # Run self-tests in the `luarocks make` build tree. - # Use bin/specl if present, or else the specl rock from EXTRA_ROCKS - # above. - - if test -f luarocks/bin/specl; then export SPECL=luarocks/bin/specl; - elif test -f bin/specl; then export SPECL=bin/specl; fi - - export LUA_PATH=`pwd`'/lib/?.lua;'"${LUA_PATH-;}" - - export LUA_CPATH=`pwd`'/ext/?.so;'"${LUA_CPATH-;}" - - export LUA_INIT=; export LUA_INIT_5_2= - - make check SPECL="$SPECL" V=1 + - LUA_PATH=`pwd`'/lib/?.lua;'"${LUA_PATH-;}" + LUA_CPATH=`pwd`'/ext/?.so;'"${LUA_CPATH-;}" + LUA_INIT= LUA_INIT_5_2= + make check V=1 diff --git a/local.mk b/local.mk index b03daf6..19b1ac0 100644 --- a/local.mk +++ b/local.mk @@ -90,8 +90,9 @@ lib/std.lua: lib/std.lua.in ./config.status --file=$@ -## Use a builtin rockspec build with root at $(srcdir)/lib -mkrockspecs_args = --module-dir $(srcdir)/lib +## Use a builtin rockspec build with root at $(srcdir)/lib, and note +## the github repository doesn't have the same name as the rockspec! +mkrockspecs_args = --module-dir $(srcdir)/lib --repository lua-stdlib ## ------------- ## diff --git a/slingshot b/slingshot index 9818ac1..a924fa6 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 9818ac16049ab43c1c3cb2b1d10915a095dd7f1c +Subproject commit a924fa68437e3d3e1c41c0d242db4f995257cc77 From b4277544ec053b30562b56d07930db63d264ea09 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Fri, 17 Jan 2014 01:04:57 +0000 Subject: [PATCH 049/703] string.lua: fix a missing close paren in a docstring --- lib/std/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index c4008e8..3d58393 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -104,7 +104,7 @@ end -- @param min lowest acceptable version (default: any) -- @param too_big lowest version that is too big (default: none) -- @param pattern to match version in `module.version` or --- `module.VERSION` (default: `".*[%.%d]+"` +-- `module.VERSION` (default: `".*[%.%d]+"`) local function require_version (module, min, too_big, pattern) local function version_to_list (v) return list.new (split (v, "%.")) From fb6efe7292bf0e116dbfb826558c04f0fefebff6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 16 Jan 2014 17:40:46 +1300 Subject: [PATCH 050/703] optparse: replace getopt with an easier to use option parser. * specs/optparse_spec.yaml: Specify behaviour for a civilised option parsing API. * lib/std/optparse.lua: New module. * lib/std/getopt.lua: Remove. * doc/config.ld (file), local.mk (dist_luastd_DATA): Adjust. * specs/getopt_spec.yaml: Remove. * local.mk (dist_modules_DATA, dist_classes_DATA): Adjust. * specs/specs.mk (specl_SPECS): Likewise. * build-aux/sanity-cfg.mk: New file. Don't fail sanity checks on account of 'OptionParser' in optparse.lua error message. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- .gitignore | 3 +- NEWS | 11 + build-aux/sanity-cfg.mk | 3 + doc/config.ld | 2 +- lib/std/getopt.lua | 299 ---------------------- lib/std/optparse.lua | 533 +++++++++++++++++++++++++++++++++++++++ local.mk | 4 +- specs/getopt_spec.yaml | 67 ----- specs/optparse_spec.yaml | 363 ++++++++++++++++++++++++++ specs/specs.mk | 4 +- 10 files changed, 918 insertions(+), 371 deletions(-) create mode 100644 build-aux/sanity-cfg.mk delete mode 100644 lib/std/getopt.lua create mode 100644 lib/std/optparse.lua delete mode 100644 specs/getopt_spec.yaml create mode 100644 specs/optparse_spec.yaml diff --git a/.gitignore b/.gitignore index 37d0166..94e8f96 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,8 @@ /README /aclocal.m4 /autom4te.cache -/build-aux +/build-aux/ +!/build-aux/sanity-cfg.mk /config.log /config.status /configure diff --git a/NEWS b/NEWS index 09a1b21..65b781f 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,17 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] +** New features: + + - New `std.optparse` module: A civilised option parser. + (L)Documentation distributed in doc/classes/std.optparse.html. + +** 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] diff --git a/build-aux/sanity-cfg.mk b/build-aux/sanity-cfg.mk new file mode 100644 index 0000000..693103f --- /dev/null +++ b/build-aux/sanity-cfg.mk @@ -0,0 +1,3 @@ +exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/optparse.lua$$ + +EXTRA_DIST += build-aux/sanity-cfg.mk diff --git a/doc/config.ld b/doc/config.ld index fb5c297..bed75e2 100644 --- a/doc/config.ld +++ b/doc/config.ld @@ -8,7 +8,6 @@ file = { "../lib/std.lua", "../lib/std/debug.lua", "../lib/std/functional.lua", - "../lib/std/getopt.lua", "../lib/std/io.lua", "../lib/std/math.lua", "../lib/std/package.lua", @@ -21,6 +20,7 @@ file = { "../lib/std/container.lua", "../lib/std/object.lua", "../lib/std/list.lua", + "../lib/std/optparse.lua", "../lib/std/set.lua", "../lib/std/strbuf.lua", } diff --git a/lib/std/getopt.lua b/lib/std/getopt.lua deleted file mode 100644 index f57fb10..0000000 --- a/lib/std/getopt.lua +++ /dev/null @@ -1,299 +0,0 @@ ---- Simplified getopt, based on Svenne Panne's Haskell GetOpt. --- --- Usage: --- --- prog = {< --- name = , --- [usage = ,] --- [options = { --- {{[, ...]}, , [ [, ]]}, --- ... --- },] --- [banner = ,] --- [purpose = ,] --- [notes = ] --- } --- --- * The `type` of option argument is one of `Req`(uired), --- `Opt`(ional) --- * The `var`is a descriptive name for the option argument. --- * `getopt.processargs (prog)` --- * Options take a single dash, but may have a double dash. --- * Arguments may be given as `-opt=arg` or `-opt arg`. --- * If an option taking an argument is given multiple times, only the --- last value is returned; missing arguments are returned as 1. --- --- getOpt, usageinfo and usage can be called directly (see --- below, and the example at the end). Set _DEBUG.std to a non-nil --- value to run the example. --- --- @todo Wrap all messages; do all wrapping in processargs, not --- usageinfo; use sdoc-like library (see string.format todos). --- @todo Don't require name to be repeated in banner. --- @todo Store version separately (construct banner?). --- --- @module std.getopt - -local io = require "std.io" -local List = require "std.list" -local Object = require "std.object" -local string = require "std.string" -local table = require "std.table" - -local M = { - opt = {}, -} - -local argtype = { Opt = true, Req = true } - - ---- Perform argument processing --- @param argIn list of command-line args --- @param options options table --- @param stop_at_nonopt if true, stop option processing at first non-option --- @return table of remaining non-options --- @return table of option key-value list pairs --- @return table of error messages -local function getopt (argIn, options, stop_at_nonopt) - local noProcess = nil - local argOut, optOut, errors = {[0] = argIn[0]}, {}, {} - -- get an argument for option opt - local function getArg (o, opt, arg, oldarg) - if not argtype[o.type] then - if arg ~= nil then - table.insert (errors, "option `" .. opt .. "' doesn't take an argument") - end - else - if arg == nil and argIn[1] and - string.sub (argIn[1], 1, 1) ~= "-" then - arg = argIn[1] - table.remove (argIn, 1) - end - if arg == nil and o.type == "Req" then - table.insert (errors, "option `" .. opt .. - "' requires an argument `" .. o.var .. "'") - return nil - end - end - return arg or 1 -- make sure arg has a value - end - - local function parseOpt (opt, arg) - local o = options.name[opt] - if o ~= nil then - o = o or {name = {opt}} - optOut[o.name[1]] = optOut[o.name[1]] or {} - table.insert (optOut[o.name[1]], getArg (o, opt, arg, optOut[o.name[1]])) - else - table.insert (errors, "unrecognized option `-" .. opt .. "'") - end - end - while argIn[1] do - local v = argIn[1] - table.remove (argIn, 1) - local _, _, dash, opt = string.find (v, "^(%-%-?)([^=-][^=]*)") - local _, _, arg = string.find (v, "=(.*)$") - if not dash and stop_at_nonopt then - noProcess = true - end - if v == "--" then - noProcess = true - elseif not dash or noProcess then -- non-option - table.insert (argOut, v) - else -- option - parseOpt (opt, arg) - end - end - return argOut, optOut, errors -end - - --- Object that defines a single Option entry. -local Option = Object {_init = {"name", "desc", "type", "var"}} - ---- Options table constructor: adds lookup tables for the option names. -local function makeOptions (t) - local options, name = {}, {} - local function appendOpt (v, nodupes) - local dupe = false - v = Option (v) - for s in List.elems (v.name) do - if name[s] then - dupe = true - end - name[s] = v - end - if not dupe or nodupes ~= true then - if dupe then io.warn ("duplicate option '%s'", s) end - for s in List.elems (v.name) do name[s] = v end - options = List.concat (options, {v}) - end - end - for v in List.elems (t or {}) do - appendOpt (v) - end - -- Unless they were supplied already, add version and help options - appendOpt ({{"version", "V"}, "print version information, then exit"}, - true) - appendOpt ({{"help", "h"}, "print this help, then exit"}, true) - options.name = name - return options -end - - ---- Produce usage info for the given options. --- @param header header string --- @param optDesc option descriptors --- @param pageWidth width to format to [78] --- @return formatted string -local function usageinfo (header, optDesc, pageWidth) - pageWidth = pageWidth or 78 - -- Format the usage info for a single option - -- @param opt the option table - -- @return options - -- @return description - local function fmtOpt (opt) - local function fmtName (o) - return (#o > 1 and "--" or "-") .. o - end - local function fmtArg () - if opt.type == "Req" then - return "=" .. opt.var - elseif opt.type == "Opt" then - return "[=" .. opt.var .. "]" - else - return "" - end - end - local textName = List.reverse (List.map (opt.name, fmtName)) - textName[#textName] = textName[#textName] .. fmtArg () - local indent = "" - if #opt.name == 1 and #opt.name[1] > 1 then - indent = " " - end - return {indent .. table.concat ({table.concat (textName, ", ")}, ", "), - opt.desc} - end - local function sameLen (xs) - local n = math.max (unpack (List.map (xs, string.len))) - for i, v in pairs (xs) do - xs[i] = string.sub (v .. string.rep (" ", n), 1, n) - end - return xs, n - end - local function paste (x, y) - return " " .. x .. " " .. y - end - local function wrapper (w, i) - return function (s) - return string.wrap (s, w, i, 0) - end - end - local optText = "" - if #optDesc > 0 then - local cols = List.transpose (List.map (optDesc, fmtOpt)) - local width - cols[1], width = sameLen (cols[1]) - cols[2] = List.map (cols[2], wrapper (pageWidth, width + 4)) - optText = "\n\n" .. - table.concat (List.map_with (List.transpose ({sameLen (cols[1]), - cols[2]}), - paste), - "\n") - end - return header .. optText -end - ---- Emit a usage message. --- @param prog table of named parameters -local function usage (prog) - local usage = "[OPTION]... [FILE]..." - local purpose, description, notes = "", "", "" - if prog.usage then - usage = prog.usage - end - usage = "Usage: " .. prog.name .. " " .. usage - if prog.purpose then - purpose = "\n\n" .. prog.purpose - end - if prog.description then - for para in List.elems (string.split (prog.description, "\n")) do - description = description .. "\n\n" .. string.wrap (para) - end - end - if prog.notes then - notes = "\n\n" - if not string.find (prog.notes, "\n") then - notes = notes .. string.wrap (prog.notes) - else - notes = notes .. prog.notes - end - end - local header = usage .. purpose .. description - io.writelines (usageinfo (header, prog.options) .. notes) -end - - -local function version (prog) - local version = prog.version or prog.name or "unknown version!" - if prog.copyright then - version = version .. "\n\n" .. prog.copyright - end - io.writelines (version) -end - - - ---- Simple getopt wrapper. --- If the caller didn't supply their own already, adds `--version`/`-V` --- and `--help`/`-h` options automatically; --- stops program if there was an error, or if `--help` or `--version` was --- used. --- @param prog table of named parameters --- @param ... extra arguments for getopt -local function processargs (prog, ...) - local totArgs = #_G.arg - local errors - prog.options = makeOptions (prog.options) - _G.arg, M.opt, errors = getopt (_G.arg, prog.options, ...) - local opt = M.opt - if (opt.version or opt.help) and prog.banner then - io.writelines (prog.banner) - end - if #errors > 0 then - local name = prog.name - prog.name = nil - if #errors > 0 then - io.warn (name .. ": " .. table.concat (errors, "\n")) - io.warn (name .. ": Try '" .. (arg[0] or name) .. " --help' for more help") - end - if #errors > 0 then - error () - end - elseif opt.version then - version (prog) - elseif opt.help then - usage (prog) - end - if opt.version or opt.help then - os.exit () - end -end - - ---- @export -local Getopt = { - getopt = getopt, - processargs = processargs, - usage = usage, - usageinfo = usageinfo, -} - --- camelCase compatibility. -Getopt = table.merge (Getopt, { - getOpt = getopt, - processArgs = processargs, - usageInfo = usageinfo, -}) - -return table.merge (M, Getopt) diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua new file mode 100644 index 0000000..7bf0ee5 --- /dev/null +++ b/lib/std/optparse.lua @@ -0,0 +1,533 @@ +--[[-- + Parse and process command line options. + + local OptionParser = require "std.optparse" + local parser = OptionParser (spec) + _G.arg, opts = parser:parse (_G.arg) + + The string `spec` passed to `OptionParser` must be a specially formatted + help text, of the form: + + any text VERSION + Additional lines of text to show when the --version + option is passed. + + Several lines or paragraphs are permitted. + + Usage: PROGNAME + + Banner text. + + Optional long description text to show when the --help + option is passed. + + Several lines or paragraphs of long description are permitted. + + Options: + + -h, --help display this help, then exit + --version display version information, then exit + -b a short option with no long option + --long a long option with no short option + --another-long a long option with internal hypen + --true a Lua keyword as an option name + -v, --verbose a combined short and long option + -n, --dryrun, --dry-run several spellings of the same option + -u, --name=USER require an argument + -o, --output=[FILE] accept an optional argument + -- end of options + + Footer text. Several lines or paragraphs are permitted. + + Please report bugs at bug-list@yourhost.com + + Most often, everything else is handled automatically. After calling + `parser:parse` as shown above, `_G.arg` will contain unparsed arguments, + usually filenames or similar, and `opts` will be a table of parsed + option values. The keys to the table are the long-options with leading + hyphens stripped, and non-word characters turned to `_`. For example + if `--another-long` had been found in `_G.arg` then `opts` would + have a key named `another_long`. If there is no long option name, then + the short option is used, e.g. `opts.b` will be set. The values saved + in those keys are controlled by the option handler, usually just `true` + or the option argument string as appropriate. + + On those occasions where more complex processing is required, handlers + can be replaced or added using parser:@{on}. + + @classmod std.optparse +]] + + +local OptionParser -- forward declaration + + +------ +-- Customized parser for your options. +-- @table parser + + +--[[ ----------------- ]]-- +--[[ Helper Functions. ]]-- +--[[ ----------------- ]]-- + + +local optional, required + + +--- Normalise an argument list. +-- Separate short options, remove `=` separators from +-- `--long-option=optarg` etc. +-- @function normalise +-- @tparam table arglist list of arguments to normalise +-- @treturn table normalised argument list +local function normalise (self, arglist) + -- First pass: Normalise to long option names, without '=' separators. + local normal = {} + local i = 0 + while i < #arglist do + i = i + 1 + local opt = arglist[i] + + -- Split '--long-option=option-argument'. + if opt:sub (1, 2) == "--" then + local x = opt:find ("=", 3, true) + if x then + table.insert (normal, opt:sub (1, x - 1)) + table.insert (normal, opt:sub (x + 1)) + else + table.insert (normal, opt) + end + + elseif opt:sub (1, 1) == "-" and string.len (opt) > 2 then + local rest + repeat + opt, rest = opt:sub (1, 2), opt:sub (3) + + table.insert (normal, opt) + + -- Split '-xyz' into '-x -yz', and reiterate for '-yz' + if self[opt].handler ~= optional and + self[opt].handler ~= required then + if string.len (rest) > 0 then + opt = "-" .. rest + else + opt = nil + end + + -- Split '-xshortargument' into '-x shortargument'. + else + table.insert (normal, rest) + opt = nil + end + until opt == nil + else + table.insert (normal, opt) + end + end + + normal[-1], normal[0] = arglist[-1], arglist[0] + return normal +end + + +--- Store `value` with `opt`. +-- @function set +-- @string opt option name +-- @param value option argument value +local function set (self, opt, value) + local key = self[opt].key + + if type (self.opts[key]) == "table" then + table.insert (self.opts[key], value) + elseif self.opts[key] ~= nil then + self.opts[key] = { self.opts[key], value } + else + self.opts[key] = value + end +end + + + +--[[ ============= ]]-- +--[[ Option Types. ]]-- +--[[ ============= ]]-- + + +--- Option at `arglist[i]` can take an argument. +-- Argument is accepted only if there is a following entry that does not +-- begin with a '-'. +-- @tparam table arglist list of arguments +-- @int i index of last processed element of `arglist` +-- @param[opt=true] value either a function to process the option +-- argument, or a default value if encountered without an optarg +-- @treturn int index of next element of `arglist` to process +function optional (self, arglist, i, value) + if i + 1 <= #arglist and arglist[i + 1]:sub (1, 1) ~= "-" then + return self:required (arglist, i, value) + end + + if type (value) == "function" then + value = value (self, opt, nil) + elseif value == nil then + value = true + end + + set (self, arglist[i], value) + return i + 1 +end + + +--- Option at `arglist[i}` requires an argument. +-- @tparam table arglist list of arguments +-- @int i index of last processed element of `arglist` +-- @param[opt] value either a function to process the option argument, +-- or a forced value to replace the user's option argument. +-- @treturn int index of next element of `arglist` to process +function required (self, arglist, i, value) + local opt = arglist[i] + if i + 1 > #arglist then + self:opterr ("option '" .. opt .. "' requires an argument") + return i + 1 + end + + if type (value) == "function" then + value = value (self, opt, arglist[i + 1]) + elseif value == nil then + value = arglist[i + 1] + end + + set (self, opt, value) + return i + 2 +end + + +--- Finish option processing +-- Usually indicated by `--` at `arglist[i]`. +-- @tparam table arglist list of arguments +-- @int i index of last processed element of `arglist` +-- @treturn int index of next element of `arglist` to process +local function finished (self, arglist, i) + for opt = i + 1, #arglist do + table.insert (self.unrecognised, arglist[opt]) + end + return 1 + #arglist +end + + +--- Option at `arglist[i]` is a boolean switch. +-- @tparam table arglist list of arguments +-- @int i index of last processed element of `arglist` +-- @param[opt] value either a function to process the option argument, +-- or a value to store when this flag is encountered +-- @treturn int index of next element of `arglist` to process +local function flag (self, arglist, i, value) + if type (value) == "function" then + value = value (self, opt, true) + elseif value == nil then + value = true + end + + set (self, arglist[i], value) + return i + 1 +end + + +--- Option should display help text, then exit. +-- @function help +local function help (self) + print (self.helptext) + os.exit (0) +end + + +--- Option should display version text, then exit. +-- @function version +local function version (self) + print (self.versiontext) + os.exit (0) +end + + + +--[[ =============== ]]-- +--[[ Argument Types. ]]-- +--[[ =============== ]]-- + + +--- Map various option strings to equivalent Lua boolean values. +-- @table boolvals +-- @field false false +-- @field 0 false +-- @field no false +-- @field n false +-- @field true true +-- @field 1 true +-- @field yes true +-- @field y true +local boolvals = { + ["false"] = false, ["true"] = true, + ["0"] = false, ["1"] = true, + no = false, yes = true, + n = false, y = true, +} + + +--- Return a Lua boolean equivalent of various `optarg` strings. +-- Report an option parse error if `optarg` is not recognised. +-- @string opt option name +-- @string[opt="1"] optarg option argument, must be a key in @{boolvals} +-- @treturn bool `true` or `false` +local function boolean (self, opt, optarg) + if optarg == nil then optarg = "1" end -- default to truthy + local b = boolvals[tostring (optarg):lower ()] + if b == nil then + return self:opterr (optarg .. ": Not a valid argument to " ..opt[1] .. ".") + end + return b +end + + +--- Report an option parse error unless `optarg` names an +-- existing file. +-- @fixme this only checks whether the file has read permissions +-- @string opt option name +-- @string optarg option argument, must be an existing file +-- @treturn `optarg` +local function file (self, opt, optarg) + local h, errmsg = io.open (optarg, "r") + if h == nil then + return self:opterr (optarg .. ": " .. errmsg) + end + h:close () + return optarg +end + + + +--[[ =============== ]]-- +--[[ Option Parsing. ]]-- +--[[ =============== ]]-- + + +--- Report an option parse error, then exit with status 2. +-- @string msg error message +local function opterr (self, msg) + local prog = self.program + -- Ensure final period. + if msg:match ("%.$") == nil then msg = msg .. "." end + io.stderr:write (prog .. ": error: " .. msg .. "\n") + io.stderr:write (prog .. ": Try '" .. prog .. " --help' for help.\n") + os.exit (2) +end + + +------ +-- Function signature of an option handler for @{on}. +-- @function on_handler +-- @tparam table arglist list of arguments +-- @int i index of last processed element of `arglist` +-- @param[opt=nil] value additional `value` registered with @{on} +-- @treturn int index of next element of `arglist` to process + + +--- Add an option handler. +-- @function on +-- @tparam[string|table] opts name of the option, or list of option names +-- @tparam on_handler handler function to call when any of `opts` is +-- encountered +-- @param value additional value passed to @{on_handler} +local function on (self, opts, handler, value) + if type (opts) == "string" then opts = { opts } end + handler = handler or flag -- unspecified options behave as flags + + normal = {} + for _, optspec in ipairs (opts) do + optspec:gsub ("(%S+)", + function (opt) + -- 'x' => '-x' + if string.len (opt) == 1 then + opt = "-" .. opt + + -- 'option-name' => '--option-name' + elseif opt:match ("^[^%-]") ~= nil then + opt = "--" .. opt + end + + if opt:match ("^%-[^%-]+") ~= nil then + -- '-xyz' => '-x -y -z' + for i = 2, string.len (opt) do + table.insert (normal, "-" .. opt:sub (i, i)) + end + else + table.insert (normal, opt) + end + end) + end + + -- strip leading '-', and convert non-alphanums to '_' + key = normal[#normal]:match ("^%-*(.*)$"):gsub ("%W", "_") + + for _, opt in ipairs (normal) do + self[opt] = { key = key, handler = handler, value = value } + end +end + + +------ +-- Parsed options table, with a key for each encountered option, each +-- with value set by that option's @{on_handler}. +-- @table opts + + +--- Parse `arglist`. +-- @tparam table arglist list of arguments +-- @treturn table a list of unrecognised `arglist` elements +-- @treturn opts parsing results +local function parse (self, arglist) + self.unrecognised = {} + + arglist = normalise (self, arglist) + + local i = 1 + while i > 0 and i <= #arglist do + local opt = arglist[i] + + if self[opt] == nil then + table.insert (self.unrecognised, opt) + i = i + 1 + + -- Following non-'-' prefixed argument is an optarg. + if i <= #arglist and arglist[i]:match "^[^%-]" then + table.insert (self.unrecognised, arglist[i]) + i = i + 1 + end + + -- Run option handler functions. + else + assert (type (self[opt].handler) == "function") + + i = self[opt].handler (self, arglist, i, self[opt].value) + end + end + + return self.unrecognised, self.opts +end + + +--- @export +local methods = { + boolean = boolean, + file = file, + finished = finished, + flag = flag, + help = help, + optional = optional, + required = required, + version = version, + + on = on, + opterr = opterr, + parse = parse, +} + + + +--- Take care not to register duplicate handlers. +-- @param current current handler value +-- @param new new handler value +-- @return `new` if `current` is nil +local function set_handler (current, new) + assert (current == nil, "only one handler per option") + return new +end + + +--- Instantiate a new parser. +-- Read the documented options from `spec` and return a new parser that +-- can be passed to @{parse} for parsing those options from an argument +-- list. +-- @static +-- @string spec option parsing specification +-- @treturn parser a parser for options described by `spec` +function OptionParser (spec) + local parser = setmetatable ({ opts = {} }, { __index = methods }) + + parser.versiontext, parser.version, parser.helptext, parser.program = + spec:match ("^([^\n]-(%S+)\n.-)%s*([Uu]sage: (%S+).-)%s*$") + + if parser.versiontext == nil then + error ("OptionParser spec argument must match '\\n" .. + "...Usage: ...'") + end + + -- Collect helptext lines that begin with two or more spaces followed + -- by a '-'. + local specs = {} + parser.helptext:gsub ("\n %s*(%-[^\n]+)", + function (spec) table.insert (specs, spec) end) + + -- Register option handlers according to the help text. + for _, spec in ipairs (specs) do + local options, handler = {} + + -- Loop around each '-' prefixed option on this line. + while spec:sub (1, 1) == "-" do + + -- Capture end of options processing marker. + if spec:match "^%-%-,?%s" then + handler = set_handler (handler, finished) + + -- Capture optional argument in the option string. + elseif spec:match "^%-[%-%w]+=%[.+%],?%s" then + handler = set_handler (handler, optional) + + -- Capture required argument in the option string. + elseif spec:match "^%-[%-%w]+=%S+,?%s" then + handler = set_handler (handler, required) + + -- Capture any specially handled arguments. + elseif spec:match "^%-%-help,?%s" then + handler = set_handler (handler, help) + + elseif spec:match "^%-%-version,?%s" then + handler = set_handler (handler, version) + end + + -- Consume argument spec, now that it was processed above. + spec = spec:gsub ("^(%-[%-%w]+)=%S+%s", "%1 ") + + -- Consume short option. + local _, c = spec:gsub ("^%-([-%w]),?%s+(.*)$", + function (opt, rest) + if opt == "-" then opt = "--" end + table.insert (options, opt) + spec = rest + end) + + -- Be careful not to consume more than one option per iteration, + -- otherwise we might miss a handler test at the next loop. + if c == 0 then + -- Consume long option. + spec:gsub ("^%-%-([%-%w]+),?%s+(.*)$", + function (opt, rest) + table.insert (options, opt) + spec = rest + end) + end + end + + -- Unless specified otherwise, treat each option as a flag. + parser:on (options, handler or flag) + end + + return parser +end + + +-- Support calling the returned table: +return setmetatable (methods, { + __call = function (_, ...) + return OptionParser (...) + end, +}) diff --git a/local.mk b/local.mk index 19b1ac0..baf2a23 100644 --- a/local.mk +++ b/local.mk @@ -67,12 +67,12 @@ dist_luastd_DATA = \ lib/std/debug.lua \ lib/std/debug_init.lua \ lib/std/functional.lua \ - lib/std/getopt.lua \ lib/std/io.lua \ lib/std/list.lua \ lib/std/math.lua \ lib/std/modules.lua \ lib/std/object.lua \ + lib/std/optparse.lua \ lib/std/package.lua \ lib/std/set.lua \ lib/std/strbuf.lua \ @@ -118,6 +118,7 @@ dist_classes_DATA += \ $(srcdir)/doc/classes/std.container.html \ $(srcdir)/doc/classes/std.list.html \ $(srcdir)/doc/classes/std.object.html \ + $(srcdir)/doc/classes/std.optparse.html \ $(srcdir)/doc/classes/std.set.html \ $(srcdir)/doc/classes/std.strbuf.html \ $(srcdir)/doc/classes/std.tree.html \ @@ -127,7 +128,6 @@ dist_modules_DATA += \ $(srcdir)/doc/modules/std.html \ $(srcdir)/doc/modules/std.debug.html \ $(srcdir)/doc/modules/std.functional.html \ - $(srcdir)/doc/modules/std.getopt.html \ $(srcdir)/doc/modules/std.io.html \ $(srcdir)/doc/modules/std.math.html \ $(srcdir)/doc/modules/std.package.html \ diff --git a/specs/getopt_spec.yaml b/specs/getopt_spec.yaml deleted file mode 100644 index c36c7d0..0000000 --- a/specs/getopt_spec.yaml +++ /dev/null @@ -1,67 +0,0 @@ -specify getopt: -- before: - getopt = require "std.getopt" - -- "describe getopt.getOpt": - - before: | - prog = { - name = "getopt_spec.lua", - options = { {{"verbose", "v"}, "verbosely list files"}, - {{"output", "o"}, "dump to FILE", "Opt", "FILE"}, - {{"name", "n"}, "only dump USER's files", "Req", "USER"}, - } - } - - function test (cmdLine, stop_at_nonopt) - local nonOpts, opts, errors = getopt.getOpt (cmdLine, prog.options, stop_at_nonopt) - if #errors == 0 then - return { options = opts, args = nonOpts } - else - return errors - end - end - - getopt.processArgs (prog) - - - it recognizes a user defined option: - expect (test {"foo", "-v"}).should_equal ( - { options = { verbose = {1}}, args = { "foo" } }) - - "it treats -- as the end of the option list": - expect (test {"foo", "--", "-v"}).should_equal ( - { options = {}, args = { "foo", "-v" } }) - - it captures a list of repeated option arguments: - expect (test {"-o", "-V", "-name", "bar", "--name=baz"}).should_equal ( - { options = { name = {"bar", "baz"}, version = {1}, output = {1}}, - args = {} }) - - it diagnoses unrecognized options: - expect (test {"-foo"}).should_contain "unrecognized option `-foo'" - - it stops option parsing at the first non-option if told to: - expect (test ({"foo", "-bar"}, true)).should_equal ( - { options = {}, args = {"foo", "-bar"} }) - -- "describe getopt.usageInfo": - - context when specifying options: - - before: - helppatt = "%-h, %-%-help%s+print this help, then exit" - versionpatt = "%-V, %-%-version%s+print version information, then exit" - prog = { name = "getopt_spec.lua", options = {} } - options = { {{"help", "?"}, "display this help"}, - {{"version"}, "display version number"} } - f = getopt.usageInfo - - - it provides a default version option: - getopt.processArgs (prog) - expect (f ("", prog.options)).should_match (versionpatt) - - it allows the user to override the version option: - prog.options = options - getopt.processArgs (prog) - expect (f ("", prog.options)).should_not_match (versionpatt) - expect (f ("", prog.options)).should_match (" %-%-version%s+display") - - it provides a default help option: - getopt.processArgs (prog) - expect (f ("", prog.options)).should_match (helppatt) - - it allows the user to override the help option: - prog.options = options - getopt.processArgs (prog) - expect (f ("", prog.options)).should_not_match (helppatt) - expect (f ("", prog.options)).should_match ("%-%?, %-%-help%s+display") diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml new file mode 100644 index 0000000..5e04947 --- /dev/null +++ b/specs/optparse_spec.yaml @@ -0,0 +1,363 @@ +before: + hell = require "specl.shell" + +specify optparse: +- before: | + OptionParser = require "std.optparse" + + help = [[ + parseme (specl spec) 0α1 + + Copyright © 2013 Gary V. Vaughan + This test program comes with ABSOLUTELY NO WARRANTY. + + Usage: parseme [] ... + + Banner text. + + Long description. + + Options: + + -h, --help display this help, then exit + --version display version information, then exit + -b a short option with no long option + --long a long option with no short option + --another-long a long option with internal hypen + --true a Lua keyword as an option name + -v, --verbose a combined short and long option + -n, --dryrun, --dry-run several spellings of the same option + -u, --name=USER require an argument + -o, --output=[FILE] accept an optional argument + -- end of options + + Footer text. + + Please report bugs at . + ]] + + -- strip off the leading whitespace required for YAML + parser = OptionParser (help:gsub ("^ ", "")) + +- describe OptionParser: + - it recognises the program name: + expect (parser.program).should_be "parseme" + - it recognises the version number: + expect (parser.version).should_be "0α1" + - it recognises the version text: + expect (parser.versiontext). + should_match "^parseme .*Copyright .*NO WARRANTY%." + - it recognises the help text: | + expect (parser.helptext). + should_match ("^Usage: parseme .*Banner .*Long .*Options:.*" .. + "Footer .*/issues>%.") + - it diagnoses incorrect input text: + expect (OptionParser "garbage in").should_error "argument must match" + +- describe parser: + - before: | + f = os.tmpname () + + function parse (arglist) + local d = f:gsub ("/[^/]*$", "", 1) + local h = io.open (f, "w") + + h:write ([[ + package.path = "]] .. package.path .. [[" + local OptionParser = require 'std.optparse' + local help = [=[]] .. help .. [[]=] + help = help:match ("^[%s\n]*(.-)[%s\n]*$") + + local parser = OptionParser (help) + local arg, opts = parser:parse (_G.arg) + + o = {} + for k, v in pairs (opts) do + table.insert (o, k .. " = " .. tostring (v)) + end + if #o > 0 then + table.sort (o) + print ("opts = { " .. table.concat (o, ", ") .. " }") + end + if #arg > 0 then + print ("args = { " .. table.concat (arg, ", ") .. " }") + end + ]]) + h:close () + + return hell.spawn {arg[-1], f, unpack (arglist)} + end + - after: os.remove (f) + + - it leaves behind unrecognised options: + expect (parse {"--not-an-option"}). + should_output "args = { --not-an-option }\n" + - it responds to --version with version text: + expect (parse {"--version"}). + should_match_output "^%s*parseme .*Copyright .*NO WARRANTY%.\n$" + - it responds to --help with help text: | + expect (parse {"--help"}). + should_match_output ("^%s*Usage: parseme .*Banner.*Long.*" .. + "Options:.*Footer.*/issues>%.\n$") + - it recognises short options: + expect (parse {"-b"}).should_output "opts = { b = true }\n" + - it recognises long options: + expect (parse {"--long"}).should_output "opts = { long = true }\n" + - it recognises long options with hyphens: + expect (parse {"--another-long"}). + should_output "opts = { another_long = true }\n" + - it recognises long options named after Lua keywords: + expect (parse {"--true"}).should_output "opts = { true = true }\n" + - it recognises combined short and long option specs: + expect (parse {"-v"}).should_output "opts = { verbose = true }\n" + expect (parse {"--verbose"}).should_output "opts = { verbose = true }\n" + - it recognises options with several spellings: + expect (parse {"-n"}).should_output "opts = { dry_run = true }\n" + expect (parse {"--dry-run"}).should_output "opts = { dry_run = true }\n" + expect (parse {"--dryrun"}).should_output "opts = { dry_run = true }\n" + - it recognises end of options marker: + expect (parse {"-- -n"}).should_output "args = { -n }\n" + - context given an option with a required argument: + - it records an argument to a long option following an '=' delimiter: + expect (parse {"--name=Gary"}). + should_output "opts = { name = Gary }\n" + - it records an argument to a short option without a space: + expect (parse {"-uGary"}). + should_output "opts = { name = Gary }\n" + - it records an argument to a long option following a space: + expect (parse {"--name Gary"}). + should_output "opts = { name = Gary }\n" + - it records an argument to a short option following a space: + expect (parse {"-u Gary"}). + should_output "opts = { name = Gary }\n" + - it diagnoses a missing argument: + expect (parse {"--name"}). + should_contain_error "'--name' requires an argument" + expect (parse {"-u"}). + should_contain_error "'-u' requires an argument" + - context given an option with an optional argument: + - it records an argument to a long option following an '=' delimiter: + expect (parse {"--output=filename"}). + should_output "opts = { output = filename }\n" + - it records an argument to a short option without a space: + expect (parse {"-ofilename"}). + should_output "opts = { output = filename }\n" + - it records an argument to a long option following a space: + expect (parse {"--output filename"}). + should_output "opts = { output = filename }\n" + - it records an argument to a short option following a space: + expect (parse {"-o filename"}). + should_output "opts = { output = filename }\n" + - it doesn't consume the following option: + expect (parse {"--output -v"}). + should_output "opts = { output = true, verbose = true }\n" + expect (parse {"-o -v"}). + should_output "opts = { output = true, verbose = true }\n" + - context when splitting combined short options: + - it separates non-argument options: + expect (parse {"-bn"}). + should_output "opts = { b = true, dry_run = true }\n" + expect (parse {"-vbn"}). + should_output "opts = { b = true, dry_run = true, verbose = true }\n" + - it stops separating at a required argument option: + expect (parse {"-vuname"}). + should_output "opts = { name = name, verbose = true }\n" + expect (parse {"-vuob"}). + shauld_output "opts = { name = ob, verbose = true }\n" + - it stops separating at an optional argument option: + expect (parse {"-vofilename"}). + should_output "opts = { output = filename, verbose = true }\n" + expect (parse {"-vobn"}). + should_output "opts = { output = bn, verbose = true }\n" + +- describe parser:on: + - before: | + f = os.tmpname () + + function parseargs (onargstr, arglist) + local d = f:gsub ("/[^/]*$", "", 1) + local h = io.open (f, "w") + + h:write ([[ + package.path = "]] .. package.path .. [[" + local OptionParser = require 'std.optparse' + local help = [=[]] .. help .. [[]=] + help = help:match ("^[%s\n]*(.-)[%s\n]*$") + + local parser = OptionParser (help) + + parser:on (]] .. onargstr .. [[) + + local arg, opts = parser:parse (_G.arg) + + o = {} + for k, v in pairs (opts) do + table.insert (o, k .. " = " .. tostring (v)) + end + if #o > 0 then + table.sort (o) + print ("opts = { " .. table.concat (o, ", ") .. " }") + end + if #arg > 0 then + print ("args = { " .. table.concat (arg, ", ") .. " }") + end + ]]) + h:close () + + return hell.spawn {arg[-1], f, unpack (arglist)} + end + - after: os.remove (f) + + - it recognises short options: + expect (parseargs ([["x"]], {"-x"})). + should_output "opts = { x = true }\n" + - it recognises long options: + expect (parseargs([["something"]], {"--something"})). + should_output "opts = { something = true }\n" + - it recognises long options with hyphens: + expect (parseargs([["some-thing"]], {"--some-thing"})). + should_output "opts = { some_thing = true }\n" + - it recognises long options named after Lua keywords: + expect (parseargs ([["if"]], {"--if"})). + should_output "opts = { if = true }\n" + - it recognises combined short and long option specs: + expect (parseargs ([[{"x", "if"}]], {"-x"})). + should_output "opts = { if = true }\n" + expect (parseargs ([[{"x", "if"}]], {"--if"})). + should_output "opts = { if = true }\n" + - it recognises options with several spellings: + expect (parseargs ([[{"x", "blah", "if"}]], {"-x"})). + should_output "opts = { if = true }\n" + expect (parseargs ([[{"x", "blah", "if"}]], {"--blah"})). + should_output "opts = { if = true }\n" + expect (parseargs ([[{"x", "blah", "if"}]], {"--if"})). + should_output "opts = { if = true }\n" + - it recognises end of options marker: + expect (parseargs ([["x"]], {"--", "-x"})). + should_output "args = { -x }\n" + - context given an option with a required argument: + - it records an argument to a short option without a space: + expect (parseargs ([["x", parser.required]], {"-y", "-xarg", "-b"})). + should_contain_output "opts = { b = true, x = arg }" + - it records an argument to a short option following a space: + expect (parseargs ([["x", parser.required]], {"-y", "-x", "arg", "-b"})). + should_contain_output "opts = { b = true, x = arg }\n" + - it records an argument to a long option following a space: + expect (parseargs ([["this", parser.required]], {"--this", "arg"})). + should_output "opts = { this = arg }\n" + - it records an argument to a long option following an '=' delimiter: + expect (parseargs ([["this", parser.required]], {"--this=arg"})). + should_output "opts = { this = arg }\n" + - it diagnoses a missing argument: + expect (parseargs ([[{"x", "this"}, parser.required]], {"-x"})). + should_contain_error "'-x' requires an argument" + expect (parseargs ([[{"x", "this"}, parser.required]], {"--this"})). + should_contain_error "'--this' requires an argument" + - context with a boolean handler function: + - it records a truthy argument: + for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} + do + expect (parseargs ([["x", parser.required, parser.boolean]], + {"-x", optarg})). + should_output "opts = { x = true }\n" + end + - it records a falsey argument: + for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} + do + expect (parseargs ([["x", parser.required, parser.boolean]], + {"-x", optarg})). + should_output "opts = { x = false }\n" + end + - context with a file handler function: + - it records an existing file: + expect (parseargs ([["x", parser.required, parser.file]], + {"-x", "/dev/null"})). + should_output "opts = { x = /dev/null }\n" + - it diagnoses a missing file: | + expect (parseargs ([["x", parser.required, parser.file]], + {"-x", "/this/file/does/not/exist"})). + should_contain_error "error: /this/file/does/not/exist: " + - context with a custom handler function: + - it calls the handler: + expect (parseargs ([["x", parser.required, function (p,o,a) + return "custom" + end + ]], {"-x", "ignored"})). + should_output "opts = { x = custom }\n" + - it diagnoses a missing argument: + expect (parseargs ([["x", parser.required, function (p,o,a) + return "custom" + end + ]], {"-x"})). + should_contain_error "option '-x' requires an argument" + - context given an option with an optional argument: + - it records an argument to a short option without a space: + expect (parseargs ([["x", parser.optional]], {"-y", "-xarg", "-b"})). + should_contain_output "opts = { b = true, x = arg }" + - it records an argument to a short option following a space: + expect (parseargs ([["x", parser.optional]], {"-y", "-x", "arg", "-b"})). + should_contain_output "opts = { b = true, x = arg }\n" + - it records an argument to a long option following a space: + expect (parseargs ([["this", parser.optional]], {"--this", "arg"})). + should_output "opts = { this = arg }\n" + - it records an argument to a long option following an '=' delimiter: + expect (parseargs ([["this", parser.optional]], {"--this=arg"})). + should_output "opts = { this = arg }\n" + - it does't consume the following option: + expect (parseargs ([[{"x", "this"}, parser.optional]], {"-x", "-b"})). + should_output "opts = { b = true, this = true }\n" + expect (parseargs ([[{"x", "this"}, parser.optional]], {"--this", "-b"})). + should_output "opts = { b = true, this = true }\n" + - context with a boolean handler function: + - it records a truthy argument: + for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} + do + expect (parseargs ([["x", parser.optional, parser.boolean]], + {"-x", optarg})). + should_output "opts = { x = true }\n" + end + - it records a falsey argument: + for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} + do + expect (parseargs ([["x", parser.optional, parser.boolean]], + {"-x", optarg})). + should_output "opts = { x = false }\n" + end + - it defaults to a truthy value: + expect (parseargs ([["x", parser.optional, parser.boolean]], + {"-x", "-b"})). + should_output "opts = { b = true, x = true }\n" + - context with a file handler function: + - it records an existing file: + expect (parseargs ([["x", parser.optional, parser.file]], + {"-x", "/dev/null"})). + should_output "opts = { x = /dev/null }\n" + - it diagnoses a missing file: | + expect (parseargs ([["x", parser.optional, parser.file]], + {"-x", "/this/file/does/not/exist"})). + should_contain_error "error: /this/file/does/not/exist: " + - context with a custom handler function: + - it calls the handler: + expect (parseargs ([["x", parser.optional, function (p,o,a) + return "custom" + end + ]], {"-x", "ignored"})). + should_output "opts = { x = custom }\n" + - it does not consume a following option: + expect (parseargs ([["x", parser.optional, function (p,o,a) + return a or "default" + end + ]], {"-x", "-b"})). + should_output "opts = { b = true, x = default }\n" + - context when splitting combined short options: + - it separates non-argument options: + expect (parseargs ([["x"]], {"-xb"})). + should_output "opts = { b = true, x = true }\n" + expect (parseargs ([["x"]], {"-vxb"})). + should_output "opts = { b = true, verbose = true, x = true }\n" + - it stops separating at a required argument option: + expect (parseargs ([[{"x", "this"}, parser.required]], {"-bxbit"})). + should_output "opts = { b = true, this = bit }\n" + - it stops separating at an optional argument option: + expect (parseargs ([[{"x", "this"}, parser.optional]], {"-bxbit"})). + should_output "opts = { b = true, this = bit }\n" diff --git a/specs/specs.mk b/specs/specs.mk index 99ae87a..0e2bf61 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -12,14 +12,16 @@ SPECL_ENV = $(LUA_ENV) ## Specs. ## ## ------ ## +SPECL_OPTS = --unicode + specl_SPECS = \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ - $(srcdir)/specs/getopt_spec.yaml \ $(srcdir)/specs/io_spec.yaml \ $(srcdir)/specs/list_spec.yaml \ $(srcdir)/specs/math_spec.yaml \ $(srcdir)/specs/object_spec.yaml \ + $(srcdir)/specs/optparse_spec.yaml \ $(srcdir)/specs/package_spec.yaml \ $(srcdir)/specs/set_spec.yaml \ $(srcdir)/specs/strbuf_spec.yaml \ From 48c0f0adb3a77503cae2c434941a68b030805d78 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 17 Jan 2014 11:38:06 +1300 Subject: [PATCH 051/703] maint: workaround a luarocks bug handling debug_init.lua. LuaRocks misinstalls build.modules entries ending in `init.lua`, so rearrange the source tree and Automake rules so that `require "std.debug_init"` still works as before even though the file ends up in the wrong directory after installation. * lib/std/debug_init.lua: Move from here... * lib/std/debug_init/init.lua: ...to here. * slingshot: upgrade for `.../init.lua` handling in mkrockspecs. * local.mk (dist_luastd_DATA): Remove lib/std/debug_init.lua. (dist_luastddebug_DATA): New installation dir. Add lib/std/debug_init/init.lua. Signed-off-by: Gary V. Vaughan --- lib/std/{debug_init.lua => debug_init/init.lua} | 0 local.mk | 14 +++++++++++++- slingshot | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) rename lib/std/{debug_init.lua => debug_init/init.lua} (100%) diff --git a/lib/std/debug_init.lua b/lib/std/debug_init/init.lua similarity index 100% rename from lib/std/debug_init.lua rename to lib/std/debug_init/init.lua diff --git a/local.mk b/local.mk index baf2a23..fc2170b 100644 --- a/local.mk +++ b/local.mk @@ -65,7 +65,6 @@ dist_luastd_DATA = \ lib/std/base.lua \ lib/std/container.lua \ lib/std/debug.lua \ - lib/std/debug_init.lua \ lib/std/functional.lua \ lib/std/io.lua \ lib/std/list.lua \ @@ -82,6 +81,19 @@ dist_luastd_DATA = \ lib/std/tree.lua \ $(NOTHING_ELSE) + +# For bugwards compatibility with LuaRocks 2.1, while ensuring that +# `require "std.debug_init"` continues to work, we have to install +# the former `$(luadir)/std/debug_init.lua` to `debug_init/init.lua`. +# When everyone has upgraded to a LuaRocks that works, move this +# file back to dist_luastd_DATA above and rename to debug_init.lua. + +luastddebugdir = $(luastddir)/debug_init + +dist_luastddebug_DATA = \ + lib/std/debug_init/init.lua \ + $(NOTHING_ELSE) + # In order to avoid regenerating std.lua at configure time, which # causes the documentation to be rebuilt and hence requires users to # have ldoc installed, put std/std.lua in as a Makefile dependency. diff --git a/slingshot b/slingshot index a924fa6..74ebb11 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit a924fa68437e3d3e1c41c0d242db4f995257cc77 +Subproject commit 74ebb112224e617387a925466c77b5e798a387c8 From 55f3a34c7c25d53a1644edc96c8d084ba59b955a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 17 Jan 2014 13:05:58 +1300 Subject: [PATCH 052/703] maint: cosmetic fix to imported module list. * lib/std/modules.lua: Replace `getopt` with `optparse`. Signed-off-by: Gary V. Vaughan --- lib/std/modules.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/modules.lua b/lib/std/modules.lua index 2beaf18..d023b1b 100644 --- a/lib/std/modules.lua +++ b/lib/std/modules.lua @@ -6,10 +6,10 @@ return { debug = true, debug_init = false, functional = false, - getopt = false, io = true, list = false, math = true, + optparse = false, package = true, set = false, strbuf = false, From 1dafb591c0fd3bde52e7da6c20a0ca93eda30eb6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 17 Jan 2014 13:10:02 +1300 Subject: [PATCH 053/703] rockspecs: update detailed description text. * rockspec.conf (description.detailed): Remove mention of regexps and getopt. Add mention of civilised option parsing. Signed-off-by: Gary V. Vaughan --- rockspec.conf | 4 ++-- rockspecs.lua | 46 ---------------------------------------------- 2 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 rockspecs.lua diff --git a/rockspec.conf b/rockspec.conf index c7813c7..2e1e688 100644 --- a/rockspec.conf +++ b/rockspec.conf @@ -6,8 +6,8 @@ description: summary: General Lua Libraries detailed: stdlib is a library of modules for common programming tasks, - including list, table and functional operations, regexps, objects, - pickling, pretty-printing and getopt. + including list, table and functional operations, objects, + pickling, pretty-printing and command-line option parsing. dependencies: - lua >= 5.1 diff --git a/rockspecs.lua b/rockspecs.lua deleted file mode 100644 index aec97fd..0000000 --- a/rockspecs.lua +++ /dev/null @@ -1,46 +0,0 @@ --- Rockspec data - --- Variables to be interpolated: --- --- package_name --- version - -local version_dashed = version:gsub ("%.", "-") - -local default = { - package = package_name, - version = version.."-1", - source = { - url = "git://github.com/rrthomas/lua-stdlib.git", - }, - description = { - summary = "General Lua libraries", - detailed = [[ - stdlib is a library of modules for common programming tasks, - including list, table and functional operations, regexps, objects, - pickling, pretty-printing and getopt. - ]], - homepage = "http://github.com/rrthomas/lua-stdlib/", - license = "MIT/X11", - }, - dependencies = { - "lua >= 5.1", - }, - build = { - type = "command", - build_command = "./configure " .. - "LUA_INCLUDE=$(LUA_INCDIR) LUA=$(LUA) CPPFLAGS=-I$(LUA_INCDIR) " .. - "--prefix=$(PREFIX) --libdir=$(LIBDIR) --datadir=$(LUADIR) " .. - "&& make clean all", - install_command = "make install luadir=$(LUADIR)", - copy_directories = {}, - }, -} - -if version ~= "git" then - default.source.branch = "release-v"..version_dashed -else - default.build.build_command = "./bootstrap && " .. default.build.build_command -end - -return {default=default, [""]={}} From 3507e84284756ef8721b6f285e2cbcc311dde916 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 17 Jan 2014 17:35:00 +1300 Subject: [PATCH 054/703] maint: plug symbol leaks with working specifications. * specs/spec_helper.lua (show_apis): New function. Compare table entries in a sub-process, to support tracking namespace changes when requiring modules by various means. * specs/functional_spec.yaml: New file. Use it to ensure "std.functional" doesn't leak symbols. * specs/debug_spec.yaml, specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/math_spec.yaml, specs/object_spec.yaml, specs/optparse_spec.yaml, specs/package_spec.yaml, specs/set_spec.yaml, specs/spec_helper.lua, specs/specs.mk, specs/strbuf_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml, specs/tree_spec.yaml: Rewrite specs that check symbol leaks using show_apis(). * lib/std/base.lua (new): Don't forget the forward declaration. * lib/std/set.lua (proper_subset): Likewise. * NEWS: Update. Reported by Dirk Laurie Signed-off-by: Gary V. Vaughan --- NEWS | 5 ++ lib/std/base.lua | 2 + lib/std/set.lua | 3 +- specs/container_spec.yaml | 9 ++- specs/debug_spec.yaml | 71 ++++++--------------- specs/functional_spec.yaml | 55 ++++++++++++++++ specs/io_spec.yaml | 105 ++++++++++++++----------------- specs/list_spec.yaml | 8 ++- specs/math_spec.yaml | 39 ++++++++++++ specs/object_spec.yaml | 9 ++- specs/optparse_spec.yaml | 9 ++- specs/package_spec.yaml | 79 ++++++++--------------- specs/set_spec.yaml | 8 ++- specs/spec_helper.lua | 120 +++++++++++++++++++++++++++++++++++ specs/specs.mk | 1 + specs/strbuf_spec.yaml | 9 ++- specs/string_spec.yaml | 125 +++++++++++++++++++------------------ specs/table_spec.yaml | 98 +++++++++++++---------------- specs/tree_spec.yaml | 33 +++++++--- 19 files changed, 496 insertions(+), 292 deletions(-) create mode 100644 specs/functional_spec.yaml diff --git a/NEWS b/NEWS index 65b781f..ce8f325 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,11 @@ Stdlib NEWS - User visible changes - 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. + ** Incompatible changes: - `std.getopt` is no more. It appears to have no users, though if there diff --git a/lib/std/base.lua b/lib/std/base.lua index bc26f82..0ac5aad 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -31,6 +31,8 @@ local function merge (t, u) return t end +local new -- forward declaration + -- Doc-commented in list.lua... local function append (l, x) local r = {unpack (l)} diff --git a/lib/std/set.lua b/lib/std/set.lua index 2eb032c..5d8034b 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -63,7 +63,8 @@ end -- High level methods (representation-independent) -local difference, symmetric_difference, intersection, union, subset, equal +local difference, symmetric_difference, intersection, union, subset, + proper_subset, equal --- Find the difference of two sets. diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 34a8ef0..ab37b67 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -1,8 +1,15 @@ before: + require "spec_helper" Container = require "std.container" prototype = (require "std.object").prototype -specify Container: +specify std.container: +- context when required: + - context by name: + - it does not touch the global table: + expect (show_apis {added_to="_G", by="std.container"}). + should_equal {} + - describe construction: - context from Container prototype: - before: diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 6ba7acc..038fe7c 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -1,60 +1,29 @@ -specify debug: -- before: | - M = require "std.debug" +before: | + require "spec_helper" - extends = debug - enhancements = {} - extensions = { "say", "trace" } + this_module = "std.debug" -- context when required: - - before: - enhanced = {} - for _, api in ipairs (enhancements) do enhanced[api] = true end + global_table = "_G" - - context by name: - - before: | - function restore (g, m) - for _, api in ipairs (enhancements) do - g[api], g["_" .. api] = m[api], m["_" .. api] - end - for _, api in ipairs (extensions) do g[api] = m[api] end - end + base_module = "debug" + extend_base = { "say", "trace" } + + M = require "std.debug" - for _, api in ipairs (enhancements) do - extends[api] = M["_" .. api] - end - for _, api in ipairs (extensions) do extends[api] = nil end - - after: - restore (extends, M) - - it does not perturb the global table: - for _, api in ipairs (extensions) do - expect (extends[api]).should_be (nil) - end - for _, api in ipairs (enhancements) do - expect (extends[api]).should_be (M["_" .. api]) - end - - it contains all global access points: - for api in pairs (extends) do - if enhanced[api] then - expect (M[api]).should_not_be (extends[api]) - else - expect (M[api]).should_be (extends[api]) - end - end +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}). + should_equal {} + - it contains apis from the core debug table: + expect (show_apis {from=base_module, not_in=this_module}). + should_contain.a_permutation_of (extend_base) - context via the std module: - - before: - require "std" - - it adds extension apis to the global table: - for api in pairs (M) do - expect (extends[api]).should_be (M[api]) - end - - it does not add any other global access points: - for api in pairs (extends) do - if not enhanced[api] then - expect (M[api]).should_be (extends[api]) - end - end + - it adds apis to the core debug table: + expect (show_apis {added_to=base_module, by="std"}). + should_contain.a_permutation_of (extend_base) - describe _DEBUG: diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml new file mode 100644 index 0000000..26b87ab --- /dev/null +++ b/specs/functional_spec.yaml @@ -0,0 +1,55 @@ +before: | + require "spec_helper" + + global_table = "_G" + this_module = "std.functional" + std_globals = { "bind", "collect", "compose", "curry", "eval", + "filter", "fold", "id", "map", "memoize", + "metamethod", "op" } + + M = require (this_module) + +specify std.functional: +- context when required: + - context by name: + - it does not touch the global table: + expect (show_apis {added_to=global_table, by=this_module}). + should_equal {} + + - context via the std module: + - it adds apis to the global table: + expect (show_apis {added_to=global_table, by="std"}). + should_contain.all_of (std_globals) + + +- describe bind: + + +- describe collect: + + +- describe compose: + + +- describe curry: + + +- describe eval: + + +- describe filter: + + +- describe fold: + + +- describe id: + + +- describe map: + + +- describe memoize: + + +- describe metamethod: diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 758060a..4c82dbf 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -1,64 +1,47 @@ -specify io: -- before: | - M = require "std.io" - - extends = io - enhancements = {} - extensions = { "catdir", "catfile", "die", "process_files", - "readlines", "shell", "slurp", "splitdir", "warn", - "writelines", - -- camelCase compatibility: - "processFiles" } +before: | + require "spec_helper" -- context when required: - - before: - enhanced = {} - for _, api in ipairs (enhancements) do enhanced[api] = true end + this_module = "std.io" + + global_table = "_G" + std_globals = { "die", "warn" } + + base_module = "io" + extend_base = { "catdir", "catfile", "die", "process_files", + "readlines", "shell", "slurp", "splitdir", + "warn", "writelines", + -- camelCase compatibility: + "processFiles" } + extend_metamethods = { "readlines", "writelines" } + M = require (this_module) + +specify std.io: +- context when required: - context by name: - - before: | - function restore (g, m) - for _, api in ipairs (enhancements) do - g[api], g["_" .. api] = m[api], m["_" .. api] - end - for _, api in ipairs (extensions) do g[api] = m[api] end - end - - for _, api in ipairs (enhancements) do - extends[api] = M["_" .. api] - end - for _, api in ipairs (extensions) do extends[api] = nil end - - after: - restore (extends, M) - - it does not perturb the global table: - for _, api in ipairs (extensions) do - expect (extends[api]).should_be (nil) - end - for _, api in ipairs (enhancements) do - expect (extends[api]).should_be (M["_" .. api]) - end - - it contains all global access points: - for api in pairs (extends) do - if enhanced[api] then - expect (M[api]).should_not_be (extends[api]) - else - expect (M[api]).should_be (extends[api]) - end - end + - it does not touch the global table: + expect (show_apis {added_to=global_table, by=this_module}). + should_equal {} + - it contains apis from the core io table: + expect (show_apis {from=base_module, not_in=this_module}). + should_contain.a_permutation_of (extend_base) + - it replaces no apis from the core io table: + expect (show_apis {from=base_module, enhanced_in=this_module}). + should_equal {} - context via the std module: - - before: - require "std" - - it adds extension apis to the global table: - for api in pairs (M) do - expect (extends[api]).should_be (M[api]) - end - - it does not add any other global access points: - for api in pairs (extends) do - if not enhanced[api] then - expect (M[api]).should_be (extends[api]) - end - end + - it adds apis to the global table: + expect (show_apis {added_to=global_table, by="std"}). + should_contain.all_of (std_globals) + - it adds apis to the core io table: + expect (show_apis {added_to=base_module, by="std"}). + should_contain.a_permutation_of (extend_base) + - it adds methods to the file metatable: + expect (show_apis {added_to="getmetatable (io.stdin)", by="std"}). + should_contain.a_permutation_of (extend_metamethods) + - it replaces no apis from the core io table: + expect (show_apis {from=base_module, enhanced_after='require "std"'}). + should_equal {} - describe catdir: @@ -67,11 +50,12 @@ specify io: - describe catfile: +- describe die: + + - describe process_files: - - before: - subject = M.process_files - it is the same function as legacy processFiles call: - expect (io.processFiles).should_be (subject) + expect (M.process_files).should_be (M.processFiles) - describe readlines: @@ -86,4 +70,7 @@ specify io: - describe splitdir: +- describe warn: + + - describe writelines: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index a80749a..523b058 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -5,7 +5,13 @@ before: l = List {"foo", "bar", "baz"} -specify List: +specify std.list: +- context when required: + - context by name: + - it does not touch the global table: + expect (show_apis {added_to="_G", by="std.list"}). + should_equal {} + - describe construction: - context from List clone method: - it constructs a new list: diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index d812d4a..5932064 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -1,3 +1,42 @@ +before: | + require "spec_helper" + + this_module = "std.math" + + global_table = "_G" + base_module = "math" + extend_base = { "round", "_floor" } + enhance_base = { "floor" } + + -- 'should_contain' will match keys as well as values :) + all_apis = {} + for _, s in ipairs (extend_base) do all_apis[s] = true end + for _, s in ipairs (enhance_base) do all_apis[s] = true end + + 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}). + should_equal {} + - it contains apis from the core math table: + expect (show_apis {from=base_module, not_in=this_module}). + should_contain.a_permutation_of (all_apis) + - it enhances some apis from the core math table: + expect (show_apis {from=base_module, enhanced_in=this_module}). + should_contain.a_permutation_of (enhance_base) + + - context via the std module: + - it adds apis to the core math table: + expect (show_apis {added_to=base_module, by="std"}). + should_contain.a_permutation_of (extend_base) + - it replaces some apis from the core math table: + expect (show_apis {from=base_module, enhanced_after='require "std"'}). + should_contain.a_permutation_of (enhance_base) + + specify math: - before: | M = require "std.math" diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 2a07af2..c0102f3 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -1,9 +1,16 @@ before: + require "spec_helper" Object = require "std.object" obj = Object {"foo", "bar", baz="quux"} prototype = Object.prototype -specify Object: +specify std.object: +- context when required: + - context by name: + - it does not touch the global table: + expect (show_apis {added_to="_G", by="std.object"}). + should_equal {} + - describe construction: - context from Object clone method: - it constructs a new object: diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index 5e04947..a3547fb 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -1,7 +1,8 @@ before: + require "spec_helper" hell = require "specl.shell" -specify optparse: +specify std.optparse: - before: | OptionParser = require "std.optparse" @@ -39,6 +40,12 @@ specify optparse: -- strip off the leading whitespace required for YAML parser = OptionParser (help:gsub ("^ ", "")) +- context when required: + - context by name: + - it does not touch the global table: + expect (show_apis {added_to="_G", by="std.optparse"}). + should_equal {} + - describe OptionParser: - it recognises the program name: expect (parser.program).should_be "parseme" diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index c7b4bbd..bf561d2 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -1,63 +1,38 @@ -specify package: -- before: | - M = require "std.package" +before: | + require "spec_helper" - extends = package - extensions = { "dirsep", "pathsep", "path_mark", "execdir", "igmark" } - enhancements = {} + this_module = "std.package" - -- Do not try to check all the entries in unextended package, - -- because they naturally change as modules are loaded. - coreapis = { "config", "cpath", "loaders", "loadlib", "preload", - "searchers", "searchpath", "seeall" } + global_table = "_G" + base_module = "package" + extend_base = { "dirsep", "pathsep", "path_mark", + "execdir", "igmark" } -- context when required: - - before: - enhanced = {} - for _, api in ipairs (enhancements) do enhanced[api] = true end + M = require (this_module) +specify std.package: +- context when required: - context by name: - - before: - for _, api in ipairs (enhancements) do - extends[api] = M["_" .. api] - end - for _, api in ipairs (extensions) do extends[api] = nil end - - after: - for _, api in ipairs (enhancements) do - extends[api], extends["_" .. api] = M[api], M["_" .. api] - end - for _, api in ipairs (extensions) do extends[api] = M[api] end - - it does not perturb the global table: - for _, api in ipairs (extensions) do - expect (extends[api]).should_be (nil) - end - for _, api in ipairs (enhancements) do - expect (extends[api]).should_be (M["_" .. api]) - end - - it contains all global access points: - for _, api in ipairs (coreapis) do - if enhanced[api] then - expect (M[api]).should_not_be (extends[api]) - else - expect (M[api]).should_be (extends[api]) - end - end + - it does not touch the global table: + expect (show_apis {added_to=global_table, by=this_module}). + should_equal {} + - it contains apis from the core package table: + expect (show_apis {from=base_module, not_in=this_module}). + should_contain.a_permutation_of (extend_base) + - it replaces no apis from the core package table: + expect (show_apis {from=base_module, enhanced_in=this_module}). + should_equal {} - context via the std module: - - before: - require "std" - - it adds extension apis to the global table: - for _, api in ipairs (extensions) do - expect (extends[api]).should_be (M[api]) - end - - it does not add any other global access points: - for _, api in ipairs (coreapis) do - if not enhanced[api] then - expect (M[api]).should_be (extends[api]) - end - end + - it adds apis to the core package table: + expect (show_apis {added_to=base_module, by="std"}). + should_contain.a_permutation_of (extend_base) + - it replaces no apis from the core package table: + expect (show_apis {from=base_module, enhanced_after='require "std"'}). + should_equal {} + -- "it splits package.config up": +- it splits package.config up: expect (string.format ("%s\n%s\n%s\n%s\n%s\n", M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark) ).should_contain (package.config) diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index 4e7f931..9f96e3a 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -5,7 +5,13 @@ before: totable = (require "std.table").totable s = Set {"foo", "bar", "bar"} -specify Set: +specify std.set: +- describe require: + - it does not perturb the global namespace: + expect (show_apis {added_to="_G", by="std.set"}). + should_equal {} + + - describe construction: - it constructs a new set: s = Set {} diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 2480cc1..e8753fd 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -1,3 +1,123 @@ +local hell = require "specl.shell" + +local LUA = "lua" + +if _G.arg ~= nil then + local i = 0 + while _G.arg[i - 1] ~= nil do + i = i - 1 + LUA = _G.arg[i] + end +end + +local function mkscript (code) + local f = os.tmpname () + local h = io.open (f, "w") + h:write (code) + h:close () + return f +end + +local function tabulate_output (code) + local f = mkscript (code) + local proc = hell.spawn { + LUA, f; + env = { LUA_PATH=package.path, LUA_INIT="", LUA_INIT_5_2="" }, + } + os.remove (f) + 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 from = ]] .. from .. [[ + local M = require "]] .. not_in .. [[" + + for k in pairs (M) do + if from[k] ~= M[k] then print (k) end + end + ]]) + + elseif from and enhanced_in then + return tabulate_output ([[ + local from = ]] .. from .. [[ + local 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 before, after = {}, {} + local from = ]] .. from .. [[ + + 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 + + -- Not local, so that it is available in spec examples. totable = (require "std.table").totable diff --git a/specs/specs.mk b/specs/specs.mk index 0e2bf61..0669ba8 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -17,6 +17,7 @@ SPECL_OPTS = --unicode specl_SPECS = \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ + $(srcdir)/specs/functional_spec.yaml \ $(srcdir)/specs/io_spec.yaml \ $(srcdir)/specs/list_spec.yaml \ $(srcdir)/specs/math_spec.yaml \ diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index 11bdc9a..6851dcd 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -1,9 +1,16 @@ before: + require "spec_helper" object = require "std.object" StrBuf = require "std.strbuf" b = StrBuf {"foo", "bar"} -specify StrBuf: +specify std.strbuf: +- describe require: + - it does not perturb the global namespace: + expect (show_apis {added_to="_G", by="std.strbuf"}). + should_equal {} + + - describe construction: - context from StrBuf clone method: - it constructs a new strbuf: diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index d4de247..e141a41 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -1,70 +1,73 @@ -specify string: -- before: | - M = require "std.string" - - extends = string - enhancements = { "assert", "format", "tostring" } - extensions = { "caps", "chomp", "escape_pattern", "escape_shell", - "finds", "ltrim", "numbertosi", "ordinal_suffix", - "pad", "pickle", "prettytostring", "render", - "require_version", "rtrim", "split", "tfind", - "trim", "wrap", - -- make these available after require "std" - "__index", "_format", "_tostring", - -- camelCase compatibility: - "escapePattern", "escapeShell", "ordinalSuffix" } - +before: | + require "spec_helper" + + this_module = "std.string" + + global_table = "_G" + std_globals = { "pickle", "prettytostring", "render", + "require_version" } + enhance_globals = { "assert", "tostring" } + + base_module = "string" + extend_base = { "assert", "caps", "chomp", "escape_pattern", + "escape_shell", "finds", "ltrim", "numbertosi", + "ordinal_suffix", "pad", "pickle", + "prettytostring", "render", "require_version", + "rtrim", "split", "tfind", "tostring", "trim", + "wrap", + -- make these available after require "std" + "__index", "_format", "_tostring", + -- camelCase compatibility: + "escapePattern", "escapeShell", + "ordinalSuffix" } + enhance_base = { "format" } + extend_metamethods = { "__append", "__concat" } + enhance_metamethods = { "__index" } + + -- 'should_contain' will match keys as well as values :) + all_apis = {} + for _, s in ipairs (std_globals) do all_apis[s] = true end + for _, s in ipairs (enhance_globals) do all_apis[s] = true end + for _, s in ipairs (extend_base) do all_apis[s] = true end + for _, s in ipairs (enhance_base) do all_apis[s] = true end + + M = require "std.string" + +specify std.string: +- before: subject = "a string \n\n" - - context when required: - - before: - enhanced = {} - for _, api in ipairs (enhancements) do enhanced[api] = true end - - context by name: - - before: | - for _, api in ipairs (enhancements) do - extends[api] = M["_" .. api] - end - for _, api in ipairs (extensions) do extends[api] = nil end - - after: - for _, api in ipairs (enhancements) do - extends[api], extends["_" .. api] = M[api], M["_" .. api] - end - for _, api in ipairs (extensions) do extends[api] = M[api] end - - it does not perturb the global table: - for _, api in ipairs (extensions) do - expect (extends[api]).should_be (nil) - end - for _, api in ipairs (enhancements) do - expect (extends[api]).should_be (M["_" .. api]) - end - - it contains all global access points: - for api in pairs (extends) do - if enhanced[api] then - expect (M[api]).should_not_be (extends[api]) - else - expect (M[api]).should_be (extends[api]) - end - end + - it does not touch the global table: + expect (show_apis {added_to=global_table, by=this_module}). + should_equal {} + - it contains apis from the core string table: + expect (show_apis {from=base_module, not_in=this_module}). + should_contain.a_permutation_of (all_apis) + - it enhances some apis from the core string table: + expect (show_apis {from=base_module, enhanced_in=this_module}). + should_contain.a_permutation_of (enhance_base) - context via the std module: - - before: - require "std" - - it adds extension apis to the global table: - for api in pairs (M) do - expect (extends[api]).should_be (M[api]) - end - - it does not add any other global access points: - for api in pairs (extends) do - if not enhanced[api] then - expect (M[api]).should_be (extends[api]) - end - end - - -- "describe ..": + - it adds apis to the global table: + expect (show_apis {added_to=global_table, by="std"}). + should_contain.all_of (std_globals) + - it adds apis to the core string table: + expect (show_apis {added_to=base_module, by="std"}). + should_contain.a_permutation_of (extend_base) + - it adds methods to the string metatable: + expect (show_apis {added_to="getmetatable ('')", by="std"}). + should_contain.a_permutation_of (extend_metamethods) + - it replaces some entries in the string metatable: + expect (show_apis {from="getmetatable ('')", enhanced_after='require "std"'}). + should_contain.a_permutation_of (enhance_metamethods) + - it replaces some apis in the core string table: + expect (show_apis {from=base_module, enhanced_after='require "std"'}). + should_contain.a_permutation_of (enhance_base) + + +- describe ..: - it concatenates string arguments: target = "a string \n\n another string" expect (subject .. " another string").should_be (target) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 2620d5c..1de200a 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -1,62 +1,50 @@ -specify table: -- before: | - M = require "std.table" - - extends = table - enhancements = { "sort" } - extensions = { "clone", "clone_rename", "empty", "invert", "keys", - "merge", "new", "pack", "ripairs", "size", "totable", - "values" } - +before: | + require "spec_helper" + + base_module = "table" + this_module = "std.table" + global_table = "_G" + + std_globals = { "pack", "ripairs", "totable" } + extend_base = { "clone", "clone_rename", "empty", + "invert", "keys", "merge", "new", + "pack", "ripairs", "size", "totable", + "values", + -- make these available after require "std" + "_sort" } + enhance_base = { "sort" } + + -- 'should_contain' will match keys as well as values :) + all_apis = {} + for _, s in ipairs (std_globals) do all_apis[s] = true end + for _, s in ipairs (extend_base) do all_apis[s] = true end + for _, s in ipairs (enhance_base) do all_apis[s] = true end + + M = require "std.table" + +specify std.table: - context when required: - - before: - enhanced = {} - for _, api in ipairs (enhancements) do enhanced[api] = true end - - context by name: - - before: | - function restore (g, m) - for _, api in ipairs (enhancements) do - g[api], g["_" .. api] = m[api], m["_" .. api] - end - for _, api in ipairs (extensions) do g[api] = m[api] end - end - - for _, api in ipairs (enhancements) do - extends[api] = M["_" .. api] - end - for _, api in ipairs (extensions) do extends[api] = nil end - - after: - restore (extends, M) - - it does not perturb the global table: - for _, api in ipairs (extensions) do - expect (extends[api]).should_be (nil) - end - for _, api in ipairs (enhancements) do - expect (extends[api]).should_be (M["_" .. api]) - end - - it contains all global access points: - for api in pairs (extends) do - if enhanced[api] then - expect (M[api]).should_not_be (extends[api]) - else - expect (M[api]).should_be (extends[api]) - end - end + - it does not touch the global table: + expect (show_apis {added_to=global_table, by=this_module}). + should_equal {} + - it contains apis from the core table table: + expect (show_apis {from=base_module, not_in=this_module}). + should_contain.a_permutation_of (all_apis) + - it enhances some apis from the core table table: + expect (show_apis {from=base_module, enhanced_in=this_module}). + should_contain.a_permutation_of (enhance_base) - context via the std module: - - before: - require "std" - - it adds extension apis to the global table: - for api in pairs (M) do - expect (extends[api]).should_be (M[api]) - end - - it does not add any other global access points: - for api in pairs (extends) do - if not enhanced[api] then - expect (M[api]).should_be (extends[api]) - end - end + - it adds apis to the global table: + expect (show_apis {added_to=global_table, by="std"}). + should_contain.all_of (std_globals) + - it adds apis to the core table table: + expect (show_apis {added_to=base_module, by="std"}). + should_contain.a_permutation_of (extend_base) + - it replaces some apis in the core table table: + expect (show_apis {from=base_module, enhanced_after='require "std"'}). + should_contain.a_permutation_of (enhance_base) - describe clone: diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index c0520da..d9ec245 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -1,11 +1,30 @@ -before: - Tree = require "std.tree" - prototype = (require "std.object").prototype - totable = (require "std.table").totable - t = {foo="foo", fnord={branch={bar="bar", baz="baz"}}, quux="quux"} - tr = Tree (t) +before: | + require "spec_helper" + + global_table = "_G" + this_module = "std.tree" + std_globals = { "ileaves", "inodes", "leaves", "nodes" } + + Tree = require "std.tree" + +specify std.tree: +- before: + prototype = (require "std.object").prototype + totable = (require "std.table").totable + t = {foo="foo", fnord={branch={bar="bar", baz="baz"}}, quux="quux"} + tr = Tree (t) + +- context when required: + - context by name: + - it does not touch the global table: + expect (show_apis {added_to=global_table, by=this_module}). + should_equal {} + + - context via the std module: + - it adds apis to the global table: + expect (show_apis {added_to=global_table, by="std"}). + should_contain.all_of (std_globals) -specify tree: - describe construction: - it constructs a new tree: tr = Tree {} From 7c1fec4b90f7454054277ebc67252758e7386696 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 17 Jan 2014 17:52:44 +1300 Subject: [PATCH 055/703] doc: add the package name and version to html doc page titles. * doc/config.ld: Move from here... * doc/config.ld.in: ...to here. Add a title setting incorporating @PACKAGE@ and @VERSION@. * configure.ac (AC_CONFIG_FILES): Add doc/config.ld. * .gitignore: Add doc/config.ld. Suggested by Dirk Laurie Signed-off-by: Gary V. Vaughan --- .gitignore | 1 + configure.ac | 2 +- doc/{config.ld => config.ld.in} | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) rename doc/{config.ld => config.ld.in} (93%) diff --git a/.gitignore b/.gitignore index 94e8f96..67af2d0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ /config.status /configure /doc/classes +/doc/config.ld /doc/files /doc/index.html /doc/ldoc.css diff --git a/configure.ac b/configure.ac index 8bde8c8..4399756 100644 --- a/configure.ac +++ b/configure.ac @@ -37,5 +37,5 @@ AC_PROG_SED dnl Generate output files SS_CONFIG_TRAVIS([ldoc specl]) -AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([Makefile doc/config.ld]) AC_OUTPUT diff --git a/doc/config.ld b/doc/config.ld.in similarity index 93% rename from doc/config.ld rename to doc/config.ld.in index bed75e2..fb4576f 100644 --- a/doc/config.ld +++ b/doc/config.ld.in @@ -1,4 +1,5 @@ -- -*- lua -*- +title = "@PACKAGE@ @VERSION@ Reference" project = "stdlib" description = "Standard Lua Libraries" dir = "." From d15726ec7854e383d6230f2359522de640c11f04 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 17 Jan 2014 18:44:01 +1300 Subject: [PATCH 056/703] std: lazy load submodules on demand. * specs/std_spec.yaml: New specifications for lazy loading. * specs/specs.mk (specl_SPECS): Add specs/std_spec.yaml. * lib/std.lua.in (std.__index): Implement it. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 7 +++++++ lib/std.lua.in | 31 +++++++++++++++++++++++++++++-- specs/specs.mk | 1 + specs/std_spec.yaml | 12 ++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 specs/std_spec.yaml diff --git a/NEWS b/NEWS index ce8f325..3bfdfe5 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,13 @@ Stdlib NEWS - User visible changes ** 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: + + 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. diff --git a/lib/std.lua.in b/lib/std.lua.in index 508f0b3..da0fb77 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -19,12 +19,21 @@ @module std ]] + --- Module table. +-- Lazy load 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: +-- +-- local std = require "std" +-- local prototype = std.container.prototype -- @table std -- @field version release version string local version = "General Lua libraries / @VERSION@" -for m, globally in pairs (require "std.modules") do +local modules = require "std.modules" + +for m, globally in pairs (modules) do if globally == true then -- Inject stdlib extensions directly into global package namespaces. for k, v in pairs (require ("std." .. m)) do @@ -193,4 +202,22 @@ local M = { version = version, } -return M + +--- 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 + -- @return the submodule that was loaded to satisfy the missing `name` + __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/specs/specs.mk b/specs/specs.mk index 0669ba8..b8644b9 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -25,6 +25,7 @@ specl_SPECS = \ $(srcdir)/specs/optparse_spec.yaml \ $(srcdir)/specs/package_spec.yaml \ $(srcdir)/specs/set_spec.yaml \ + $(srcdir)/specs/std_spec.yaml \ $(srcdir)/specs/strbuf_spec.yaml \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml new file mode 100644 index 0000000..f8f758c --- /dev/null +++ b/specs/std_spec.yaml @@ -0,0 +1,12 @@ +specify std: +- describe lazy loading: + - before: + std = require "std" + - it has no submodules on initial load: + expect (std).should_equal {version = std.version} + - it loads submodules on demand: + lazy = std.set + expect (lazy).should_be (require "std.set") + - it loads submodule functions on demand: + expect (std.object.prototype (std.set {"Lazy"})). + should_be "Set" From d7f3abe7a0ff6ccf9c788d17ea539bff392f5c30 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 17 Jan 2014 19:16:12 +1300 Subject: [PATCH 057/703] specs: compensate for table.pack differences between Lua 5.1/5.2. * specs/table_spec (before): Make sure `extend_base` and `enhance_base` reflect whether core table library has a `pack` entry. Signed-off-by: Gary V. Vaughan --- specs/table_spec.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 1de200a..d33cbf9 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -8,18 +8,26 @@ before: | std_globals = { "pack", "ripairs", "totable" } extend_base = { "clone", "clone_rename", "empty", "invert", "keys", "merge", "new", - "pack", "ripairs", "size", "totable", - "values", + "ripairs", "size", "totable", "values", -- make these available after require "std" "_sort" } enhance_base = { "sort" } + -- 'should_contain' will match keys as well as values :) all_apis = {} for _, s in ipairs (std_globals) do all_apis[s] = true end for _, s in ipairs (extend_base) do all_apis[s] = true end for _, s in ipairs (enhance_base) do all_apis[s] = true end + if table.pack then + -- Lua 5.2 + table.insert (enhance_base, "pack") + else + -- Lua 5.1 + table.insert (extend_base, "pack") + end + M = require "std.table" specify std.table: From f4998917d73c3ad1a31a0fb24c62f063cb4148e3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 18 Jan 2014 08:43:38 +1300 Subject: [PATCH 058/703] specs: compatibility with Specl < 11. * specs/specs.mk (specl_SPECS): Move std_spec.yaml to the end of the list, where symbol leaks don't affect subsequent examples. Signed-off-by: Gary V. Vaughan --- specs/specs.mk | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/specs/specs.mk b/specs/specs.mk index b8644b9..7e72d0a 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -14,6 +14,11 @@ SPECL_ENV = $(LUA_ENV) SPECL_OPTS = --unicode +## For compatibility with Specl < 11, std_spec.yaml has to be +## last, so that when `require "std"` leaks symbols into the +## Specl global environment, subsequent example blocks are not +## affected. + specl_SPECS = \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ @@ -25,11 +30,11 @@ specl_SPECS = \ $(srcdir)/specs/optparse_spec.yaml \ $(srcdir)/specs/package_spec.yaml \ $(srcdir)/specs/set_spec.yaml \ - $(srcdir)/specs/std_spec.yaml \ $(srcdir)/specs/strbuf_spec.yaml \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ + $(srcdir)/specs/std_spec.yaml \ $(NOTHING_ELSE) EXTRA_DIST += \ From 7eb8794b880c386c301f362cb44854a49e5d48f8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 18 Jan 2014 08:45:49 +1300 Subject: [PATCH 059/703] specs: remove duplicate examples. * specs/math_spec.yaml: forgot to remove the old namespace corruption tests from this file when adding new check in 3507e84. Signed-off-by: Gary V. Vaughan --- specs/math_spec.yaml | 60 -------------------------------------------- 1 file changed, 60 deletions(-) diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 5932064..02c2cb0 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -37,66 +37,6 @@ specify std.math: should_contain.a_permutation_of (enhance_base) -specify math: -- before: | - M = require "std.math" - - extends = math - enhancements = { "floor" } - extensions = { "round" } - - -- context when required: - - before: - enhanced = {} - for _, api in ipairs (enhancements) do enhanced[api] = true end - - - context by name: - - before: | - function restore (g, m) - for _, api in ipairs (enhancements) do - g[api], g["_" .. api] = m[api], m["_" .. api] - end - for _, api in ipairs (extensions) do g[api] = m[api] end - end - - for _, api in ipairs (enhancements) do - extends[api] = M["_" .. api] - end - for _, api in ipairs (extensions) do extends[api] = nil end - - after: - restore (extends, M) - - it does not perturb the global table: - for _, api in ipairs (extensions) do - expect (extends[api]).should_be (nil) - end - for _, api in ipairs (enhancements) do - expect (extends[api]).should_be (M["_" .. api]) - end - - it contains all global access points: - for api in pairs (extends) do - if enhanced[api] then - expect (M[api]).should_not_be (extends[api]) - else - expect (M[api]).should_be (extends[api]) - end - end - - - context via the std module: - - before: - require "std" - - it adds extension apis to the global table: - for api in pairs (M) do - expect (extends[api]).should_be (M[api]) - end - - it does not add any other global access points: - for api in pairs (extends) do - if not enhanced[api] then - expect (M[api]).should_be (extends[api]) - end - end - - - describe floor: From 0941b0b57ddb71b351229751aedeac6acf60fcbd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 18 Jan 2014 11:13:15 +1300 Subject: [PATCH 060/703] specs: make sure hell.spawn uses the same Lua as Specl examples. * slingshot: Sync with upstream, for build-aux/specs.mk fix. * configure.ac (AC_CONFIG_FILES): Add specs/spec_helper.lua. * specs/spec_helper.lua: Move from here... * specs/spec_helper.lua.in: ...to here. * .gitignore: Add spec_helper.lua. * specs/specs.mk (SPECL_ENV): Add $builddir/specs to LUA_PATH for generated spec_helper.lua. * specs/spec_helper.lua.in (LUA): Don't dig through `_G.arg`, wait for configure substitution. Signed-off-by: Gary V. Vaughan --- .gitignore | 3 ++- configure.ac | 2 +- slingshot | 2 +- specs/{spec_helper.lua => spec_helper.lua.in} | 14 +++++--------- specs/specs.mk | 5 +++-- 5 files changed, 12 insertions(+), 14 deletions(-) rename specs/{spec_helper.lua => spec_helper.lua.in} (93%) diff --git a/.gitignore b/.gitignore index 67af2d0..4634fab 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,10 @@ /doc/ldoc.css /doc/modules /lib/std.lua -/stdlib-*.tar.gz /luarocks /m4/ax_compare_version.m4 /m4/ax_lua.m4 /m4/slingshot.m4 +/specs/spec_helper.lua +/stdlib-*.tar.gz /travis.yml.in diff --git a/configure.ac b/configure.ac index 4399756..24c91cd 100644 --- a/configure.ac +++ b/configure.ac @@ -37,5 +37,5 @@ AC_PROG_SED dnl Generate output files SS_CONFIG_TRAVIS([ldoc specl]) -AC_CONFIG_FILES([Makefile doc/config.ld]) +AC_CONFIG_FILES([Makefile doc/config.ld specs/spec_helper.lua]) AC_OUTPUT diff --git a/slingshot b/slingshot index 74ebb11..65a3777 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 74ebb112224e617387a925466c77b5e798a387c8 +Subproject commit 65a3777d86204e5990a95894fa010afd069538be diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua.in similarity index 93% rename from specs/spec_helper.lua rename to specs/spec_helper.lua.in index e8753fd..75b4232 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua.in @@ -1,14 +1,10 @@ local hell = require "specl.shell" -local LUA = "lua" - -if _G.arg ~= nil then - local i = 0 - while _G.arg[i - 1] ~= nil do - i = i - 1 - LUA = _G.arg[i] - end -end +-- Substitute configured LUA so that hell.spawn doesn't pick up +-- a different Lua binary to the one used by Specl itself. If +-- we could rely on luaposix availability `posix.getenv` would +-- be a nicer way to find this... +local LUA = "@LUA@" local function mkscript (code) local f = os.tmpname () diff --git a/specs/specs.mk b/specs/specs.mk index 7e72d0a..45c1fcc 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -5,7 +5,8 @@ ## Environment. ## ## ------------ ## -SPECL_ENV = $(LUA_ENV) +specs_path = $(abs_builddir)/specs/?.lua +SPECL_ENV = LUA_PATH="$(specs_path);$(std_path);$(LUA_PATH)" ## ------ ## @@ -38,7 +39,7 @@ specl_SPECS = \ $(NOTHING_ELSE) EXTRA_DIST += \ - $(srcdir)/specs/spec_helper.lua \ + $(srcdir)/specs/spec_helper.lua.in \ $(NOTHING_ELSE) include build-aux/specl.mk From a9835d748ba828d3db271e1621465c7db5e47d17 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 18 Jan 2014 11:59:41 +1300 Subject: [PATCH 061/703] Release version 37 * NEWS: Record release date. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 3bfdfe5..cc9d78b 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ Stdlib NEWS - User visible changes -* Noteworthy changes in release ?.? (????-??-??) [?] +* Noteworthy changes in release 37 (2014-01-18) [stable] ** New features: From 846fd4e578cf8a57c8268979c9fbd2495b0e7b5b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 18 Jan 2014 11:59:56 +1300 Subject: [PATCH 062/703] maint: post-release administrivia. * configure.ac (AC_INIT): Bump version to 38. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS | 3 +++ configure.ac | 2 +- local.mk | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.prev-version b/.prev-version index 7facc89..81b5c5d 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -36 +37 diff --git a/NEWS b/NEWS index cc9d78b..26857e0 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Stdlib NEWS - User visible changes +* Noteworthy changes in release ?.? (????-??-??) [?] + + * Noteworthy changes in release 37 (2014-01-18) [stable] ** New features: diff --git a/configure.ac b/configure.ac index 24c91cd..1b94dd1 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [37], [http://github.com/rrthomas/lua-stdlib/issues]) +AC_INIT([stdlib], [38], [http://github.com/rrthomas/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/local.mk b/local.mk index fc2170b..2c3a5bb 100644 --- a/local.mk +++ b/local.mk @@ -29,7 +29,7 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" ## Bootstrap. ## ## ---------- ## -old_NEWS_hash = 7a7647cb5b2d5d886a18070e1f4ac5fd +old_NEWS_hash = 8e7c5bd2d633a7ea22c0919d3d417a27 update_copyright_env = \ UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ From 7d45e96a7fafbe963b327f2361b17a7edd8e7252 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 19 Jan 2014 16:45:20 +1300 Subject: [PATCH 063/703] Revert "maint: post-release administrivia." This reverts commit 846fd4e578cf8a57c8268979c9fbd2495b0e7b5b. --- .prev-version | 2 +- NEWS | 3 --- configure.ac | 2 +- local.mk | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.prev-version b/.prev-version index 81b5c5d..7facc89 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -37 +36 diff --git a/NEWS b/NEWS index 26857e0..3bfdfe5 100644 --- a/NEWS +++ b/NEWS @@ -2,9 +2,6 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] - -* Noteworthy changes in release 37 (2014-01-18) [stable] - ** New features: - Lazy loading of submodules into `std` on first reference. On initial diff --git a/configure.ac b/configure.ac index 1b94dd1..24c91cd 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [38], [http://github.com/rrthomas/lua-stdlib/issues]) +AC_INIT([stdlib], [37], [http://github.com/rrthomas/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/local.mk b/local.mk index 2c3a5bb..fc2170b 100644 --- a/local.mk +++ b/local.mk @@ -29,7 +29,7 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" ## Bootstrap. ## ## ---------- ## -old_NEWS_hash = 8e7c5bd2d633a7ea22c0919d3d417a27 +old_NEWS_hash = 7a7647cb5b2d5d886a18070e1f4ac5fd update_copyright_env = \ UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ From 2d95d5986e40625f1df29f15114b243af0b04444 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 19 Jan 2014 13:45:26 +1300 Subject: [PATCH 064/703] string: use new syntax for List instantiation. * lib/std/string.lua: List is an Object now. (split): Use `List` instead of `list`. (require_version): Use new syntax for List instantiation. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index 3d58393..08a6b9c 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -4,7 +4,7 @@ ]] local func = require "std.functional" -local list = require "std.list" +local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" @@ -91,7 +91,7 @@ local function split (s, sep) -- flatten the result, discarding the captures, and prepend 0 (1 -- before the first character) and append 0 (1 after the last -- character), and then read off the result in pairs. - local pairs = list.concat ({0}, list.flatten (finds (s, sep)), {0}) + local pairs = List.concat ({0}, List.flatten (finds (s, sep)), {0}) local l = {} for i = 1, #pairs, 2 do table.insert (l, string.sub (s, pairs[i] + 1, pairs[i + 1] - 1)) @@ -107,7 +107,7 @@ end -- `module.VERSION` (default: `".*[%.%d]+"`) local function require_version (module, min, too_big, pattern) local function version_to_list (v) - return list.new (split (v, "%.")) + return List (split (v, "%.")) end local function module_version (module, pattern) return version_to_list (string.match (module.version or module._VERSION, From 747704cc47c93554146f2e07953b9f92074b1f0b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 19 Jan 2014 16:52:05 +1300 Subject: [PATCH 065/703] object: share metatables more aggressively. * lib/std/container.lua (clone): Don't create a new metatable unnecessarily just because the base object has a `_function` element -- which is not copied in any case. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++++ lib/std/container.lua | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 3bfdfe5..0240fe5 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,10 @@ Stdlib NEWS - User visible changes - 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 + ** Incompatible changes: - `std.getopt` is no more. It appears to have no users, though if there diff --git a/lib/std/container.lua b/lib/std/container.lua index 5104569..b5f9874 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -159,7 +159,7 @@ local metatable = { pairs, obj_mt._functions or {}) -- _functions is not propagated from prototype to clone. - if next (obj_mt) == nil and mt._functions == nil then + if next (obj_mt) == nil then -- Reuse metatable if possible obj_mt = getmetatable (self) else From 75f6ef39e072085bd62230869352abb6cb57d80b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 19 Jan 2014 17:01:50 +1300 Subject: [PATCH 066/703] maint: retract release 36. Release 36 intentionally, but unnecessarily, broke backwards compatibility in list, set, strbuf and tree. Reinstate compatibility with v35 and earlier where possible. * lib/std/list.lua (list.filter, list.foldl, list.foldr) (list.indexKey, list.indexValue, list.map, list.mapWith) (list.new, list.project, list.shape, list.zipWith): Accept parameters in the original v35 order. * lib/std/set.lua, lib/std/strbuf.lua, lib/std/tree.lua (new): Reinstate for undocumented backwards compatibility. * News: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 ++++++++ lib/std/list.lua | 15 +++++++++++++++ lib/std/set.lua | 35 +++++++++++++++++++++-------------- lib/std/strbuf.lua | 5 +++++ lib/std/tree.lua | 28 +++++++++++++++++----------- 5 files changed, 66 insertions(+), 25 deletions(-) diff --git a/NEWS b/NEWS index 0240fe5..a690003 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,14 @@ Stdlib NEWS - User visible changes 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. + ** Incompatible changes: - `std.getopt` is no more. It appears to have no users, though if there diff --git a/lib/std/list.lua b/lib/std/list.lua index 5cb0ea8..96b2bf8 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -427,6 +427,21 @@ List = Object { mapWith = map_with, zipWith = zip_with, }), + + -- backwards compatibility. + _functions = { + filter = function (p, l) return filter (l, p) end, + foldl = function (f, e, l) return foldl (l, f, e) end, + foldr = function (f, e, l) return foldr (l, f, e) end, + indexKey = function (f, l) return index_key (l, f) end, + indexValue = function (f, l) return index_value (l, f) end, + map = function (f, l) return map (l, f) end, + mapWith = function (f, l) return map_with (l, f) end, + new = function (t) return List (t or {}) end, + project = function (f, l) return project (l, f) end, + shape = function (s, l) return shape (l, s) end, + zipWith = function (f, l) return zip_with (l, f) end, + }, } diff --git a/lib/std/set.lua b/lib/std/set.lua index 5d8034b..c3911d1 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -171,6 +171,23 @@ function equal (set1, set2) return subset (set1, set2) and subset (set2, set1) end + +--- @export +local _functions = { + delete = delete, + difference = difference, + elems = elems, + equal = equal, + insert = insert, + intersection = intersection, + member = member, + proper_subset = proper_subset, + subset = subset, + symmetric_difference = symmetric_difference, + union = union, +} + + --- Set prototype object. -- @table std.set -- @string[opt="Set"] _type type of Set, returned by @@ -269,20 +286,10 @@ Set = Container { end, - --- @export - _functions = { - delete = delete, - difference = difference, - elems = elems, - equal = equal, - insert = insert, - intersection = intersection, - member = member, - proper_subset = proper_subset, - subset = subset, - symmetric_difference = symmetric_difference, - union = union, - }, + _functions = base.merge (_functions, { + -- backwards compatibility. + new = function (t) return Set (t or {}) end, + }), } return Set diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 77ed1bb..a2ee225 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -53,4 +53,9 @@ return Object { concat = concat, tostring = tostring, }, + + -- backwards compatibility. + _functions = { + new = function () return StrBuf {} end, + }, } diff --git a/lib/std/tree.lua b/lib/std/tree.lua index f1c2d28..643fe49 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -13,7 +13,7 @@ local base = require "std.base" local Container = require "std.container" -local list = require "std.list" +local List = require "std.list" local func = require "std.functional" local prototype = (require "std.object").prototype @@ -177,6 +177,17 @@ local function merge (t, u) end +--- @export +local _functions = { + clone = clone, + ileaves = ileaves, + inodes = inodes, + leaves = leaves, + merge = merge, + nodes = nodes, +} + + --- Tree prototype object. -- @table std.tree -- @string[opt="Tree"] _type type of Tree, returned by @@ -197,7 +208,7 @@ Tree = Container { -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? __index = function (self, i) if type (i) == "table" and #i > 0 then - return list.foldl (i, func.op["[]"], self) + return List.foldl (func.op["[]"], self, i) else return rawget (self, i) end @@ -223,15 +234,10 @@ Tree = Container { end end, - --- @export - _functions = { - clone = clone, - ileaves = ileaves, - inodes = inodes, - leaves = leaves, - merge = merge, - nodes = nodes, - }, + _functions = base.merge (_functions, { + -- backwards compatibility. + new = function (t) return Tree (t or {}) end, + }), } return Tree From 6165213d5acfe289bcc003c004935c04fe2652ae Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 19 Jan 2014 19:43:47 +1300 Subject: [PATCH 067/703] refactor: rearrange methods and functions for backwards compatibility. * lib/std/list.lua: Make use of `_function` table to reinstate backwards compatible module functions. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 605 ++++++++++++++++++++++++++++++----------------- 1 file changed, 389 insertions(+), 216 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 96b2bf8..1343277 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -13,7 +13,9 @@ => 2 => 1 - ...they can also be called as module functions with an explicit argument: + ... some can also be called as module functions with an explicit list + argument in the first or last parameter, check the documentation for + details: local List = require "std.list" local l = List {1, 2, 3} @@ -30,238 +32,236 @@ local func = require "std.functional" local Object = require "std.object" +local List -- forward declaration + +------ +-- An Object derived List. +-- @table List + +--- Append an item to a list. +-- @tparam List l a list +-- @param x item +-- @treturn List new list containing `{l[1], ..., l[#l], x}` +local function append (l, x) + return List (base.append (l, x)) +end + + --- Compare two lists element-by-element, from left-to-right. -- -- if a_list:compare (another_list) == 0 then print "same" end +-- @static -- @function compare --- @tparam table l another list --- @return -1 if `self` is less than `l`, 0 if they are the same, and 1 --- if `self` is greater than `l` +-- @tparam List l a list +-- @tparam table m another list +-- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 +-- if `l` is greater than `m` local compare = base.compare ---- An iterator over the elements of a list. --- @function elems --- @treturn function iterator function which returns successive elements of `self` --- @treturn table *list* --- @return `true` -local elems = base.elems - - -local List -- list prototype object forward declaration - - ---- Append an item to a list. --- @param x item --- @treturn std.list new list containing `{self[1], ..., self[#self], x}` -local function append (self, x) - return List (base.append (self, x)) +--- Concatenate arguments into a list. +-- @tparam List l a list +-- @param ... tuple of lists +-- @treturn List new list containing +-- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` +local function concat (l, ...) + return List (base.concat (l, ...)) end ---- Concatenate arguments into a list. --- @param ... tuple of lists --- @treturn std.list new list containing --- `{self[1], ..., self[#self], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` -local function concat (self, ...) - return List (base.concat (self, ...)) +--- Prepend an item to a list. +-- @tparam List l a list +-- @param x item +-- @treturn List new list containing `{x, unpack (l)}` +local function cons (l, x) + return List {x, unpack (l)} end ---- An iterator over the elements of a list, in reverse. --- @treturn function iterator function which returns precessive elements of the `self` --- @treturn std.list `self` +--- An iterator over the elements of a list. +-- @static +-- @function elems +-- @tparam List l a list +-- @treturn function iterator function which returns successive elements +-- of `l` +-- @treturn List `l` -- @return `true` -local function relems (self) - local n = #self + 1 - return function (self) - n = n - 1 - if n > 0 then - return self[n] - end - end, - self, true -end +local elems = base.elems ---- Map a function over a list. --- @tparam function f map function --- @treturn std.list new list containing `{f (self[1]), ..., f (self[#self])}` -local function map (self, f) - return List (func.map (f, elems, self)) +--- Turn a list of pairs into a table. +-- @todo Find a better name. +-- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` +-- @treturn table a new list containing table `{i1=v1, ..., in=vn}` +-- @see enpair +local function depair (ls) + local t = {} + for v in elems (ls) do + t[v[1]] = v[2] + end + return t end ---- Map a function over a list of lists. --- @tparam table ls a list of lists --- @tparam function f map function --- @treturn std.list new list `{f (unpack (ls[1]))), ..., f (unpack (ls[#ls]))}` -local function map_with (ls, f) - return List (func.map (func.compose (f, unpack), elems, ls)) +--- Turn a table into a list of pairs. +-- @todo Find a better name. +-- @tparam table t a table `{i1=v1, ..., in=vn}` +-- @treturn List a new list containing `{{i1, v1}, ..., {in, vn}}` +-- @see depair +local function enpair (t) + local ls = List {} + for i, v in pairs (t) do + table.insert (ls, List {i, v}) + end + return ls end --- Filter a list according to a predicate. --- @tparam function p predicate function, of one argument returning a boolean --- @treturn std.list new list containing elements `e` of `self` for which `p (e)` is true -local function filter (self, p) - return List (func.filter (p, elems, self)) +-- @func p predicate function, of one argument returning a boolean +-- @tparam List l a list +-- @treturn List new list containing elements `e` of `l` for which +-- `p (e)` is true +-- @see std.list:filter +local function filter (p, l) + return List (func.filter (p, elems, l)) end ---- Return a sub-range of a list. --- (The equivalent of `string.sub` on strings; negative list indices --- count from the end of the list.) --- @tparam number from start of range (default: 1) --- @tparam number to end of range (default: `#self`) --- @treturn std.list new list containing `{self[from], ..., self[to]}` -local function sub (self, from, to) +--- Flatten a list. +-- @tparam List l a list +-- @treturn List flattened list +local function flatten (l) local r = List {} - local len = #self - from = from or 1 - to = to or len - if from < 0 then - from = from + len + 1 - end - if to < 0 then - to = to + len + 1 - end - for i = from, to do - table.insert (r, self[i]) + for v in base.ileaves (l) do + table.insert (r, v) end return r end ---- Return a list with its first element removed. --- @treturn std.list new list containing `{self[2], ..., self[#self]}` -local function tail (self) - return sub (self, 2) -end - - --- Fold a binary function through a list left associatively. --- @tparam function f binary function --- @param e element to place in left-most position +-- @func fn binary function +-- @param e element to place in left-most position +-- @tparam List l a list -- @return result -local function foldl (self, f, e) - return func.fold (f, e, elems, self) +-- @see std.list:foldl +local function foldl (fn, e, l) + return func.fold (fn, e, elems, l) end ---- Fold a binary function through a list right associatively. --- @tparam function f binary function --- @param e element to place in right-most position --- @return result -local function foldr (self, f, e) - return List (func.fold (function (x, y) return f (y, x) end, - e, relems, self)) +--- An iterator over the elements of a list, in reverse. +-- @tparam List l a list +-- @treturn function iterator function which returns precessive elements +-- of the `l` +-- @treturn List `l` +-- @return `true` +local function relems (l) + local n = #l + 1 + return function (l) + n = n - 1 + if n > 0 then + return l[n] + end + end, + l, true end ---- Prepend an item to a list. --- @param x item --- @treturn std.list new list containing `{x, unpack (self)}` -local function cons (self, x) - return List {x, unpack (self)} +--- Fold a binary function through a list right associatively. +-- @func fn binary function +-- @param e element to place in right-most position +-- @tparam List l a list +-- @return result +-- @see std.list:foldr +local function foldr (fn, e, l) + return List (func.fold (function (x, y) return fn (y, x) end, + e, relems, l)) end ---- Repeat a list. --- @tparam number n number of times to repeat --- @treturn std.list `n` copies of `self` appended together -local function rep (self, n) +--- Make an index of a list of tables on a given field +-- @param f field +-- @tparam List l list of tables `{t1, ..., tn}` +-- @treturn List index `{t1[f]=1, ..., tn[f]=n}` +local function index_key (f, l) local r = List {} - for i = 1, n do - r = concat (r, self) + for i, v in ipairs (l) do + local k = v[f] + if k then + r[k] = i + end end return r end ---- Reverse a list. --- @treturn std.list new list containing `{self[#self], ..., self[1]}` -local function reverse (self) +--- Copy a list of tables, indexed on a given field +-- @param f field whose value should be used as index +-- @tparam List l list of tables `{i1=t1, ..., in=tn}` +-- @treturn List index `{t1[f]=t1, ..., tn[f]=tn}` +local function index_value (f, l) local r = List {} - for i = #self, 1, -1 do - table.insert (r, self[i]) + for i, v in ipairs (l) do + local k = v[f] + if k then + r[k] = v + end end return r end ---- Transpose a list of lists. --- This function in Lua is equivalent to zip and unzip in more strongly --- typed languages. --- @tparam table ls --- `{{ls<1,1>, ..., ls<1,c>}, ..., {ls<r,1>, ..., ls<r,c>}}` --- @treturn std.list new list containing --- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` -local function transpose (ls) - local rs, len = List {}, #ls - for i = 1, math.max (unpack (map (ls, function (l) return #l end))) do - rs[i] = List {} - for j = 1, len do - rs[i][j] = ls[j][i] - end - end - return rs +--- Map a function over a list. +-- @func fn map function +-- @tparam List l a list +-- @treturn List new list containing `{fn (l[1]), ..., fn (l[#l])}` +-- @see std.list:map +local function map (fn, l) + return List (func.map (fn, elems, l)) end ---- Zip a list of lists together with a function. --- @tparam table ls list of lists --- @tparam function f function --- @treturn std.list a new list containing --- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` --- where `N = max {map (function (l) return #l end, ls)}` -local function zip_with (ls, f) - return map_with (transpose (ls), f) +--- Map a function over a list of lists. +-- @func fn map function +-- @tparam List ls a list of lists +-- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` +local function map_with (fn, ls) + return List (func.map (func.compose (fn, unpack), elems, ls)) end --- Project a list of fields from a list of tables. --- @param f field to project --- @treturn std.list list of `f` fields -local function project (self, f) - return map (self, function (t) return t[f] end) +-- @param f field to project +-- @tparam List l a list +-- @treturn List list of `f` fields +-- @see std.list:project +local function project (f, l) + return map (function (t) return t[f] end, l) end ---- Turn a table into a list of pairs. --- @todo Find a better name. --- @tparam table t a table `{i1=v1, ..., in=vn}` --- @treturn std.list a new list containing `{{i1, v1}, ..., {in, vn}}` --- @see depair -local function enpair (t) - local ls = List {} - for i, v in pairs (t) do - table.insert (ls, List {i, v}) - end - return ls -end - - ---- Turn a list of pairs into a table. --- @todo Find a better name. --- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` --- @treturn table a new list containing table `{i1=v1, ..., in=vn}` --- @see enpair -local function depair (ls) - local t = {} - for v in elems (ls) do - t[v[1]] = v[2] +--- Repeat a list. +-- @tparam List l a list +-- @int n number of times to repeat +-- @treturn List `n` copies of `l` appended together +local function rep (l, n) + local r = List {} + for i = 1, n do + r = concat (r, l) end - return t + return r end ---- Flatten a list. --- @treturn std.list flattened list -local function flatten (self) +--- Reverse a list. +-- @tparam List l a list +-- @treturn List new list containing `{l[#l], ..., l[1]}` +local function reverse (l) local r = List {} - for v in base.ileaves (self) do - table.insert (r, v) + for i = #l, 1, -1 do + table.insert (r, l[i]) end return r end @@ -284,9 +284,11 @@ end -- @todo Use ileaves instead of flatten (needs a while instead of a -- for in fill function) -- @tparam table s `{d1, ..., dn}` +-- @tparam List l a list -- @return reshaped list -local function shape (self, s) - self = flatten (self) +-- @see std.list:shape +local function shape (s, l) + l = flatten (l) -- Check the shape and calculate the size of the zero, if any local size = 1 local zero @@ -302,11 +304,11 @@ local function shape (self, s) end end if zero then - s[zero] = math.ceil (#self / size) + s[zero] = math.ceil (#l / size) end local function fill (i, d) if d > #s then - return self[i], i + 1 + return l[i], i + 1 else local r = List {} for j = 1, s[d] do @@ -321,40 +323,71 @@ local function shape (self, s) end ---- Make an index of a list of tables on a given field --- @tparam table l list of tables `{t1, ..., tn}` --- @param f field --- @treturn std.list index `{t1[f]=1, ..., tn[f]=n}` -local function index_key (l, f) +--- Return a sub-range of a list. +-- (The equivalent of `string.sub` on strings; negative list indices +-- count from the end of the list.) +-- @tparam List l a list +-- @int from start of range (default: 1) +-- @int to end of range (default: `#l`) +-- @treturn List new list containing `{l[from], ..., l[to]}` +local function sub (l, from, to) local r = List {} - for i, v in ipairs (l) do - local k = v[f] - if k then - r[k] = i - end + local len = #l + from = from or 1 + to = to or len + if from < 0 then + from = from + len + 1 + end + if to < 0 then + to = to + len + 1 + end + for i = from, to do + table.insert (r, l[i]) end return r end ---- Copy a list of tables, indexed on a given field --- @tparam table l list of tables `{i1=t1, ..., in=tn}` --- @param f field whose value should be used as index --- @treturn std.list index `{t1[f]=t1, ..., tn[f]=tn}` -local function index_value (l, f) - local r = List {} - for i, v in ipairs (l) do - local k = v[f] - if k then - r[k] = v +--- Return a list with its first element removed. +-- @tparam List l a list +-- @treturn List new list containing `{l[2], ..., l[#l]}` +local function tail (l) + return sub (l, 2) +end + + +--- Transpose a list of lists. +-- This function in Lua is equivalent to zip and unzip in more strongly +-- typed languages. +-- @tparam table ls +-- `{{ls<1,1>, ..., ls<1,c>}, ..., {ls<r,1>, ..., ls<r,c>}}` +-- @treturn List new list containing +-- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` +local function transpose (ls) + local rs, len = List {}, #ls + for i = 1, math.max (unpack (map (ls, function (l) return #l end))) do + rs[i] = List {} + for j = 1, len do + rs[i][j] = ls[j][i] end end - return r + return rs +end + + +--- Zip a list of lists together with a function. +-- @tparam table ls list of lists +-- @tparam function f function +-- @treturn List a new list containing +-- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` +-- where `N = max {map (function (l) return #l end, ls)}` +local function zip_with (ls, f) + return map_with (transpose (ls), f) end --- @export -local metamethods = { +local _functions = { append = append, compare = compare, concat = concat, @@ -390,7 +423,7 @@ List = Object { -- Concatenate lists. -- new = list .. table -- @function __concat - -- @tparam std.list list a list + -- @tparam List list a list -- @tparam table table another list, hash part is ignored -- @see concat __concat = concat, @@ -399,7 +432,7 @@ List = Object { -- Append element to list. -- list = list + element -- @function __add - -- @tparam std.list list a list + -- @tparam List list a list -- @param element element to append -- @see append __add = append, @@ -407,45 +440,185 @@ List = Object { ------ -- List order operator. -- max = list1 > list2 and list1 or list2 - -- @tparam std.list list1 a list - -- @tparam std.list list2 another list + -- @tparam List list1 a list + -- @tparam List list2 another list -- @see std.list:compare __lt = function (list1, list2) return compare (list1, list2) < 0 end, ------ -- List equality or order operator. -- min = list1 <= list2 and list1 or list2 - -- @tparam std.list list1 a list - -- @tparam std.list list2 another list + -- @tparam List list1 a list + -- @tparam List list2 another list -- @see std.list:compare __le = function (list1, list2) return compare (list1, list2) <= 0 end, - __index = base.merge (metamethods, { - -- camelCase compatibility. - indexKey = index_key, - indexValue = index_value, - mapWith = map_with, - zipWith = zip_with, - }), - - -- backwards compatibility. - _functions = { - filter = function (p, l) return filter (l, p) end, - foldl = function (f, e, l) return foldl (l, f, e) end, - foldr = function (f, e, l) return foldr (l, f, e) end, - indexKey = function (f, l) return index_key (l, f) end, - indexValue = function (f, l) return index_value (l, f) end, - map = function (f, l) return map (l, f) end, - mapWith = function (f, l) return map_with (l, f) end, - new = function (t) return List (t or {}) end, - project = function (f, l) return project (l, f) end, - shape = function (s, l) return shape (l, s) end, - zipWith = function (f, l) return zip_with (l, f) end, + __index = { + ------ + -- Append an item to a list. + -- @function append + -- @param x item + -- @treturn List new list containing `{self[1], ..., self[#self], x}` + append = append, + + ------ + -- Compare two lists element-by-element, from left-to-right. + -- + -- if a_list:compare (another_list) == 0 then print "same" end + -- @function compare + -- @tparam table l a list + -- @return -1 if `self` is less than `l`, 0 if they are the same, and 1 + -- if `self` is greater than `l` + compare = compare, + + ------ + -- Concatenate arguments into a list. + -- @function concat + -- @param ... tuple of lists + -- @treturn List new list containing + -- `{self[1], ..., self[#self], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` + concat = concat, + + ------ + -- Prepend an item to a list. + -- @function cons + -- @param x item + -- @treturn List new list containing `{x, unpack (self)}` + cons = cons, + + ------ + -- An iterator over the elements of a list. + -- @function elems + -- @treturn function iterator function which returns successive + -- elements of `self` + -- @treturn List `self` + -- @return `true` + elems = elems, + + ------ + -- Filter a list according to a predicate. + -- @function filter + -- @func p predicate function, of one argument returning a boolean + -- @treturn List new list containing elements `e` of `self` for which + -- `p (e)` is true + -- @see std.list.filter + filter = function (self, p) return filter (p, self) end, + + ------ + -- Flatten a list. + -- @function flatten + -- @treturn List flattened list + flatten = flatten, + + ------ + -- Fold a binary function through a list left associatively. + -- @function foldl + -- @func fn binary function + -- @param e element to place in left-most position + -- @return result + -- @see std.list.foldl + foldl = function (self, fn, e) return foldl (fn, e, self) end, + + ------ + -- Fold a binary function through a list right associatively. + -- @function foldr + -- @func f binary function + -- @param e element to place in right-most position + -- @return result + -- @see std.list.foldr + foldr = function (self, fn, e) return foldr (fn, e, self) end, + + ------ + -- Map a function over a list. + -- @function map + -- @func fn map function + -- @treturn List new list containing + -- `{fn (self[1]), ..., fn (self[#self])}` + -- @see std.list.map + map = function (self, fn) return map (fn, self) end, + + ------ + -- Project a list of fields from a list of tables. + -- @function project + -- @param f field to project + -- @treturn List list of `f` fields + -- @see std.list.project + project = function (self, f) return project (f, self) end, + + ------ + -- An iterator over the elements of a list, in reverse. + -- @function relems + -- @treturn function iterator function which returns precessive elements + -- of the `self` + -- @treturn List `self` + -- @return `true` + relems = relems, + + ------ + -- Repeat a list. + -- @function rep + -- @int n number of times to repeat + -- @treturn List `n` copies of `self` appended together + rep = rep, + + ------ + -- Reverse a list. + -- @function reverse + -- @treturn List new list containing `{self[#self], ..., self[1]}` + reverse = reverse, + + ----- + -- Shape a list according to a list of dimensions. + -- @function shape + -- @tparam table s `{d1, ..., dn}` + -- @return reshaped list + -- @see std.list.shape + shape = function (self, s) return shape (s, self) end, + + ------ + -- Return a sub-range of a list. + -- (The equivalent of `string.sub` on strings; negative list indices + -- count from the end of the list.) + -- @function sub + -- @int from start of range (default: 1) + -- @int to end of range (default: `#self`) + -- @treturn List new list containing `{self[from], ..., self[to]}` + sub = sub, + + ------ + -- Return a list with its first element removed. + -- @function tail + -- @treturn List new list containing `{self[2], ..., self[#self]}` + tail = tail, + + -- For backwards compatibility with pre-Object era lists, but + -- undocumented so that new code doesn't get tangled up in it. + depair = depair, + index_key = function (self, f) return index_key (f, self) end, + index_value = function (self, f) return index_value (f, self) end, + indexKey = function (self, f) return indexKey (f, self) end, + indexValue = function (self, f) return indexValue (f, self) end, + map_with = function (self, f) return map_with (f, self) end, + transpose = transpose, + zip_with = function (self, f) return zip_with (f, self) end, }, + + _functions = (base.merge (_functions, { + -- backwards compatibility + new = function (t) return List (t or {}) end, + slice = sub, + + -- camelCase compatibility + indexKey = index_key, + indexValue = index_value, + mapWith = map_with, + zipWith = zip_with, + })), } -- Function forms of operators func.op[".."] = concat + return List From 61cfbf3b4e9f5f1121af4b5c499f91bd5a211580 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 19 Jan 2014 20:09:18 +1300 Subject: [PATCH 068/703] doc: keep config.ld out of doc/ for LuaRocks docdir install. * doc/config.ld.in: Move from here... * build-aux/config.ld.in: ...to here. * configure.a (AC_CONFIG_FILES): Adjust. * local.mk ($(dist_lua_DATA)): Adjust. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 2 ++ {doc => build-aux}/config.ld.in | 0 configure.ac | 2 +- local.mk | 5 +++-- 4 files changed, 6 insertions(+), 3 deletions(-) rename {doc => build-aux}/config.ld.in (100%) diff --git a/NEWS b/NEWS index a690003..0d55888 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,8 @@ Stdlib NEWS - User visible changes - `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 diff --git a/doc/config.ld.in b/build-aux/config.ld.in similarity index 100% rename from doc/config.ld.in rename to build-aux/config.ld.in diff --git a/configure.ac b/configure.ac index 24c91cd..c0b6bf8 100644 --- a/configure.ac +++ b/configure.ac @@ -37,5 +37,5 @@ AC_PROG_SED dnl Generate output files SS_CONFIG_TRAVIS([ldoc specl]) -AC_CONFIG_FILES([Makefile doc/config.ld specs/spec_helper.lua]) +AC_CONFIG_FILES([Makefile build-aux/config.ld specs/spec_helper.lua]) AC_OUTPUT diff --git a/local.mk b/local.mk index fc2170b..3e62865 100644 --- a/local.mk +++ b/local.mk @@ -112,7 +112,7 @@ mkrockspecs_args = --module-dir $(srcdir)/lib --repository lua-stdlib ## ------------- ## EXTRA_DIST += \ - doc/config.ld \ + build-aux/config.ld.in \ lib/std.lua.in \ $(NOTHING_ELSE) @@ -151,4 +151,5 @@ dist_modules_DATA += \ ldoc_DEPS = $(dist_lua_DATA) $(dist_luastd_DATA) $(dist_doc_DATA) $(dist_classes_DATA) $(dist_modules_DATA): $(ldoc_DEPS) - cd $(srcdir) && $(LDOC) -c doc/config.ld . + test -d "$(srcdir)/doc" || mkdir "$(srcdir)/doc" + $(LDOC) -c build-aux/config.ld -d $(abs_srcdir)/doc . From de2e171dc21aa64e5025727f7366ec9694dea29d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 19 Jan 2014 20:11:43 +1300 Subject: [PATCH 069/703] Release version 37 * NEWS: Record release date. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 0d55888..639ce82 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ Stdlib NEWS - User visible changes -* Noteworthy changes in release ?.? (????-??-??) [?] +* Noteworthy changes in release 37 (2014-01-19) [stable] ** New features: From 26adb0b83697961b5654ae51a371ece7278504fc Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 19 Jan 2014 20:11:59 +1300 Subject: [PATCH 070/703] maint: post-release administrivia. * configure: Bump revision to 38. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS | 3 +++ configure.ac | 2 +- local.mk | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.prev-version b/.prev-version index 7facc89..81b5c5d 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -36 +37 diff --git a/NEWS b/NEWS index 639ce82..ef37b5f 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Stdlib NEWS - User visible changes +* Noteworthy changes in release ?.? (????-??-??) [?] + + * Noteworthy changes in release 37 (2014-01-19) [stable] ** New features: diff --git a/configure.ac b/configure.ac index c0b6bf8..6e99177 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [37], [http://github.com/rrthomas/lua-stdlib/issues]) +AC_INIT([stdlib], [38], [http://github.com/rrthomas/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/local.mk b/local.mk index 3e62865..4fe5628 100644 --- a/local.mk +++ b/local.mk @@ -29,7 +29,7 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" ## Bootstrap. ## ## ---------- ## -old_NEWS_hash = 7a7647cb5b2d5d886a18070e1f4ac5fd +old_NEWS_hash = 1a28799a850e021e45c3e98064a746d7 update_copyright_env = \ UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ From 76262f12a2fba5599d6b75f5d6d3940e9f40af65 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 20 Jan 2014 13:22:52 +0000 Subject: [PATCH 071/703] optparse.lua: move "default" options to bottom of help A more standard position. --- lib/std/optparse.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 7bf0ee5..dec0dd2 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -25,8 +25,6 @@ Options: - -h, --help display this help, then exit - --version display version information, then exit -b a short option with no long option --long a long option with no short option --another-long a long option with internal hypen @@ -35,6 +33,8 @@ -n, --dryrun, --dry-run several spellings of the same option -u, --name=USER require an argument -o, --output=[FILE] accept an optional argument + --version display version information, then exit + -h, --help display this help, then exit -- end of options Footer text. Several lines or paragraphs are permitted. From 8eb505a0631f7f4214f465c85ba57e94ab2dc55f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 21 Jan 2014 14:42:24 +1300 Subject: [PATCH 072/703] doc: vastly improve optparse LDocs. * lib/std/optparse.lua: Vastly improve optparse LDocs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 5 ++ lib/std/optparse.lua | 188 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 171 insertions(+), 22 deletions(-) diff --git a/NEWS b/NEWS index ef37b5f..b5e4172 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,11 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] +** Bug fixes: + + - Much improved documentation for `optparse`, so you should be able + to use it without reading the source code now! + * Noteworthy changes in release 37 (2014-01-19) [stable] diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index dec0dd2..4a81511 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -1,13 +1,9 @@ ---[[-- +--[=[-- Parse and process command line options. local OptionParser = require "std.optparse" - local parser = OptionParser (spec) - _G.arg, opts = parser:parse (_G.arg) - - The string `spec` passed to `OptionParser` must be a specially formatted - help text, of the form: + local parser = OptionParser [[ any text VERSION Additional lines of text to show when the --version option is passed. @@ -28,35 +24,50 @@ -b a short option with no long option --long a long option with no short option --another-long a long option with internal hypen - --true a Lua keyword as an option name -v, --verbose a combined short and long option -n, --dryrun, --dry-run several spellings of the same option -u, --name=USER require an argument -o, --output=[FILE] accept an optional argument --version display version information, then exit - -h, --help display this help, then exit - -- end of options + --help display this help, then exit Footer text. Several lines or paragraphs are permitted. Please report bugs at bug-list@yourhost.com + ]] + + _G.arg, _G.opts = parser:parse (_G.arg) Most often, everything else is handled automatically. After calling `parser:parse` as shown above, `_G.arg` will contain unparsed arguments, - usually filenames or similar, and `opts` will be a table of parsed + usually filenames or similar, and `_G.opts` will be a table of parsed option values. The keys to the table are the long-options with leading hyphens stripped, and non-word characters turned to `_`. For example - if `--another-long` had been found in `_G.arg` then `opts` would + if `--another-long` had been found in `_G.arg` then `_G.opts` would have a key named `another_long`. If there is no long option name, then - the short option is used, e.g. `opts.b` will be set. The values saved - in those keys are controlled by the option handler, usually just `true` - or the option argument string as appropriate. + the short option is used, e.g. `_G.opts.b` will be set. The values + saved in those keys are controlled by the option handler, usually just + `true` or the option argument string as appropriate. On those occasions where more complex processing is required, handlers - can be replaced or added using parser:@{on}. + can be replaced or added using parser:@{on}. A good option to always + add, is to make `--` signal the end of processed options, so that any + options following `--` on the command line, even if they begin with a + hyphen and look like options otherwise, are not processed but instead + left in the modified `_G.arg` returned by `parser:parse`: + + parser:on ('--', parser.finished) + + See the documentation for @{std.optparse:on} for more details of how to + use this powerful method. + + When writing your own handlers for @{std.optparse:on}, you only need + to deal with normalised arguments, because combined short arguments + (`-xyz`), equals separators to long options (`--long=ARG`) are fully + expanded before any handler is called. @classmod std.optparse -]] +]=] local OptionParser -- forward declaration @@ -64,7 +75,22 @@ local OptionParser -- forward declaration ------ -- Customized parser for your options. +-- +-- This table is returned by @{OptionParser}, and most importantly has +-- the @{parse} method you call to fill the `opts` table according to +-- what command-line options were passed to your program. -- @table parser +-- @string program the first word following `Usage:` in @{OptionParser} +-- spec string +-- @string version the last white-space delimited word on the first line +-- of text in the spec string +-- @string versiontext everything preceding `Usage:` in the spec string, +-- and which will be displayed by the @{version} @{on_handler} +-- @string helptext everything including and following `Usage:` in the +-- spec string and which will be displayed by the @{help} +-- @{on_handler} +-- @func parse see @{parse} +-- @func on see @{on} --[[ ----------------- ]]-- @@ -78,6 +104,7 @@ local optional, required --- Normalise an argument list. -- Separate short options, remove `=` separators from -- `--long-option=optarg` etc. +-- @local -- @function normalise -- @tparam table arglist list of arguments to normalise -- @treturn table normalised argument list @@ -132,6 +159,7 @@ end --- Store `value` with `opt`. +-- @local -- @function set -- @string opt option name -- @param value option argument value @@ -157,6 +185,16 @@ end --- Option at `arglist[i]` can take an argument. -- Argument is accepted only if there is a following entry that does not -- begin with a '-'. +-- +-- This is the handler automatically assigned to options that have +-- `--opt=[ARG]` style specifications in the @{OptionParser} spec +-- argument. You can also pass it as the `handler` argument to @{on} for +-- options you want to add manually without putting them in the +-- @{OptionParser} spec. +-- +-- Like @{required}, this handler will store multiple occurrences of a +-- command-line option. +-- @static -- @tparam table arglist list of arguments -- @int i index of last processed element of `arglist` -- @param[opt=true] value either a function to process the option @@ -179,6 +217,28 @@ end --- Option at `arglist[i}` requires an argument. +-- +-- This is the handler automatically assigned to options that have +-- `--opt=ARG` style specifications in the @{OptionParser} spec argument. +-- You can also pass it as the `handler` argument to @{on} for options +-- you want to add manually without putting them in the @{OptionParser} +-- spec. +-- +-- Normally the value stored in the `opt` table by this handler will be +-- the string given as the argument to that option on the command line. +-- However, if the option is given on the command-line multiple times, +-- `opt["name"]` will end up with all those arguments stored in the +-- array part of a table: +-- +-- $ cat ./prog +-- ... +-- parser:on ({"-e", "-exec"}, required) +-- _G.arg, _G.opt = parser:parse (_G.arg) +-- print std.string.tostring (_G.opt.exec) +-- ... +-- $ ./prog -e '(foo bar)' -e '(foo baz)' -- qux +-- {1=(foo bar),2=(foo baz)} +-- @static -- @tparam table arglist list of arguments -- @int i index of last processed element of `arglist` -- @param[opt] value either a function to process the option argument, @@ -203,7 +263,16 @@ end --- Finish option processing --- Usually indicated by `--` at `arglist[i]`. +-- +-- This is the handler automatically assigned to the option written as +-- `--` in the @{OptionParser} spec argument. You can also pass it as +-- the `handler` argument to @{on} if you want to manually add an end +-- of options marker without writing it in the @{OptionParser} spec. +-- +-- This handler tells the parser to stop processing arguments, so that +-- anything after it will be an argument even if it otherwise looks +-- like an option. +-- @static -- @tparam table arglist list of arguments -- @int i index of last processed element of `arglist` -- @treturn int index of next element of `arglist` to process @@ -216,24 +285,42 @@ end --- Option at `arglist[i]` is a boolean switch. +-- +-- This is the handler automatically assigned to options that have +-- `--long-opt` or `-x` style specifications in the @{OptionParser} spec +-- argument. You can also pass it as the `handler` argument to @{on} for +-- options you want to add manually without putting them in the +-- @{OptionParser} spec. +-- +-- Beware that, _unlike_ @{required}, this handler will store multiple +-- occurrences of a command-line option as a table **only** when given a +-- `value` function. Automatically assigned handlers do not do this, so +-- the option will simply be `true` if the option was given one or more +-- times on the command-line. +-- @static -- @tparam table arglist list of arguments -- @int i index of last processed element of `arglist` -- @param[opt] value either a function to process the option argument, -- or a value to store when this flag is encountered -- @treturn int index of next element of `arglist` to process local function flag (self, arglist, i, value) + local opt = arglist[i] if type (value) == "function" then - value = value (self, opt, true) + set (self, opt, value (self, opt, true)) elseif value == nil then - value = true + local key = self[opt].key + self.opts[key] = true end - set (self, arglist[i], value) return i + 1 end --- Option should display help text, then exit. +-- +-- This is the handler automatically assigned tooptions that have +-- `--help` in the specification, e.g. `-h, -?, --help`. +-- @static -- @function help local function help (self) print (self.helptext) @@ -242,6 +329,10 @@ end --- Option should display version text, then exit. +-- +-- This is the handler automatically assigned tooptions that have +-- `--version` in the specification, e.g. `-V, --version`. +-- @static -- @function version local function version (self) print (self.versiontext) @@ -275,6 +366,11 @@ local boolvals = { --- Return a Lua boolean equivalent of various `optarg` strings. -- Report an option parse error if `optarg` is not recognised. +-- +-- Pass this as the `value` function to @{on} when you want various +-- *truthy* or *falsey* option arguments to be coerced to a Lua `true` +-- or `false` respectively in the options table. +-- @static -- @string opt option name -- @string[opt="1"] optarg option argument, must be a key in @{boolvals} -- @treturn bool `true` or `false` @@ -290,7 +386,11 @@ end --- Report an option parse error unless `optarg` names an -- existing file. +-- +-- Pass this as the `value` function to @{on} when you want to accept +-- only option arguments that name an existing file. -- @fixme this only checks whether the file has read permissions +-- @static -- @string opt option name -- @string optarg option argument, must be an existing file -- @treturn `optarg` @@ -311,6 +411,10 @@ end --- Report an option parse error, then exit with status 2. +-- +-- Use this in your custom option handlers for consistency with the +-- error output from built-in `optparse` error messages. +-- @static -- @string msg error message local function opterr (self, msg) local prog = self.program @@ -332,6 +436,18 @@ end --- Add an option handler. +-- +-- When the automatically assigned option handlers don't do everything +-- you require, or when you don't want to put an option into the +-- @{OptionParser} `spec` argument, use this function to specify custom +-- behaviour. If you write the option into the `spec` argument anyway, +-- calling this function will replace the automatically assigned handler +-- with your own. +-- +-- parser:on ("--", parser.finished) +-- parser:on ("-V", parser.version) +-- parser:on ("--config-file", parser.required, parser.file) +-- parser:on ("--enable-nls", parser.optional, parser.boolean) -- @function on -- @tparam[string|table] opts name of the option, or list of option names -- @tparam on_handler handler function to call when any of `opts` is @@ -376,7 +492,34 @@ end ------ -- Parsed options table, with a key for each encountered option, each --- with value set by that option's @{on_handler}. +-- with value set by that option's @{on_handler}. Where an option +-- has one or more long-options specified, the key will be the first +-- one of those with leading hyphens stripped and non-alphanumeric +-- characters replaced with underscores. For options that can only be +-- specified by a short option, the key will be the letter of the first +-- of the specified short options: +-- +-- {"-e", "--eval-file"} => opts.eval_file +-- {"-n", "--dryrun", "--dry-run"} => opts.dryrun +-- {"-t", "-T"} => opts.t +-- +-- Generally there will be one key for each previously specified +-- option (either automatically assigned by @{OptionParser} or +-- added manually with @{on}) containing the value(s) assigned by the +-- associated @{on_handler}. For automatically assigned handlers, +-- that means `true` for straight-forward flags and +-- optional-argument options for which no argument was given; or else +-- the string value of the argument passed with an option given only +-- once; or a table of string values of the same for arguments given +-- multiple times. +-- +-- ./prog -x -n -x => opts = { x = true, dryrun = true } +-- ./prog -e '(foo bar)' -e '(foo baz)' +-- => opts = {eval_file = {"(foo bar)", "(foo baz)"} } +-- +-- If you write your own handlers, or otherwise specify custom +-- handling of options with @{on}, then whatever value those handlers +-- return will be assigned to the respective keys in `opts`. -- @table opts @@ -446,7 +589,8 @@ end --- Instantiate a new parser. -- Read the documented options from `spec` and return a new parser that -- can be passed to @{parse} for parsing those options from an argument --- list. +-- list. Options are recognised as lines that begin with at least two +-- spaces, followed by a hyphen. -- @static -- @string spec option parsing specification -- @treturn parser a parser for options described by `spec` From 72f4a969c836544200a20ef4fd3d269ca2a5c395 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 22 Jan 2014 11:54:47 +1300 Subject: [PATCH 073/703] travis: remove unrelease ldoc workarounds. * .travis.yml: Regenerate, to delete local patches for LDoc. Signed-off-by: Gary V. Vaughan --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index cead315..4a5973e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ env: - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - LUAROCKS_BASE=luarocks-2.1.1 - LUAROCKS="$LUA $HOME/bin/luarocks" - - LDOC_ROCKSPEC=http://raw.github.com/gvvaughan/LDoc/next/ldoc-next-1.rockspec matrix: - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 LUA_SUFFIX=5.1 - LUA=lua5.2 LUA_INCDIR=/usr/include/lua5.2 LUA_SUFFIX=5.2 @@ -52,9 +51,7 @@ script: - export PATH=`pwd`/luarocks/bin:$PATH # Install extra rocks into $LUAROCKS_CONFIG rocks tree. - - $LUAROCKS install lyaml; $LUAROCKS install specl; - # LDoc 1.4.0 is not in luarocks yet, and LDoc master has no rockspec :( - - $LUAROCKS install $LDOC_ROCKSPEC + - $LUAROCKS install lyaml; $LUAROCKS install ldoc; $LUAROCKS install specl; # Make git rockspec for this stdlib - make rockspecs LUAROCKS="$LUAROCKS" V=1 From ec7ca283bb3770ef1077781732dce6769e01dfb9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 22 Jan 2014 12:13:15 +1300 Subject: [PATCH 074/703] configury: update rockspecs in buildreq. * bootstrap.conf (buildreq): LDoc is now available in the required version from the official luarocks repo. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap.conf b/bootstrap.conf index d9b0e91..90eff27 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -33,8 +33,8 @@ # Build prerequisites buildreq=' git 1.5.5 http://git-scm.com - ldoc =next-1 http://raw.github.com/gvvaughan/LDoc/next/ldoc-next-1.rockspec - specl 8 http://luarocks.org/repositories/rocks/specl-9-1.rockspec + ldoc 1.4.0 http://luarocks.org/repositories/rocks/ldoc-1.4.2-1.rockspec + specl 8 http://luarocks.org/repositories/rocks/specl-10-1.rockspec ' # List of slingshot files to link into stdlib tree before autotooling. From 5c298a5505b39b9105440e0de1701ee8a485e098 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 23 Jan 2014 21:52:17 +1300 Subject: [PATCH 075/703] container: distinguish between the two `functions` tables. One is the core of Container with metatable as its metatable; the other is metatable._functions, and is just a list of unpropagated method functions. We don't want to give the latter a __tostring metamethod or printing gets very weird. * lib/std/container.lua (metatable._functions): Make a nometa clone of the table we return. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 2 ++ lib/std/container.lua | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index b5e4172..f284efa 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ Stdlib NEWS - User visible changes - Much improved documentation for `optparse`, so you should be able to use it without reading the source code now! + - Fix `getmetatable (container._functions) == getmetatable (container)`, + which made tostring on containers misbehave, among other latent bugs. * Noteworthy changes in release 37 (2014-01-19) [stable] diff --git a/lib/std/container.lua b/lib/std/container.lua index b5f9874..6ac68b6 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -121,7 +121,10 @@ local functions = { local metatable = { _type = "Container", _init = {}, - _functions = functions, + + -- Set this to a copy of the table we return as the Container module + -- at the end of this file, which has its metatable set. + _functions = base.clone (functions, "nometa"), --- Return a clone of this container. From d25ea45334a9a52882b67cc2e1a1cf22ef31d661 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 24 Jan 2014 00:36:12 +1300 Subject: [PATCH 076/703] container: cleanup, speedup and bonus bugfix. * lib/std/container.lua (mapfields): Factored out of `metatable.__call` and optimised for speed. (metatable.__call): Adjust accordingly. (empty): Copied from table.empty to avoind introducing a require loop. (modulefunction): Wrap argument in a functable for easily recognising what not to copy during cloning. (metatable): Simplify. * lib/std/object.lua: Import mapfields from Containec, but unwrap it along with Container.prototype before saving in the Object metatable for faster access. Add appropriate LDocs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 38 +++++++++ lib/std/container.lua | 191 +++++++++++++++++++++++++++--------------- lib/std/object.lua | 40 ++++++++- 3 files changed, 197 insertions(+), 72 deletions(-) diff --git a/NEWS b/NEWS index f284efa..04d8a21 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,37 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] +** New featurs: + + - 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: + + 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. + ** Bug fixes: - Much improved documentation for `optparse`, so you should be able @@ -10,6 +41,13 @@ Stdlib NEWS - User visible changes - 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: diff --git a/lib/std/container.lua b/lib/std/container.lua index 6ac68b6..b63775f 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -59,7 +59,8 @@ local base = require "std.base" -local func = require "std.functional" + +local clone, merge = base.clone, base.merge --- Return the named entry from x's metatable. @@ -94,22 +95,88 @@ local function filter (t, f) end -local functions = { - -- Type of this container. - -- @static - -- @tparam std.container o an container - -- @treturn string type of the container - -- @see std.object.prototype - prototype = function (o) - local _type = metaentry (o, "_type") - if type (o) == "table" and _type ~= nil then - return _type - end - return type (o) - end, +--- Return whether table is empty. +-- @tparam table t any table +-- @return `true` if `t` is empty, otherwise `false` +-- @see std.table.empty +local function empty (t) + return not next (t) +end + + +local ModuleFunction = { + __tostring = function (self) return tostring (self.call) end, + __call = function (self, ...) return self.call (...) end, } +--- Mark a function not to be copied into clones. +-- +-- It responds to `type` with `table`, but otherwise behaves like a +-- regular function. Marking uncopied module functions in-situ like this +-- (as opposed to doing book keeping in the metatable) means that we +-- don't have to create a new metatable with the book keeping removed for +-- cloned objects, we can just share our existing metatable directly. +-- @func fn a function +-- @treturn functable a callable functable for `fn` +local function modulefunction (fn) + return setmetatable ({_type = "modulefunction", call = fn}, ModuleFunction) +end + + +--- Return `obj` with references to the fields of `src` merged in. +-- @static +-- @tparam table obj destination object +-- @tparam table src fields to copy int clone +-- @tparam[opt={}] table map `{old_key=new_key, ...}` +-- @treturn table `obj` with non-private fields from `src` merged, and +-- a metatable with private fields (if any) merged, both sets of keys +-- renamed according to `map` +-- @see std.object.mapfields +local function mapfields (obj, src, map) + map = map or {} + local mt = getmetatable (obj) or {} + + -- Map key pairs. + for k, v in pairs (src) do + local key, dst = map[k] or k, obj + if type (key) == "string" and key:sub (1, 1) == "_" then + dst = mt + end + dst[key] = v + end + + -- Quicker to remove this after copying fields than test for it + -- it on every iteration above. + mt._functions = nil + + -- Inject module functions. + for k, v in pairs (src._functions or {}) do + obj[k] = modulefunction (v) + end + + -- Only set non-empty metatable. + if not empty (mt) then + setmetatable (obj, mt) + end + return obj +end + + +-- Type of this container. +-- @static +-- @tparam std.container o an container +-- @treturn string type of the container +-- @see std.object.prototype +local function prototype (o) + local _type = metaentry (o, "_type") + if type (o) == "table" and _type ~= nil then + return _type + end + return type (o) +end + + --- Container prototype. -- @table std.container -- @string[opt="Container"] _type type of Container, returned by @@ -119,68 +186,46 @@ local functions = { -- @tfield nil|table _functions a table of module functions not copied -- by @{std.object.__call} local metatable = { - _type = "Container", - _init = {}, - - -- Set this to a copy of the table we return as the Container module - -- at the end of this file, which has its metatable set. - _functions = base.clone (functions, "nometa"), - + _type = "Container", + _init = {}, --- Return a clone of this container. -- @function __call - -- @param ... arguments for `_init` + -- @param x a table if prototype `_init` is a table, otherwise first + -- argument for a function type `_init` + -- @param ... any additional arguments for `_init` -- @treturn std.container a clone of the called container. -- @see std.object:__call - __call = function (self, ...) - local mt = getmetatable (self) - - -- Make a shallow copy of prototype, skipping metatable - -- _functions. - local fn = mt._functions or {} - local obj = filter (self, function (e) return not fn[e] end) - - -- Map arguments according to _init metamethod. - local _init = metaentry (self, "_init") - if type (_init) == "table" then - base.merge (obj, base.clone_rename (_init, ...)) - else - obj = _init (obj, ...) - end - - -- Extract any new fields beginning with "_". - local obj_mt = {} - for k, v in pairs (obj) do - if type (k) == "string" and k:sub (1, 1) == "_" then - obj_mt[k], obj[k] = v, nil + __call = function (self, x, ...) + local mt = getmetatable (self) + local obj_mt = mt + local obj = {} + + -- This is the slowest part of cloning for any objects that have + -- a lot of fields to test and copy. If you need to clone a lot of + -- objects from a prototype with several module functions, it's much + -- faster to clone objects from each other than the prototype! + for k, v in pairs (self) do + if type (v) ~= "table" or v._type ~= "modulefunction" then + obj[k] = v end end - -- However, newly passed _functions from _init arguments are - -- copied as prototype functions into the object. - func.map (function (k) obj[k] = obj_mt._functions[k] end, - pairs, obj_mt._functions or {}) - - -- _functions is not propagated from prototype to clone. - if next (obj_mt) == nil then - -- Reuse metatable if possible - obj_mt = getmetatable (self) + if type (mt._init) == "table" then + obj = (self.mapfields or mapfields) (obj, x, mt._init) else + obj = mt._init (obj, x, ...) + end + + -- If a metatable was set, then merge our fields and use it. + if not empty (getmetatable (obj) or {}) then + obj_mt = merge (clone (mt), getmetatable (obj)) - -- Otherwise copy the prototype metatable... - local t = filter (mt, function (e) return e ~= "_functions" end) - -- ...but give preference to "_" prefixed keys from init table - obj_mt = base.merge (t, obj_mt) - - -- ...and merge container methods from prototype too. - if mt then - if type (obj_mt.__index) == "table" and type (mt.__index) == "table" then - local methods = base.clone (obj_mt.__index) - for k, v in pairs (mt.__index) do - methods[k] = methods[k] or v - end - obj_mt.__index = methods - end + -- Merge object methods. + if type (obj_mt.__index) == "table" and + type ((mt or {}).__index) == "table" + then + obj_mt.__index = merge (clone (mt.__index), obj_mt.__index) end end @@ -194,8 +239,8 @@ local metatable = { -- @see std.object.__tostring __tostring = function (self) local totable = getmetatable (self).__totable - local array = base.clone (totable (self), "nometa") - local other = base.clone (array, "nometa") + local array = clone (totable (self), "nometa") + local other = clone (array, "nometa") local s = "" if #other > 0 then for i in ipairs (other) do other[i] = nil end @@ -233,4 +278,12 @@ local metatable = { end, } -return setmetatable (functions, metatable) +return setmetatable ({ + + -- Normally, these are set and wrapped automatically during cloning. + -- But, we have to bootstrap the first object, so in this one instance + -- it has to be done manually. + + mapfields = modulefunction (mapfields), + prototype = modulefunction (prototype), +}, metatable) diff --git a/lib/std/object.lua b/lib/std/object.lua index 355f2b7..eea9635 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -57,6 +57,13 @@ ]] + +-- Surprise!! The real root object is Container, which has less +-- functionality than Object, but that makes the heirarchy hard to +-- explain, so the documentation pretends this is the root object, and +-- Container is derived from it. Confused? ;-) + + local Container = require "std.container" local metamethod = (require "std.functional").metamethod @@ -81,7 +88,7 @@ return Container { __index = { --- Clone this Object. -- @function clone - -- @tparam std.object o an object + -- @tparam std.object obj an object -- @param ... a list of arguments if `o._init` is a function, or a -- single table if `o._init` is a table. -- @treturn std.object a clone of `o` @@ -112,11 +119,38 @@ return Container { -- @function prototype -- @param x anything -- @treturn string type of `x` - prototype = Container.prototype, + prototype = Container.prototype.call, + + + --- Return `obj` with references to the fields of `src` merged in. + -- + -- More importantly, split the fields in `src` between `obj` and its + -- metatable. If any field names begin with `\_`, attach a metatable + -- to `obj` if it doesn't have one yet, and copy the "private" `\_` + -- prefixed fields there. + -- + -- You might want to use this function to instantiate your derived + -- objct clones when the prototype's `_init` is a function -- when + -- `_init` is a table, the default (inherited unless you overwrite + -- it) clone method calls `mapfields` automatically. When you're + -- using a function `_init` setting, `clone` doesn't know what to + -- copy into a new object from the `_init` function's arguments... + -- so you're on your own. Except that calling `mapfields` inside + -- `_init` is safer than manually splitting `src` into `obj` and + -- its metatable, because you'll pick up fixes and changes when you + -- upgrade stdlib. + -- @function mapfields + -- @tparam table obj destination object + -- @tparam table src fields to copy int clone + -- @tparam[opt={}] table map `{old_key=new_key, ...}` + -- @treturn table `obj` with non-private fields from `src` merged, + -- and a metatable with private fields (if any) merged, both sets + -- of keys renamed according to `map` + mapfields = Container.mapfields.call, -- Backwards compatibility: - type = Container.prototype, + type = Container.prototype.call, }, From 4f3ce0a29437bf8c1a058463ddf403f3a941453d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 24 Jan 2014 02:42:50 +1300 Subject: [PATCH 077/703] container: inline `filter` to only caller. * lib/std/container.lua (filter): Remove from here. (metatable.__totable): Inline `filter` here. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index b63775f..5a499a8 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -77,24 +77,6 @@ local function metaentry (x, n) end ---- Filter a table with a function. --- @tparam table t source table --- @tparam function f a function that takes key and value arguments --- from calling `pairs` on `t`, and returns non-`nil` for elements --- that should be in the returned table --- @treturn table a shallow copy of `t`, with elements removed according --- to `f` -local function filter (t, f) - local r = {} - for k, v in pairs (t) do - if f (k, v) then - r[k] = v - end - end - return r -end - - --- Return whether table is empty. -- @tparam table t any table -- @return `true` if `t` is empty, otherwise `false` @@ -272,9 +254,13 @@ local metatable = { -- @treturn table a shallow copy of non-private container fields -- @see std.object:__totable __totable = function (self) - return filter (self, function (e) - return type (e) ~= "string" or e:sub (1, 1) ~= "_" - end) + local t = {} + for k, v in pairs (self) do + if type (k) ~= "string" or k:sub (1, 1) ~= "_" then + t[k] = v + end + end + return t end, } From 4235647518bda91a7340b9230434916c8b119bbd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 24 Jan 2014 02:53:12 +1300 Subject: [PATCH 078/703] container: drastically simplify and speed up `prototype`. * lib/std/container.lua (prototype): Inline metaentry, and simplify. (metatable.__tostring): Use it. (metaentry): Remove. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++++ lib/std/container.lua | 22 ++-------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index 04d8a21..d30c1f6 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,10 @@ Stdlib NEWS - User visible changes 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 used to to + return its results. + ** Bug fixes: - Much improved documentation for `optparse`, so you should be able diff --git a/lib/std/container.lua b/lib/std/container.lua index 5a499a8..5d07fec 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -63,20 +63,6 @@ local base = require "std.base" local clone, merge = base.clone, base.merge ---- Return the named entry from x's metatable. --- @param x anything --- @tparam string n name of entry --- @return value associate with `n` in `x`'s metatable, else nil -local function metaentry (x, n) - local ok, f = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if not ok then f = nil end - return f -end - - --- Return whether table is empty. -- @tparam table t any table -- @return `true` if `t` is empty, otherwise `false` @@ -151,11 +137,7 @@ end -- @treturn string type of the container -- @see std.object.prototype local function prototype (o) - local _type = metaentry (o, "_type") - if type (o) == "table" and _type ~= nil then - return _type - end - return type (o) + return (getmetatable (o) or {})._type or type (o) end @@ -245,7 +227,7 @@ local metatable = { s = s .. table.concat (dict, ", ") end - return metaentry (self, "_type") .. " {" .. s .. "}" + return prototype (self) .. " {" .. s .. "}" end, From 92b6b501cb81ca5a4f8a4db5806b59adcb70282d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 24 Jan 2014 03:01:23 +1300 Subject: [PATCH 079/703] container: inline calls to `empty`. * lib/std/container (mapfields, metatable._init): Replace `not empty` with `next`. (empty): Remove. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 5d07fec..64cd413 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -63,15 +63,6 @@ local base = require "std.base" local clone, merge = base.clone, base.merge ---- Return whether table is empty. --- @tparam table t any table --- @return `true` if `t` is empty, otherwise `false` --- @see std.table.empty -local function empty (t) - return not next (t) -end - - local ModuleFunction = { __tostring = function (self) return tostring (self.call) end, __call = function (self, ...) return self.call (...) end, @@ -124,7 +115,7 @@ local function mapfields (obj, src, map) end -- Only set non-empty metatable. - if not empty (mt) then + if next (mt) then setmetatable (obj, mt) end return obj @@ -182,7 +173,7 @@ local metatable = { end -- If a metatable was set, then merge our fields and use it. - if not empty (getmetatable (obj) or {}) then + if next (getmetatable (obj) or {}) then obj_mt = merge (clone (mt), getmetatable (obj)) -- Merge object methods. From 4afa84e59166c0d24a55a6ce7f9bf8fd29799421 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 28 Jan 2014 15:53:24 +1300 Subject: [PATCH 080/703] io: fix actual and latent bugs in `io.die` and `io.warn`. * specs/spec_helper.lua.in (luaproc): Factor out of `tabulate_output`. (tabulate_output): Adjust. * specs/io_spec.lua (die, warn): Add new behaviour examples. * lib/std/io.lua (warn): Adjust to work properly with new specs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 6 +++++ lib/std/io.lua | 20 +++++++-------- specs/io_spec.yaml | 54 ++++++++++++++++++++++++++++++++++++++++ specs/spec_helper.lua.in | 7 +++++- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index d30c1f6..6822d89 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,12 @@ Stdlib NEWS - User visible changes - 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. + - Fix `getmetatable (container._functions) == getmetatable (container)`, which made tostring on containers misbehave, among other latent bugs. diff --git a/lib/std/io.lua b/lib/std/io.lua index d72883d..7d5a730 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -112,19 +112,17 @@ end --- Give warning with the name of program and file (if any). -- @param ... arguments for format local function warn (...) - if prog.name then - io.stderr:write (prog.name .. ":") + local prefix = "" + if (prog or {}).name then + prefix = prog.name .. ":" + elseif (prog or {}).file then + prefix = prog.file .. ":" end - if prog.file then - io.stderr:write (prog.file .. ":") + if #prefix > 0 and (prog or {}).line then + prefix = prefix .. tostring (prog.line) .. ":" end - if prog.line then - io.stderr:write (tostring (prog.line) .. ":") - end - if prog.name or prog.file or prog.line then - io.stderr:write (" ") - end - writelines (io.stderr, string.format (...)) + if #prefix > 0 then prefix = prefix .. " " end + writelines (io.stderr, prefix .. string.format (...)) end --- Die with error. diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 4c82dbf..dcd7c6a 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -51,6 +51,33 @@ specify std.io: - describe die: + - before: + script = [[require "std.io".warn "By 'eck!"]] + - it outputs a message to stderr: + expect (luaproc (script)).should_output_error "By 'eck!\n" + - it ignores `prog.line` without `prog.file` or `prog.name`: + script = [[prog = { line = 125 };]] .. script + expect (luaproc (script)).should_output_error "By 'eck!\n" + - it prefixes `prog.name` if any: | + script = [[prog = { name = "name" };]] .. script + expect (luaproc (script)).should_output_error "name: By 'eck!\n" + - it appends `prog.line` if any, to `prog.name`: | + script = [[prog = { line = 125, name = "name" };]] .. script + expect (luaproc (script)).should_output_error "name:125: By 'eck!\n" + - it prefixes `prog.file` if any: | + script = [[prog = { file = "file" };]] .. script + expect (luaproc (script)).should_output_error "file: By 'eck!\n" + - it appends `prog.line` if any, to `prog.name`: | + script = [[prog = { file = "file", line = 125 };]] .. script + expect (luaproc (script)).should_output_error "file:125: By 'eck!\n" + - it prefers `prog.name` to `prog.file`: | + script = [[prog = { file = "file", name = "name" };]] .. script + expect (luaproc (script)).should_output_error "name: By 'eck!\n" + - it appends `prog.line` if any to `prog.name` over `prog.file`: | + script = [[ + prog = { file = "file", line = 125, name = "name" } + ]] .. script + expect (luaproc (script)).should_output_error "name:125: By 'eck!\n" - describe process_files: @@ -71,6 +98,33 @@ specify std.io: - describe warn: + - before: + script = [[require "std.io".warn "Ayup!"]] + - it outputs a message to stderr: + expect (luaproc (script)).should_output_error "Ayup!\n" + - it ignores `prog.line` without `prog.file` or `prog.name`: + script = [[prog = { line = 125 };]] .. script + expect (luaproc (script)).should_output_error "Ayup!\n" + - it prefixes `prog.name` if any: | + script = [[prog = { name = "name" };]] .. script + expect (luaproc (script)).should_output_error "name: Ayup!\n" + - it appends `prog.line` if any, to `prog.name`: | + script = [[prog = { line = 125, name = "name" };]] .. script + expect (luaproc (script)).should_output_error "name:125: Ayup!\n" + - it prefixes `prog.file` if any: | + script = [[prog = { file = "file" };]] .. script + expect (luaproc (script)).should_output_error "file: Ayup!\n" + - it appends `prog.line` if any, to `prog.name`: | + script = [[prog = { file = "file", line = 125 };]] .. script + expect (luaproc (script)).should_output_error "file:125: Ayup!\n" + - it prefers `prog.name` to `prog.file`: | + script = [[prog = { file = "file", name = "name" };]] .. script + expect (luaproc (script)).should_output_error "name: Ayup!\n" + - it appends `prog.line` if any to `prog.name` over `prog.file`: | + script = [[ + prog = { file = "file", line = 125, name = "name" } + ]] .. script + expect (luaproc (script)).should_output_error "name:125: Ayup!\n" - describe writelines: diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 75b4232..5ec6bd6 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -14,13 +14,18 @@ local function mkscript (code) return f end -local function tabulate_output (code) +function luaproc (code) local f = mkscript (code) local proc = hell.spawn { LUA, f; env = { LUA_PATH=package.path, LUA_INIT="", LUA_INIT_5_2="" }, } os.remove (f) + return proc +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]*", From 4169bbcb9726d410c7abcbb7b79c2fdd82985f40 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 28 Jan 2014 18:27:42 +1300 Subject: [PATCH 081/703] optparse: integrate with `io.die` and `io.warn`. * specs/io_spec.yaml: Additional specs for what to do when `opts.program` and/or `opts.line` are set. * specs/optparse_spec.yaml (io.die, io.warn): New specs how to behave when `io.die` or `io.warn` are called after a successful `parser:parse` call. * lib/std/io.lua (warn): Implement specified behaviour for new `opts.line` and `opts.warn` specs. * lib/std/optparse.lua: Add LDocs for new behaviour. (parser:parse): Set a metatable on the returned opts table so than `opts.program` etc. can be found by `io.die` and `io.warn`. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 13 +++++++++ lib/std/io.lua | 32 +++++++++++++++++++-- lib/std/optparse.lua | 8 +++++- specs/io_spec.yaml | 61 ++++++++++++++++++++++++++++++++++------ specs/optparse_spec.yaml | 60 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 161 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index 6822d89..963a8f4 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,19 @@ Stdlib NEWS - User visible changes than previous releases, taking about 20% of the time it used to 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: + + 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 diff --git a/lib/std/io.lua b/lib/std/io.lua index 7d5a730..609fc6d 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -110,23 +110,49 @@ local function process_files (f) 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. @{std.optparse:parse} +-- returns an `opts` table that provides the required `program` +-- field, as long as you assign it back to `_G.opts`: +-- +-- 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 +-- -- @param ... arguments for format +-- @see std.optparse:parse local function warn (...) local prefix = "" if (prog or {}).name then prefix = prog.name .. ":" + if prog.line then + prefix = prefix .. tostring (prog.line) .. ":" + end elseif (prog or {}).file then prefix = prog.file .. ":" - end - if #prefix > 0 and (prog or {}).line then - prefix = prefix .. tostring (prog.line) .. ":" + if prog.line then + prefix = prefix .. tostring (prog.line) .. ":" + end + elseif (opts or {}).program then + prefix = opts.program .. ":" + if opts.line then + prefix = prefix .. tostring (opts.line) .. ":" + end end if #prefix > 0 then prefix = prefix .. " " end writelines (io.stderr, prefix .. string.format (...)) end --- Die with error. +-- This function uses the same rules to build a message prefix +-- as @{std.io.warn}. -- @param ... arguments for format +-- @see std.io.warn local function die (...) warn (...) error () diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 4a81511..f57592a 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -66,6 +66,10 @@ (`-xyz`), equals separators to long options (`--long=ARG`) are fully expanded before any handler is called. + Note that @{std.io.die} and @{std.io.warn} will only prefix messages + with `parser.program` if the parser options are assigned back to + `_G.opts` as shown in the example above. + @classmod std.optparse ]=] @@ -554,7 +558,9 @@ local function parse (self, arglist) end end - return self.unrecognised, self.opts + -- metatable allows `io.warn` to find `parser.program` when assigned + -- back to _G.opts. + return self.unrecognised, setmetatable (self.opts, {__index = self}) end diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index dcd7c6a..0b0e5e8 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -52,12 +52,15 @@ specify std.io: - describe die: - before: - script = [[require "std.io".warn "By 'eck!"]] + script = [[require "std.io".die "By 'eck!"]] - it outputs a message to stderr: expect (luaproc (script)).should_output_error "By 'eck!\n" - it ignores `prog.line` without `prog.file` or `prog.name`: script = [[prog = { line = 125 };]] .. script expect (luaproc (script)).should_output_error "By 'eck!\n" + - it ignores `opts.line` without `opts.program`: + script = [[opts = { line = 99 };]] .. script + expect (luaproc (script)).should_output_error "By 'eck!\n" - it prefixes `prog.name` if any: | script = [[prog = { name = "name" };]] .. script expect (luaproc (script)).should_output_error "name: By 'eck!\n" @@ -70,14 +73,35 @@ specify std.io: - it appends `prog.line` if any, to `prog.name`: | script = [[prog = { file = "file", line = 125 };]] .. script expect (luaproc (script)).should_output_error "file:125: By 'eck!\n" - - it prefers `prog.name` to `prog.file`: | - script = [[prog = { file = "file", name = "name" };]] .. script + - it prefers `prog.name` to `prog.file` or `opts.program`: | + script = [[ + prog = { file = "file", name = "name" } + opts = { program = "program" } + ]] .. script expect (luaproc (script)).should_output_error "name: By 'eck!\n" - - it appends `prog.line` if any to `prog.name` over `prog.file`: | + - 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)).should_output_error "name:125: By 'eck!\n" + - it prefers `prog.file` to `opts.program`: | + script = [[ + prog = { file = "file" }; opts = { program = "program" } + ]] .. script + expect (luaproc (script)).should_output_error "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)).should_output_error "file:125: By 'eck!\n" + - it prefixes `opts.program` if any: | + script = [[opts = { program = "program" };]] .. script + expect (luaproc (script)).should_output_error "program: By 'eck!\n" + - it appends `opts.line` if any, to `opts.program`: | + script = [[opts = { line = 99, program = "program" };]] .. script + expect (luaproc (script)).should_output_error "program:99: By 'eck!\n" - describe process_files: @@ -102,7 +126,7 @@ specify std.io: script = [[require "std.io".warn "Ayup!"]] - it outputs a message to stderr: expect (luaproc (script)).should_output_error "Ayup!\n" - - it ignores `prog.line` without `prog.file` or `prog.name`: + - it ignores `prog.line` without `prog.file`, `prog.name` or `opts.program`: script = [[prog = { line = 125 };]] .. script expect (luaproc (script)).should_output_error "Ayup!\n" - it prefixes `prog.name` if any: | @@ -117,14 +141,35 @@ specify std.io: - it appends `prog.line` if any, to `prog.name`: | script = [[prog = { file = "file", line = 125 };]] .. script expect (luaproc (script)).should_output_error "file:125: Ayup!\n" - - it prefers `prog.name` to `prog.file`: | - script = [[prog = { file = "file", name = "name" };]] .. script + - it prefers `prog.name` to `prog.file` or `opts.program`: | + script = [[ + prog = { file = "file", name = "name" } + opts = { program = "program" } + ]] .. script expect (luaproc (script)).should_output_error "name: Ayup!\n" - - it appends `prog.line` if any to `prog.name` over `prog.file`: | + - 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)).should_output_error "name:125: Ayup!\n" + - it prefers `prog.file` to `opts.program`: | + script = [[ + prog = { file = "file" }; opts = { program = "program" } + ]] .. script + expect (luaproc (script)).should_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)).should_output_error "file:125: Ayup!\n" + - it prefixes `opts.program` if any: | + script = [[opts = { program = "program" };]] .. script + expect (luaproc (script)).should_output_error "program: Ayup!\n" + - it appends `opts.line` if any, to `opts.program`: | + script = [[opts = { line = 99, program = "program" };]] .. script + expect (luaproc (script)).should_output_error "program:99: Ayup!\n" - describe writelines: diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index a3547fb..fcd747e 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -195,7 +195,7 @@ specify std.optparse: parser:on (]] .. onargstr .. [[) - local arg, opts = parser:parse (_G.arg) + _G.arg, _G.opts = parser:parse (_G.arg) o = {} for k, v in pairs (opts) do @@ -368,3 +368,61 @@ specify std.optparse: - it stops separating at an optional argument option: expect (parseargs ([[{"x", "this"}, parser.optional]], {"-bxbit"})). should_output "opts = { b = true, this = bit }\n" + + - context with io.die: + - before: | + runscript = function (code) + return luaproc ([[ + local OptionParser = require "std.optparse" + local parser = OptionParser ("program 0\nUsage: program\n") + _G.arg, _G.opts = parser:parse (_G.arg) + ]] .. code .. [[ + require "std.io".die "By 'eck!" + ]]) + end + - it prefers `prog.name` to `opts.program`: | + code = [[prog = { file = "file", name = "name" }]] + expect (runscript (code)).should_output_error "name: By 'eck!\n" + - it prefers `prog.file` to `opts.program`: | + code = [[prog = { file = "file" }]] + expect (runscript (code)).should_output_error "file: By 'eck!\n" + - it appends `prog.line` if any to `prog.file` over using `opts`: | + code = [[ + prog = { file = "file", line = 125 }; opts.line = 99]] + expect (runscript (code)). + should_output_error "file:125: By 'eck!\n" + - it prefixes `opts.program` if any: | + expect (runscript ("")).should_output_error "program: By 'eck!\n" + - it appends `opts.line` if any, to `opts.program`: | + code = [[opts.line = 99]] + expect (runscript (code)). + should_output_error "program:99: By 'eck!\n" + + - context with io.warn: + - before: | + runscript = function (code) + return luaproc ([[ + local OptionParser = require "std.optparse" + local parser = OptionParser ("program 0\nUsage: program\n") + _G.arg, _G.opts = parser:parse (_G.arg) + ]] .. code .. [[ + require "std.io".warn "Ayup!" + ]]) + end + - it prefers `prog.name` to `opts.program`: | + code = [[prog = { file = "file", name = "name" }]] + expect (runscript (code)).should_output_error "name: Ayup!\n" + - it prefers `prog.file` to `opts.program`: | + code = [[prog = { file = "file" }]] + expect (runscript (code)).should_output_error "file: Ayup!\n" + - it appends `prog.line` if any to `prog.file` over using `opts`: | + code = [[ + prog = { file = "file", line = 125 }; opts.line = 99]] + expect (runscript (code)). + should_output_error "file:125: Ayup!\n" + - it prefixes `opts.program` if any: | + expect (runscript ("")).should_output_error "program: Ayup!\n" + - it appends `opts.line` if any, to `opts.program`: | + code = [[opts.line = 99]] + expect (runscript (code)). + should_output_error "program:99: Ayup!\n" From 370bb5c7550915f4b15f09447ebd25031c82c40d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 28 Jan 2014 19:16:34 +1300 Subject: [PATCH 082/703] specs: use a custom matcher to specify process status and error. * specs/spec_helper.lua.in (matchers.fail_with): Custom matcher to specify process non-zero exit as well as stderr content. * specs/io_spec.yaml (io.die), specs/optparse_spec.yaml (io.die): Use it to ensure that in addition to the correct error output, the exit status is non-zero. Signed-off-by: Gary V. Vaughan --- specs/io_spec.yaml | 26 +++++++------- specs/optparse_spec.yaml | 11 +++--- specs/spec_helper.lua.in | 73 +++++++++++++++++++++++++++++----------- 3 files changed, 72 insertions(+), 38 deletions(-) diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 0b0e5e8..cc76e82 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -54,54 +54,54 @@ specify std.io: - before: script = [[require "std.io".die "By 'eck!"]] - it outputs a message to stderr: - expect (luaproc (script)).should_output_error "By 'eck!\n" + expect (luaproc (script)).should_fail_with "By 'eck!\n" - it ignores `prog.line` without `prog.file` or `prog.name`: script = [[prog = { line = 125 };]] .. script - expect (luaproc (script)).should_output_error "By 'eck!\n" + expect (luaproc (script)).should_fail_with "By 'eck!\n" - it ignores `opts.line` without `opts.program`: script = [[opts = { line = 99 };]] .. script - expect (luaproc (script)).should_output_error "By 'eck!\n" + expect (luaproc (script)).should_fail_with "By 'eck!\n" - it prefixes `prog.name` if any: | script = [[prog = { name = "name" };]] .. script - expect (luaproc (script)).should_output_error "name: By 'eck!\n" + expect (luaproc (script)).should_fail_with "name: By 'eck!\n" - it appends `prog.line` if any, to `prog.name`: | script = [[prog = { line = 125, name = "name" };]] .. script - expect (luaproc (script)).should_output_error "name:125: By 'eck!\n" + expect (luaproc (script)).should_fail_with "name:125: By 'eck!\n" - it prefixes `prog.file` if any: | script = [[prog = { file = "file" };]] .. script - expect (luaproc (script)).should_output_error "file: By 'eck!\n" + expect (luaproc (script)).should_fail_with "file: By 'eck!\n" - it appends `prog.line` if any, to `prog.name`: | script = [[prog = { file = "file", line = 125 };]] .. script - expect (luaproc (script)).should_output_error "file:125: By 'eck!\n" + expect (luaproc (script)).should_fail_with "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)).should_output_error "name: By 'eck!\n" + expect (luaproc (script)).should_fail_with "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)).should_output_error "name:125: By 'eck!\n" + expect (luaproc (script)).should_fail_with "name:125: By 'eck!\n" - it prefers `prog.file` to `opts.program`: | script = [[ prog = { file = "file" }; opts = { program = "program" } ]] .. script - expect (luaproc (script)).should_output_error "file: By 'eck!\n" + expect (luaproc (script)).should_fail_with "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)).should_output_error "file:125: By 'eck!\n" + expect (luaproc (script)).should_fail_with "file:125: By 'eck!\n" - it prefixes `opts.program` if any: | script = [[opts = { program = "program" };]] .. script - expect (luaproc (script)).should_output_error "program: By 'eck!\n" + expect (luaproc (script)).should_fail_with "program: By 'eck!\n" - it appends `opts.line` if any, to `opts.program`: | script = [[opts = { line = 99, program = "program" };]] .. script - expect (luaproc (script)).should_output_error "program:99: By 'eck!\n" + expect (luaproc (script)).should_fail_with "program:99: By 'eck!\n" - describe process_files: diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index fcd747e..d0d800d 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -382,21 +382,20 @@ specify std.optparse: end - it prefers `prog.name` to `opts.program`: | code = [[prog = { file = "file", name = "name" }]] - expect (runscript (code)).should_output_error "name: By 'eck!\n" + expect (runscript (code)).should_fail_with "name: By 'eck!\n" - it prefers `prog.file` to `opts.program`: | code = [[prog = { file = "file" }]] - expect (runscript (code)).should_output_error "file: By 'eck!\n" + expect (runscript (code)).should_fail_with "file: By 'eck!\n" - it appends `prog.line` if any to `prog.file` over using `opts`: | code = [[ prog = { file = "file", line = 125 }; opts.line = 99]] - expect (runscript (code)). - should_output_error "file:125: By 'eck!\n" + expect (runscript (code)).should_fail_with "file:125: By 'eck!\n" - it prefixes `opts.program` if any: | - expect (runscript ("")).should_output_error "program: By 'eck!\n" + expect (runscript ("")).should_fail_with "program: By 'eck!\n" - it appends `opts.line` if any, to `opts.program`: | code = [[opts.line = 99]] expect (runscript (code)). - should_output_error "program:99: By 'eck!\n" + should_fail_with "program:99: By 'eck!\n" - context with io.warn: - before: | diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 5ec6bd6..6fb1b92 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -123,28 +123,63 @@ end totable = (require "std.table").totable --- Custom matcher for set membership. +do + -- Custom matcher for set membership. -local set = require "std.set" -local util = require "specl.util" -local matchers = require "specl.matchers" + local set = require "std.set" + local util = require "specl.util" + local matchers = require "specl.matchers" -local Matcher, matchers, q = - matchers.Matcher, matchers.matchers, matchers.stringify + local Matcher, matchers, q = + matchers.Matcher, matchers.matchers, matchers.stringify -matchers.have_member = Matcher { - function (actual, expect) - return set.member (actual, expect) - end, + matchers.have_member = Matcher { + function (actual, expect) + return set.member (actual, expect) + end, - actual = "set", + actual = "set", - format_expect = function (expect) - return " a set containing " .. q (expect) .. ", " - end, + format_expect = function (expect) + return " a set containing " .. q (expect) .. ", " + end, - format_any_of = function (alternatives) - return " a set containing any of " .. - util.concat (alternatives, util.QUOTED) .. ", " - end, -} + format_any_of = function (alternatives) + return " a set containing any of " .. + util.concat (alternatives, util.QUOTED) .. ", " + end, + } +end + + +do + --[[ ======================================= ]]-- + --[[ Remove this after Specl 11 is released. ]]-- + --[[ ======================================= ]]-- + + -- Custom matcher for specl.shell failure with error message. + + local matchers = require "specl.matchers" + local Matcher, matchers, reformat = + matchers.Matcher, matchers.matchers, matchers.reformat + + matchers.fail_with = matchers.fail_with or Matcher { + function (actual, expect) + return (actual.status ~= 0) and (actual.errout == expect) + end, + + actual_type = "Process", + + format_actual = function (process) + return ":" .. reformat (process.errout) + end, + + format_expect = function (expect) + return " error output: " .. reformat (expect) + end, + + format_alternatives = function (adaptor, alternatives) + return " error output:" .. reformat (alternatives, adaptor) + end, + } +end From e7a80b9d9894f498ee4f01a57a5da87a9d938987 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 28 Jan 2014 19:30:51 +1300 Subject: [PATCH 083/703] specs: use Specl 10 `a_permutation_of` adaptor for tighter specs. * specs/tree_spec.yaml: Use `a_permutation_of` instead of `all_of` to enforce some reordering of only the specified elements. Signed-off-by: Gary V. Vaughan --- specs/tree_spec.yaml | 80 +++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index d9ec245..ca0c8b7 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -205,7 +205,8 @@ specify std.tree: for v in f {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} do l[1+#l]=v end - expect (l).should_contain.all_of {"one", 2, "three", 4, "bar", "five"} + expect (l).should_contain. + a_permutation_of {"one", 2, "three", 4, "bar", "five"} - it works on trees too: for v in f (Tree {Tree {"one", Tree {two=2}, @@ -215,7 +216,8 @@ specify std.tree: do l[1+#l]=v end - expect (l).should_contain.all_of {"one", 2, "three", 4, "bar", "five"} + expect (l).should_contain. + a_permutation_of {"one", 2, "three", 4, "bar", "five"} - it diagnoses non-table arguments: expect (f ()).should_error ("table expected") expect (f "string").should_error ("table expected") @@ -293,33 +295,33 @@ specify std.tree: -- even that the array elements are visited in order! subject = {"first", "second"; third = "3rd"} expect (traverse (subject)).should_contain. - all_of {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {"third"}, subject["third"]}, -- 3rd - {"join", {}, subject}} -- } + a_permutation_of {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {"third"}, subject["third"]}, -- 3rd + {"join", {}, subject}} -- } - it includes hash parts of a nested table argument: | -- like `pairs`, `nodes` can visit elements in any order, so we cannot -- guarantee the array part is always visited before the hash part, or -- even that the array elements are visited in order! subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} expect (traverse (subject)).should_contain. - all_of {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"leaf", {"foo"}, subject["foo"]}, -- bar, - {"join", {}, subject}} -- } + a_permutation_of {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"leaf", {"foo"}, subject["foo"]}, -- bar, + {"join", {}, subject}} -- } - it works on trees too: | -- like `pairs`, `nodes` can visit elements in any order, so we cannot -- guarantee the array part is always visited before the hash part, or @@ -330,22 +332,22 @@ specify std.tree: foo="bar", "five"} expect (traverse (subject)).should_contain. - all_of {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"leaf", {"foo"}, subject["foo"]}, -- bar, - {"join", {}, subject}} -- } + a_permutation_of {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"leaf", {"foo"}, subject["foo"]}, -- bar, + {"join", {}, subject}} -- } - it generates path key-lists that are valid __index arguments: | pending "std.tree.__index handling empty list keys" subject = Tree {"first", Tree {"second"}, "3rd"} From be34432129c7154f25590b6dc825146359df3b6a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 30 Jan 2014 17:21:09 +1300 Subject: [PATCH 084/703] specs: more comprehensive specs for std.string.split. * specs/string_spec.yaml (split): Add a lot more specs. Empty separator causes an infinite loop with the current implementation. Signed-off-by: Gary V. Vaughan --- specs/string_spec.yaml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index e141a41..499571d 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -415,8 +415,25 @@ specify std.string: target = { "first", "the second one", "final entry" } subject = table.concat (target, ", ") f = M.split + - it returns a one-element list for an empty string: + expect (f ("", ", ")).should_equal {""} - it makes a table of substrings delimited by a separator: expect (f (subject, ", ")).should_equal (target) + - it returns n+1 elements for n separators: + expect (f (subject, "zero")).should_have_size (1) + expect (f (subject, "c")).should_have_size (2) + expect (f (subject, "s")).should_have_size (3) + expect (f (subject, "t")).should_have_size (4) + expect (f (subject, "e")).should_have_size (5) + - it returns an empty string element for consecutive separators: + expect (f ("xyzyzxy", "yz")).should_equal {"x", "", "xy"} + - it returns an empty string element when starting with separator: + expect (f ("xyzyzxy", "xyz")).should_equal {"", "yzxy"} + - it returns an empty string element when ending with separator: + expect (f ("xyzyzxy", "zxy")).should_equal {"xyzy", ""} + - it returns a table of 1-character strings for "" separator: + pending "uncomment after fixing infinite loop" + -- expect (f ("abcdef", "")).should_equal {"", "a", "b", "c", "d", "e", "f", ""} - it is available as a string metamethod: expect (subject:split ", ").should_equal (target) expect (("/foo/bar/baz.quux"):split "/").should_equal {"", "foo", "bar", "baz.quux"} @@ -424,7 +441,10 @@ specify std.string: original = subject newstring = f (subject, "e") expect (subject).should_be (original) - - "it diagnoses non-string arguments": + - it takes a Lua pattern as a separator: + expect (f (subject, "%s+")). + should_equal {"first,", "the", "second", "one,", "final", "entry"} + - it diagnoses non-string arguments: expect (f ("a string")).should_error ("string expected") expect (f (nil, ",")).should_error ("string expected") expect (f ({"a table"}, ",")).should_error ("string expected") From e67e13623eaf27f69484b006a5c9bb823dc5953c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 30 Jan 2014 17:31:32 +1300 Subject: [PATCH 085/703] string: simplify and speed up `std.string.split`. * lib/std/string.lua (split): It's a bit more code, but it is much easier to understand, and it's a lot faster now! * specs/string_spec.yaml (split): Uncomment pending infinite loop fix. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/string.lua | 22 +++++++++++++--------- specs/string_spec.yaml | 6 +++--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 963a8f4..c9d18bf 100644 --- a/NEWS +++ b/NEWS @@ -61,6 +61,9 @@ Stdlib NEWS - User visible changes - `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. diff --git a/lib/std/string.lua b/lib/std/string.lua index 08a6b9c..dd9027c 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -82,21 +82,25 @@ local function finds (s, p, init, plain) 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`. -- @todo Consider Perl and Python versions. -- @param s string to split -- @param sep separator pattern -- @return list of strings local function split (s, sep) - -- finds gets a list of {from, to, capt = {}} lists; we then - -- flatten the result, discarding the captures, and prepend 0 (1 - -- before the first character) and append 0 (1 after the last - -- character), and then read off the result in pairs. - local pairs = List.concat ({0}, List.flatten (finds (s, sep)), {0}) - local l = {} - for i = 1, #pairs, 2 do - table.insert (l, string.sub (s, pairs[i] + 1, pairs[i + 1] - 1)) + assert (type (s) == "string", + "bad argument #1 to 'split' (string expected, got " .. type (s) .. ")") + assert (type (sep) == "string", + "bad argument #2 to 'split' (string expected, got " .. type (sep) .. ")") + local b, len, t, patt = 0, #s, {}, "(.-)" .. sep + if sep == "" then patt = "(.)"; table.insert (t, "") end + while b <= len do + local e, n, m = string.find (s, patt, b + 1) + table.insert (t, m or s:sub (b + 1, len)) + b = n or len + 1 end - return l + return t end --- Require a module with a particular version. diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 499571d..7852876 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -432,11 +432,11 @@ specify std.string: - it returns an empty string element when ending with separator: expect (f ("xyzyzxy", "zxy")).should_equal {"xyzy", ""} - it returns a table of 1-character strings for "" separator: - pending "uncomment after fixing infinite loop" - -- expect (f ("abcdef", "")).should_equal {"", "a", "b", "c", "d", "e", "f", ""} + expect (f ("abcdef", "")).should_equal {"", "a", "b", "c", "d", "e", "f", ""} - it is available as a string metamethod: expect (subject:split ", ").should_equal (target) - expect (("/foo/bar/baz.quux"):split "/").should_equal {"", "foo", "bar", "baz.quux"} + expect (("/foo/bar/baz.quux"):split "/"). + should_equal {"", "foo", "bar", "baz.quux"} - the original subject is not perturbed: original = subject newstring = f (subject, "e") From 4981f30efa0a3e9daa6c39a3e9426d9c84ba610e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 30 Jan 2014 17:40:53 +1300 Subject: [PATCH 086/703] string: split now splits on consecutive whitespace by default. * lib/std/string.lua (split): Improve LDocs. Remove @todo. We now have whitespace default split pattern unless otherwise specified, just like Python. I didn't consider Perl, because my brain starts to haemorrhage whenever I do that. :-p * specs/string_spec.yaml (split): Remove spec for string `sep` argument now that it is optional. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 6 +++++- lib/std/string.lua | 8 +++----- specs/string_spec.yaml | 1 - 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index c9d18bf..16aadf7 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,11 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] -** New featurs: +** 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 diff --git a/lib/std/string.lua b/lib/std/string.lua index dd9027c..b11f135 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -84,15 +84,13 @@ 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`. --- @todo Consider Perl and Python versions. --- @param s string to split --- @param sep separator pattern +-- @string s to split +-- @string[opt="%s*"] sep separator pattern +-- @return list of strings -- @return list of strings local function split (s, sep) assert (type (s) == "string", "bad argument #1 to 'split' (string expected, got " .. type (s) .. ")") - assert (type (sep) == "string", - "bad argument #2 to 'split' (string expected, got " .. type (sep) .. ")") local b, len, t, patt = 0, #s, {}, "(.-)" .. sep if sep == "" then patt = "(.)"; table.insert (t, "") end while b <= len do diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 7852876..e061eab 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -445,7 +445,6 @@ specify std.string: expect (f (subject, "%s+")). should_equal {"first,", "the", "second", "one,", "final", "entry"} - it diagnoses non-string arguments: - expect (f ("a string")).should_error ("string expected") expect (f (nil, ",")).should_error ("string expected") expect (f ({"a table"}, ",")).should_error ("string expected") From 92ed82dd82f1a9c4ab7832f4f266327871d308d1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 30 Jan 2014 20:32:28 +1300 Subject: [PATCH 087/703] NEWS: reword a doubled 'to' to satisfy sanity checks. * NEWS: Reword a doubled 'to'. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 16aadf7..df91a37 100644 --- a/NEWS +++ b/NEWS @@ -38,8 +38,8 @@ Stdlib NEWS - User visible changes 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 used to to - return its results. + 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 From f19643086eb5b58c8837759d54d6494c4e9ac2e4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 30 Jan 2014 20:34:10 +1300 Subject: [PATCH 088/703] specs: remove trailing whitespace. * specs/optparse_spec.yaml: Remove trailing whitespace. Signed-off-by: Gary V. Vaughan --- specs/optparse_spec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index d0d800d..c16829a 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -381,7 +381,7 @@ specify std.optparse: ]]) end - it prefers `prog.name` to `opts.program`: | - code = [[prog = { file = "file", name = "name" }]] + code = [[prog = { file = "file", name = "name" }]] expect (runscript (code)).should_fail_with "name: By 'eck!\n" - it prefers `prog.file` to `opts.program`: | code = [[prog = { file = "file" }]] @@ -409,7 +409,7 @@ specify std.optparse: ]]) end - it prefers `prog.name` to `opts.program`: | - code = [[prog = { file = "file", name = "name" }]] + code = [[prog = { file = "file", name = "name" }]] expect (runscript (code)).should_output_error "name: Ayup!\n" - it prefers `prog.file` to `opts.program`: | code = [[prog = { file = "file" }]] From c71748a7e70050b95c107d340011f1ac150b97fa Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 30 Jan 2014 21:09:06 +1300 Subject: [PATCH 089/703] specs: commit new have_size matcher. * specs/spec_helper.lua.in (matchers.have_size): New matcher used by std.string.split specs. * configure.ac (AC_CONFIG_FILES): Remove write permission from generated spec_helper so I don't edit the wrong file next time. Signed-off-by: Gary V. Vaughan --- configure.ac | 3 ++- specs/spec_helper.lua.in | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 6e99177..63c8860 100644 --- a/configure.ac +++ b/configure.ac @@ -37,5 +37,6 @@ AC_PROG_SED dnl Generate output files SS_CONFIG_TRAVIS([ldoc specl]) -AC_CONFIG_FILES([Makefile build-aux/config.ld specs/spec_helper.lua]) +AC_CONFIG_FILES([Makefile build-aux/config.ld]) +AC_CONFIG_FILES([specs/spec_helper.lua], [chmod a-w specs/spec_helper.lua]) AC_OUTPUT diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 6fb1b92..bb0a7c0 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -124,7 +124,7 @@ totable = (require "std.table").totable do - -- Custom matcher for set membership. + -- Custom matcher for set size and set membership. local set = require "std.set" local util = require "specl.util" @@ -133,6 +133,25 @@ do local Matcher, matchers, q = matchers.Matcher, matchers.matchers, matchers.stringify + matchers.have_size = Matcher { + function (actual, expect) + local size = 0 + for _ in pairs (actual) do size = size + 1 end + return size == expect + end, + + actual = "table", + + format_expect = function (expect) + return " a table containing " .. expect .. " elements, " + end, + + format_any_of = function (alternatives) + return " a table with any of " .. + util.concat (alternatives, util.QUOTED) .. " elements, " + end, + } + matchers.have_member = Matcher { function (actual, expect) return set.member (actual, expect) From afa0c0087c4a2ab94e1f8af537b5a7a695f7e6da Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 30 Jan 2014 21:11:33 +1300 Subject: [PATCH 090/703] Release version 38 * NEWS: Record release date. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index df91a37..369d8d2 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ Stdlib NEWS - User visible changes -* Noteworthy changes in release ?.? (????-??-??) [?] +* Noteworthy changes in release 38 (2014-01-30) [stable] ** New features: From 7615e5b41006c6f8b84e22198e0fc2927bb03909 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 30 Jan 2014 21:11:46 +1300 Subject: [PATCH 091/703] maint: post-release administrivia. * configure.ac (AC_INIT): Bump release number to 39. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS | 3 +++ configure.ac | 2 +- local.mk | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.prev-version b/.prev-version index 81b5c5d..e522732 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -37 +38 diff --git a/NEWS b/NEWS index 369d8d2..570bdec 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Stdlib NEWS - User visible changes +* Noteworthy changes in release ?.? (????-??-??) [?] + + * Noteworthy changes in release 38 (2014-01-30) [stable] ** New features: diff --git a/configure.ac b/configure.ac index 63c8860..83ad307 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [38], [http://github.com/rrthomas/lua-stdlib/issues]) +AC_INIT([stdlib], [39], [http://github.com/rrthomas/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/local.mk b/local.mk index 4fe5628..0cf83e0 100644 --- a/local.mk +++ b/local.mk @@ -29,7 +29,7 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" ## Bootstrap. ## ## ---------- ## -old_NEWS_hash = 1a28799a850e021e45c3e98064a746d7 +old_NEWS_hash = 1c4d1bfae2d511327b83800043bc19c7 update_copyright_env = \ UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ From dbbec687163d94c494a1480bcadff17fcd274da2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 1 Feb 2014 20:03:42 +1300 Subject: [PATCH 092/703] specs: remove last vestiges of optparse's specl origins. * specs/optparse_spec.yaml (std.optparse): Update category, bug address, and copyright to be stdlib appropriate. Signed-off-by: Gary V. Vaughan --- specs/optparse_spec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index c16829a..c27e2e9 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -7,9 +7,9 @@ specify std.optparse: OptionParser = require "std.optparse" help = [[ - parseme (specl spec) 0α1 + parseme (stdlib spec) 0α1 - Copyright © 2013 Gary V. Vaughan + Copyright © 2014 Gary V. Vaughan This test program comes with ABSOLUTELY NO WARRANTY. Usage: parseme [] ... @@ -34,7 +34,7 @@ specify std.optparse: Footer text. - Please report bugs at . + Please report bugs at . ]] -- strip off the leading whitespace required for YAML From 887404b569c4be91dc6c892ff10ab741aefaa258 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 1 Feb 2014 22:32:57 +1300 Subject: [PATCH 093/703] optparse: fix a parse error with unhandled opt in combined options. * specs/optparse_spec.yaml: Add specifications for behaviour when encountering unhandled options in various circumstances. * lib/std/optparse.lua (normalise): Meet specifications. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 15 +++++++++++++++ lib/std/optparse.lua | 35 ++++++++++++++++++++++++++--------- specs/optparse_spec.yaml | 30 +++++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 570bdec..1e9c38f 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,21 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] +** Bug fixes: + + - `std.optparse` no longer throws an error when it encounters an + unhandled option in a combined (i.e. `-xyz`) short option string. + +** Incompatible changes: + + - `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. + * Noteworthy changes in release 38 (2014-01-30) [stable] diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index f57592a..8926c0e 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -113,7 +113,6 @@ local optional, required -- @tparam table arglist list of arguments to normalise -- @treturn table normalised argument list local function normalise (self, arglist) - -- First pass: Normalise to long option names, without '=' separators. local normal = {} local i = 0 while i < #arglist do @@ -124,22 +123,37 @@ local function normalise (self, arglist) if opt:sub (1, 2) == "--" then local x = opt:find ("=", 3, true) if x then - table.insert (normal, opt:sub (1, x - 1)) - table.insert (normal, opt:sub (x + 1)) - else + local optname = opt:sub (1, x -1) + + -- Only split recognised long options. + if self[optname] then + table.insert (normal, optname) + table.insert (normal, opt:sub (x + 1)) + else + x = nil + end + end + + if x == nil then + -- No '=', or substring before '=' is not a known option name. table.insert (normal, opt) end elseif opt:sub (1, 1) == "-" and string.len (opt) > 2 then - local rest + local orig, split, rest = opt, {} repeat opt, rest = opt:sub (1, 2), opt:sub (3) - table.insert (normal, opt) + table.insert (split, opt) + + -- If there's no handler, the option was a typo, or not supposed + -- to be an option at all. + if self[opt] == nil then + opt, split = nil, { orig } -- Split '-xyz' into '-x -yz', and reiterate for '-yz' - if self[opt].handler ~= optional and - self[opt].handler ~= required then + elseif self[opt].handler ~= optional and + self[opt].handler ~= required then if string.len (rest) > 0 then opt = "-" .. rest else @@ -148,10 +162,13 @@ local function normalise (self, arglist) -- Split '-xshortargument' into '-x shortargument'. else - table.insert (normal, rest) + table.insert (split, rest) opt = nil end until opt == nil + + -- Append split options to normalised list + for _, v in ipairs (split) do table.insert (normal, v) end else table.insert (normal, opt) end diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index c27e2e9..0508afe 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -96,9 +96,6 @@ specify std.optparse: end - after: os.remove (f) - - it leaves behind unrecognised options: - expect (parse {"--not-an-option"}). - should_output "args = { --not-an-option }\n" - it responds to --version with version text: expect (parse {"--version"}). should_match_output "^%s*parseme .*Copyright .*NO WARRANTY%.\n$" @@ -106,8 +103,13 @@ specify std.optparse: expect (parse {"--help"}). should_match_output ("^%s*Usage: parseme .*Banner.*Long.*" .. "Options:.*Footer.*/issues>%.\n$") + - it leaves behind unrecognised short options: + expect (parse {"-x"}).should_output "args = { -x }\n" - it recognises short options: expect (parse {"-b"}).should_output "opts = { b = true }\n" + - it leaves behind unrecognised options: + expect (parse {"--not-an-option"}). + should_output "args = { --not-an-option }\n" - it recognises long options: expect (parse {"--long"}).should_output "opts = { long = true }\n" - it recognises long options with hyphens: @@ -124,6 +126,10 @@ specify std.optparse: expect (parse {"--dryrun"}).should_output "opts = { dry_run = true }\n" - it recognises end of options marker: expect (parse {"-- -n"}).should_output "args = { -n }\n" + - context given an unhandled long option: + - it leaves behind unmangled argument: + expect (parse {"--not-an-option=with-an-argument"}). + should_output "args = { --not-an-option=with-an-argument }\n" - context given an option with a required argument: - it records an argument to a long option following an '=' delimiter: expect (parse {"--name=Gary"}). @@ -176,6 +182,24 @@ specify std.optparse: should_output "opts = { output = filename, verbose = true }\n" expect (parse {"-vobn"}). should_output "opts = { output = bn, verbose = true }\n" + - it leaves behind unsplittable short options: + expect (parse {"-xvb"}).should_output "args = { -xvb }\n" + expect (parse {"-vxb"}).should_output "args = { -vxb }\n" + expect (parse {"-vbx"}).should_output "args = { -vbx }\n" + - it separates short options before unsplittable options: + expect (parse {"-vb -xvb"}). + should_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" + expect (parse {"-vb -vxb"}). + should_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" + expect (parse {"-vb -vbx"}). + should_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" + - it separates short options after unsplittable options: + expect (parse {"-xvb -vb"}). + should_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" + expect (parse {"-vxb -vb"}). + should_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" + expect (parse {"-vbx -vb"}). + should_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" - describe parser:on: - before: | From a866cd51a5bb678ca03a95d3b216ff1d94a91824 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 3 Feb 2014 17:30:20 +1300 Subject: [PATCH 094/703] configury: be nicer to parallel make. * local.mk ($(srcdir)/doc): Factor out of... ($(dist_doc_DATA)): ...here. (ldoc_DEPS): Add $(srcdir)/doc. Signed-off-by: Gary V. Vaughan --- local.mk | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/local.mk b/local.mk index 0cf83e0..a7ef078 100644 --- a/local.mk +++ b/local.mk @@ -148,8 +148,10 @@ dist_modules_DATA += \ $(srcdir)/doc/modules/std.table.html \ $(NOTHING_ELSE) -ldoc_DEPS = $(dist_lua_DATA) $(dist_luastd_DATA) +$(srcdir)/doc: + mkdir $@ + +ldoc_DEPS = $(srcdir)/doc $(dist_lua_DATA) $(dist_luastd_DATA) $(dist_doc_DATA) $(dist_classes_DATA) $(dist_modules_DATA): $(ldoc_DEPS) - test -d "$(srcdir)/doc" || mkdir "$(srcdir)/doc" $(LDOC) -c build-aux/config.ld -d $(abs_srcdir)/doc . From 3610abc5298d7ec1773221b955a2b97957026c22 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 3 Feb 2014 17:57:32 +1300 Subject: [PATCH 095/703] configury: use a sentinel file for multi-target rules. With thanks to http://stackoverflow.com/questions/17216048/parallel-gnu-make-and-rules-that-create-several-targets-at-once * local.mk (ldoc_DEPS): Remove. $(dist_doc_DATA) $(dist_classes_DATA) $(dist_modules_DATA): Depend on sentinel file. ($(srcdir)/doc): Make and populate doc tree atomically for parallel make. Signed-off-by: Gary V. Vaughan --- local.mk | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/local.mk b/local.mk index a7ef078..c3176b8 100644 --- a/local.mk +++ b/local.mk @@ -148,10 +148,11 @@ dist_modules_DATA += \ $(srcdir)/doc/modules/std.table.html \ $(NOTHING_ELSE) -$(srcdir)/doc: - mkdir $@ +## Parallel make gets confused when one command ($(LDOC)) produces +## multiple targets (all the html files above), so use the presence +## of the doc directory as a sentinel file. +$(dist_doc_DATA) $(dist_classes_DATA) $(dist_modules_DATA): $(srcdir)/doc -ldoc_DEPS = $(srcdir)/doc $(dist_lua_DATA) $(dist_luastd_DATA) - -$(dist_doc_DATA) $(dist_classes_DATA) $(dist_modules_DATA): $(ldoc_DEPS) +$(srcdir)/doc: $(dist_lua_DATA) $(dist_luastd_DATA) + test -d $@ || mkdir $@ $(LDOC) -c build-aux/config.ld -d $(abs_srcdir)/doc . From 1767b72b31c30ebd1c4bf38bbc18ebef80a180f6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 6 Feb 2014 00:43:34 +1300 Subject: [PATCH 096/703] optparse: simplify default option setting. * specs/optparse_spec.yaml: Specify behaviour of new option defaults parameter. * lib/std/optparse.lua (parser): Add an optional default options parameter. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 5 ++ lib/std/optparse.lua | 10 ++- specs/optparse_spec.yaml | 141 +++++++++++++++++++++++---------------- 3 files changed, 97 insertions(+), 59 deletions(-) diff --git a/NEWS b/NEWS index 1e9c38f..6b8f8b1 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,11 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] +** New features: + + - `std.optparse:parse` accepts a second optional parameter, a table of + default option values. + ** Bug fixes: - `std.optparse` no longer throws an error when it encounters an diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 8926c0e..8ebb188 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -546,10 +546,11 @@ end --- Parse `arglist`. -- @tparam table arglist list of arguments +-- @tparam[opt] table defaults table of default option values -- @treturn table a list of unrecognised `arglist` elements -- @treturn opts parsing results -local function parse (self, arglist) - self.unrecognised = {} +local function parse (self, arglist, defaults) + self.unrecognised, self.opts = {}, {} arglist = normalise (self, arglist) @@ -575,6 +576,11 @@ local function parse (self, arglist) end end + -- Merge defaults into user options. + for k, v in pairs (defaults or {}) do + if self.opts[k] == nil then self.opts[k] = v end + end + -- metatable allows `io.warn` to find `parser.program` when assigned -- back to _G.opts. return self.unrecognised, setmetatable (self.opts, {__index = self}) diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index 0508afe..b6539bb 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -201,6 +201,90 @@ specify std.optparse: expect (parse {"-vbx -vb"}). should_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" + - context with option defaults: + - before: | + function main (arg) + local OptionParser = require "std.optparse" + local parser = OptionParser ("program 0\nUsage: program\n" .. + " -x set x\n" .. + " -y set y\n" .. + " -z set z\n") + local state = { arg = {}, opts = { x={"t"}, y=false }} + state.arg, state.opts = parser:parse (arg, state.opts) + return state + end + - it prefers supplied argument: + expect (main {"-x", "-y"}). + should_equal { arg = {}, opts = { x=true, y=true }} + expect (main {"-x", "-y", "-z"}). + should_equal { arg = {}, opts = { x=true, y=true, z=true }} + expect (main {"-w", "-x", "-y"}). + should_equal { arg = {"-w"}, opts = { x=true, y=true }} + - it defers to default value: + expect (main {}). + should_equal { arg = {}, opts = { x={"t"}, y=false }} + expect (main {"-z"}). + should_equal { arg = {}, opts = { x={"t"}, y=false, z=true }} + expect (main {"-w"}). + should_equal { arg = {"-w"}, opts = { x={"t"}, y=false }} + + - context with io.die: + - before: | + runscript = function (code) + return luaproc ([[ + local OptionParser = require "std.optparse" + local parser = OptionParser ("program 0\nUsage: program\n") + _G.arg, _G.opts = parser:parse (_G.arg) + ]] .. code .. [[ + require "std.io".die "By 'eck!" + ]]) + end + - it prefers `prog.name` to `opts.program`: | + code = [[prog = { file = "file", name = "name" }]] + expect (runscript (code)).should_fail_with "name: By 'eck!\n" + - it prefers `prog.file` to `opts.program`: | + code = [[prog = { file = "file" }]] + expect (runscript (code)).should_fail_with "file: By 'eck!\n" + - it appends `prog.line` if any to `prog.file` over using `opts`: | + code = [[ + prog = { file = "file", line = 125 }; opts.line = 99]] + expect (runscript (code)).should_fail_with "file:125: By 'eck!\n" + - it prefixes `opts.program` if any: | + expect (runscript ("")).should_fail_with "program: By 'eck!\n" + - it appends `opts.line` if any, to `opts.program`: | + code = [[opts.line = 99]] + expect (runscript (code)). + should_fail_with "program:99: By 'eck!\n" + + - context with io.warn: + - before: | + runscript = function (code) + return luaproc ([[ + local OptionParser = require "std.optparse" + local parser = OptionParser ("program 0\nUsage: program\n") + _G.arg, _G.opts = parser:parse (_G.arg) + ]] .. code .. [[ + require "std.io".warn "Ayup!" + ]]) + end + - it prefers `prog.name` to `opts.program`: | + code = [[prog = { file = "file", name = "name" }]] + expect (runscript (code)).should_output_error "name: Ayup!\n" + - it prefers `prog.file` to `opts.program`: | + code = [[prog = { file = "file" }]] + expect (runscript (code)).should_output_error "file: Ayup!\n" + - it appends `prog.line` if any to `prog.file` over using `opts`: | + code = [[ + prog = { file = "file", line = 125 }; opts.line = 99]] + expect (runscript (code)). + should_output_error "file:125: Ayup!\n" + - it prefixes `opts.program` if any: | + expect (runscript ("")).should_output_error "program: Ayup!\n" + - it appends `opts.line` if any, to `opts.program`: | + code = [[opts.line = 99]] + expect (runscript (code)). + should_output_error "program:99: Ayup!\n" + - describe parser:on: - before: | f = os.tmpname () @@ -392,60 +476,3 @@ specify std.optparse: - it stops separating at an optional argument option: expect (parseargs ([[{"x", "this"}, parser.optional]], {"-bxbit"})). should_output "opts = { b = true, this = bit }\n" - - - context with io.die: - - before: | - runscript = function (code) - return luaproc ([[ - local OptionParser = require "std.optparse" - local parser = OptionParser ("program 0\nUsage: program\n") - _G.arg, _G.opts = parser:parse (_G.arg) - ]] .. code .. [[ - require "std.io".die "By 'eck!" - ]]) - end - - it prefers `prog.name` to `opts.program`: | - code = [[prog = { file = "file", name = "name" }]] - expect (runscript (code)).should_fail_with "name: By 'eck!\n" - - it prefers `prog.file` to `opts.program`: | - code = [[prog = { file = "file" }]] - expect (runscript (code)).should_fail_with "file: By 'eck!\n" - - it appends `prog.line` if any to `prog.file` over using `opts`: | - code = [[ - prog = { file = "file", line = 125 }; opts.line = 99]] - expect (runscript (code)).should_fail_with "file:125: By 'eck!\n" - - it prefixes `opts.program` if any: | - expect (runscript ("")).should_fail_with "program: By 'eck!\n" - - it appends `opts.line` if any, to `opts.program`: | - code = [[opts.line = 99]] - expect (runscript (code)). - should_fail_with "program:99: By 'eck!\n" - - - context with io.warn: - - before: | - runscript = function (code) - return luaproc ([[ - local OptionParser = require "std.optparse" - local parser = OptionParser ("program 0\nUsage: program\n") - _G.arg, _G.opts = parser:parse (_G.arg) - ]] .. code .. [[ - require "std.io".warn "Ayup!" - ]]) - end - - it prefers `prog.name` to `opts.program`: | - code = [[prog = { file = "file", name = "name" }]] - expect (runscript (code)).should_output_error "name: Ayup!\n" - - it prefers `prog.file` to `opts.program`: | - code = [[prog = { file = "file" }]] - expect (runscript (code)).should_output_error "file: Ayup!\n" - - it appends `prog.line` if any to `prog.file` over using `opts`: | - code = [[ - prog = { file = "file", line = 125 }; opts.line = 99]] - expect (runscript (code)). - should_output_error "file:125: Ayup!\n" - - it prefixes `opts.program` if any: | - expect (runscript ("")).should_output_error "program: Ayup!\n" - - it appends `opts.line` if any, to `opts.program`: | - code = [[opts.line = 99]] - expect (runscript (code)). - should_output_error "program:99: Ayup!\n" From 7e676163ccf7f10bab2545224c378be208f33dd1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 6 Feb 2014 15:01:45 +1300 Subject: [PATCH 097/703] functional: new `case` module function. * specs/functional_spec.yaml: Add specifications for a `case` function. * lib/std/functional.lua (case): New function. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 14 ++++++++++++++ lib/std/functional.lua | 21 +++++++++++++++++++++ specs/functional_spec.yaml | 24 ++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/NEWS b/NEWS index 6b8f8b1..9a9dffd 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,20 @@ Stdlib NEWS - User visible changes ** 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. + - `std.optparse:parse` accepts a second optional parameter, a table of default option values. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index a151ab8..29efc52 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -45,6 +45,26 @@ local function bind (f, ...) end +--- A rudimentary case statement. +-- Match `with` against keys in `branches` table, and return the result +-- of running the function in the table value for the matching key, or +-- the first non-key value function if no key matches. +-- +-- return case (type (object), { +-- table = function () return something end, +-- string = function () return something else end, +-- function (s) error ("unhandled type: "..s) end, +-- }) +-- +-- @param with expression to match +-- @tparam table branches map possible matches to functions +-- @return the return value from function with a matching key, or nil. +local function case (with, branches) + local fn = branches[with] or branches[1] + if fn then return fn (with) end +end + + --- Curry a function. -- @param f function to curry -- @param n number of arguments @@ -161,6 +181,7 @@ end --- @export functional = { bind = bind, + case = case, collect = collect, compose = compose, curry = curry, diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 26b87ab..e350571 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -25,6 +25,30 @@ specify std.functional: - describe bind: +- describe case: + - before: + yes = function () return true end + no = function () return false end + default = function (s) return s end + branches = { yes = yes, no = no, default } + - it matches against branch keys: + expect (M.case ("yes", branches)).should_be (true) + expect (M.case ("no", branches)).should_be (false) + - it has a default for unmatched keys: + expect (M.case ("none", branches)).should_be "none" + - it returns nil for unmatched keys with no default: + expect (M.case ("none", { yes = yes, no = no })).should_be (nil) + - it evaluates `with` exactly once: + s = "prince" + function acc () s = s .. "s"; return s end + expect (M.case (acc (), { + prince = function () return "one" end, + princes = function () return "many" end, + princess = function () return "one" end, + function () return "gibberish" end, + })).should_be "many" + + - describe collect: From d9dcdc9747433a17e68a64ec19eea0803bb0e074 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 6 Feb 2014 21:15:20 +1300 Subject: [PATCH 098/703] package: new path management apis. * specs/package_spec.yaml: Specify behaviour of new apis. * lib/std/io.lua (pathsep): Recalculate rather than introduce a require loop with std.package. * lib/std/package.lua (find, insert, mappath, normalize, remove): New functions for maintaining clean pathstrings. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 27 +++++++ lib/std/io.lua | 4 +- lib/std/package.lua | 157 +++++++++++++++++++++++++++++++++++++++- specs/package_spec.yaml | 138 ++++++++++++++++++++++++++++++++++- 4 files changed, 322 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 9a9dffd..36db0dc 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,33 @@ Stdlib NEWS - User visible changes 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: + + 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: + + package.path = package.normalize ("./lib/?.lua", package.path) + - `std.optparse:parse` accepts a second optional parameter, a table of default option values. diff --git a/lib/std/io.lua b/lib/std/io.lua index 609fc6d..95ff22d 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -3,10 +3,12 @@ @module std.io ]] -local package = require "std.package" local string = require "std.string" local tree = require "std.tree" +local package = { + dirsep = string.match (package.config, "^([^\n]+)\n"), +} -- Get an input file handle. -- @param h file handle or name (default: `io.input ()`) diff --git a/lib/std/package.lua b/lib/std/package.lua index ab791b5..4034aa8 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -3,7 +3,161 @@ @module std.package ]] -local M = {} + +local M -- forward declaration + + +local string = require "std.string" + +local split, escape_pattern = string.split, string.escape_pattern + + +--- Look for a path segment match of `patt` in `pathstrings`. +-- @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 +local function find (pathstrings, patt, init, plain) + assert (type (pathstrings) == "string", + "bad argument #1 to find (string expected, got " .. type (pathstrings) .. ")") + assert (type (patt) == "string", + "bad argument #2 to find (string expected, got " .. type (patt) .. ")") + local paths = split (pathstrings, M.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 paths[i]:find (patt) then return i, paths[i] end + end +end + + +local case = require "std.functional".case + +--- Substitute special characters in a path string. +-- Characters prefixed with `%` have the `%` stripped, but are not +-- subject to further substitution. +-- @string path a path element with explicit `/` and `?` as necessary +-- @treturn string `path` with `dirsep` and `path_mark` substituted +-- for `/` and `?` +local function pathsub (path) + return path:gsub ("%%?.", function (capture) + return case (capture, { + ["?"] = function () return M.path_mark end, + ["/"] = function () return M.dirsep end, + function (s) return s:gsub ("^%%", "", 1) end, + }) + end) +end + + +local catfile = require "std.io".catfile +local invert = require "std.table".invert + +--- 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 +-- `path_mark` (unless immediately preceded by a `%` character). +-- @param ... path elements +-- @treturn string a single normalized `pathsep` delimited paths string +local function normalize (...) + assert (select ("#", ...) > 0, "wrong number of arguments to 'normalize'") + local i, paths, pathstrings = 1, {}, table.concat ({...}, M.pathsep) + for _, path in ipairs (split (pathstrings, M.pathsep)) do + path = pathsub (path): + gsub (catfile ("^[^", "]"), catfile (".", "%0")): + gsub (catfile ("", "%.", ""), M.dirsep): + gsub (catfile ("", "%.$"), ""): + gsub (catfile ("", "[^", "]+", "%.%.", ""), M.dirsep): + gsub (catfile ("", "[^", "]+", "%.%.$"), ""): + gsub (catfile ("%.", "%..", ""), catfile ("..", "")): + gsub (catfile ("", "$"), "") + + -- 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 table.concat (invert (paths), M.pathsep) +end + + +------ +-- 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 + +local unpack = unpack or table.unpack + +local function insert (pathstrings, ...) + assert (type (pathstrings) == "string", + "bad argument #1 to insert (string expected, got " .. type (pathstrings) .. ")") + local paths = split (pathstrings, M.pathsep) + table.insert (paths, ...) + return normalize (unpack (paths)) +end + + +------ +-- Function signature of a callback for @{mappath}. +-- @function mappath_callback +-- @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 + + +--- Call a function with each element of a path string. +-- @string pathstrings a `package.path` like string +-- @tparam mappath_callback callback function to call for each element +-- @param ... additional arguments passed to `callback` +-- @return nil, or first non-nil returned by `callback` +local function mappath (pathstrings, callback, ...) + assert (type (pathstrings) == "string", + "bad argument #1 to mappath (string expected, got " .. type (pathstrings) .. ")") + assert (type (callback) == "function", + "bad argument #2 to mappath (function expected, got " .. type (pathstrings) .. ")") + for _, path in ipairs (split (pathstrings, M.pathsep)) do + local r = callback (path, ...) + if r ~= nil then return r end + end +end + + +--- Remove any element from a `package.path` like string of paths. +-- @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 +local function remove (pathstrings, pos) + assert (type (pathstrings) == "string", + "bad argument #1 to remove (string expected, got " .. type (pathstrings) .. ")") + local paths = split (pathstrings, M.pathsep) + table.remove (paths, pos) + return table.concat (paths, M.pathsep) +end + + +--- @export +M = { + find = find, + insert = insert, + mappath = mappath, + normalize = normalize, + remove = remove, +} --- Make named constants for `package.config` -- (undocumented in 5.1; see luaconf.h for C equivalents). @@ -16,6 +170,7 @@ local M = {} M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark = string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") + for k, v in pairs (package) do M[k] = M[k] or v end diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index bf561d2..bb4d47f 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -5,11 +5,18 @@ before: | global_table = "_G" base_module = "package" - extend_base = { "dirsep", "pathsep", "path_mark", - "execdir", "igmark" } + extend_base = { "dirsep", "execdir", "find", "igmark", "insert", + "mappath", "normalize", "pathsep", "path_mark", + "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: @@ -32,6 +39,133 @@ specify std.package: should_equal {} +- describe find: + - before: path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) + - it diagnoses missing arguments: | + expect (M.find ()).should_error "bad argument #1 to find" + expect (M.find (path)).should_error "bad argument #2 to find" + - it returns nil for unmatched element: + expect (M.find (path, "unmatchable")).should_be (nil) + - it returns the element index for a matched element: + expect (M.find (path, "end")).should_be (3) + - it returns the element text for a matched element: + i, element = M.find (path, "e.*n") + expect ({i, element}).should_equal {1, "begin"} + - it accepts a search start element argument: + i, element = M.find (path, "e.*n", 2) + expect ({i, element}).should_equal {3, "end"} + - it works with plain text search strings: + expect (M.find (path, "m%ddl.")).should_be (nil) + i, element = M.find (path, "%ddl.", 1, ":plain") + expect ({i, element}).should_equal {2, "m%ddl."} + + +- describe insert: + - it diagnoses missing arguments: | + expect (M.insert ()).should_error "bad argument #1 to insert" + expect (M.insert (path)).should_error "wrong number of arguments" + - it appends by default: + expect (M.insert (path, "new")). + should_be (M.normalize ("begin", "middle", "end", "new")) + - it prepends with pos set to 1: + expect (M.insert (path, 1, "new")). + should_be (M.normalize ("new", "begin", "middle", "end")) + - it can insert in the middle too: + expect (M.insert (path, 2, "new")). + should_be (M.normalize ("begin", "new", "middle", "end")) + expect (M.insert (path, 3, "new")). + should_be (M.normalize ("begin", "middle", "new", "end")) + - it normalizes the returned path: + path = table.concat ({"begin", "middle", "end"}, M.pathsep) + expect (M.insert (path, "new")). + should_be (M.normalize ("begin", "middle", "end", "new")) + expect (M.insert (path, 1, "./x/../end")). + should_be (M.normalize ("end", "begin", "middle")) + + +- describe mappath: + - before: + expected = require "std.string".split (path, M.pathsep) + - it diagnoses bad arguments: | + expect (M.mappath ()).should_error "bad argument #1 to mappath" + expect (M.mappath ("")).should_error "bad argument #2 to mappath" + - it calls a function with each path element: + t = {} + M.mappath (path, function (e) t[#t + 1] = e end) + expect (t).should_equal (expected) + - it passes additional arguments through: | + reversed = {} + for i = #expected, 1, -1 do + table.insert (reversed, expected[i]) + end + t = {} + M.mappath (path, function (e, pos) table.insert (t, pos, e) end, 1) + expect (t).should_equal (reversed) + + +- describe normalize: + - it diagnoses bad arguments: + expect (M.normalize ()).should_error "wrong number of arguments" + + - context with a single element: + - it strips redundant . directories: + expect (M.normalize "./x/./y/.").should_be (catfile (".", "x", "y")) + - it strips redundant .. directories: + expect (M.normalize "../x/../y/z/..").should_be (catfile ("..", "y")) + - it normalizes / to platform dirsep: + expect (M.normalize "/foo/bar").should_be (catfile ("", "foo", "bar")) + - it normalizes ? to platform path_mark: + expect (M.normalize "?.lua"). + should_be (catfile (".", M.path_mark .. ".lua")) + - it strips redundant trailing /: + expect (M.normalize "/foo/bar/").should_be (catfile ("", "foo", "bar")) + - it inserts missing ./ for relative paths: + for _, path in ipairs {"x", "./x"} do + expect (M.normalize (path)).should_be (catfile (".", "x")) + end + + - context with multiple elements: + - it strips redundant . directories: + expect (M.normalize ("./x/./y/.", "x")). + should_be (catpath (catfile (".", "x", "y"), catfile (".", "x"))) + - it strips redundant .. directories: + expect (M.normalize ("../x/../y/z/..", "x")). + should_be (catpath (catfile ("..", "y"), catfile (".", "x"))) + - it normalizes / to platform dirsep: + expect (M.normalize ("/foo/bar", "x")). + should_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) + - it normalizes ? to platform path_mark: + expect (M.normalize ("?.lua", "x")). + should_be (catpath (catfile (".", M.path_mark .. ".lua"), catfile (".", "x"))) + - it strips redundant trailing /: + expect (M.normalize ("/foo/bar/", "x")). + should_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) + - it inserts missing ./ for relative paths: + for _, path in ipairs {"x", "./x"} do + expect (M.normalize (path, "a")). + should_be (catpath (catfile (".", "x"), catfile (".", "a"))) + end + + - it eliminates all but the first equivalent elements: + expect (M.normalize (catpath ("1", "x", "2", "./x", "./2", "./x/../x"))). + should_be (catpath ("./1", "./x", "./2")) + + +- describe remove: + - it diagnoses bad arguments: | + expect (M.remove ()).should_error "bad argument #1 to remove" + - it removes the last item by default: + expect (M.remove (path)).should_be (M.normalize ("begin", "middle")) + - it pops the first item with pos set to 1: + expect (M.remove (path, 1)).should_be (M.normalize ("middle", "end")) + - it can remove from the middle too: + expect (M.remove (path, 2)).should_be (M.normalize ("begin", "end")) + - it does not normalize the returned path: + path = table.concat ({"begin", "middle", "end"}, M.pathsep) + expect (M.remove (path)). + should_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.path_mark, M.execdir, M.igmark) From 64259a15a79e47662be991179a66f54b3c39b38b Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 14 Apr 2014 22:37:43 +0100 Subject: [PATCH 099/703] Fix documentation of table.clone_rename: the parameters got swapped during the earlier re-org --- lib/std/table.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index f4261b5..c644a27 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -19,8 +19,8 @@ local clone = base.clone --- Clone a table, renaming some keys. -- @function clone_rename --- @tparam table t source table -- @tparam table map table `{old_key=new_key, ...}` +-- @tparam table t source table -- @return copy of *table* local clone_rename = base.clone_rename From 9c44d3584768a10c6aa7752d07b5de0a350ea2ab Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 14 Apr 2014 23:18:04 +0100 Subject: [PATCH 100/703] Fix a comment typo --- lib/std/container.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 64cd413..e5e1149 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -86,7 +86,7 @@ end --- Return `obj` with references to the fields of `src` merged in. -- @static -- @tparam table obj destination object --- @tparam table src fields to copy int clone +-- @tparam table src fields to copy into clone -- @tparam[opt={}] table map `{old_key=new_key, ...}` -- @treturn table `obj` with non-private fields from `src` merged, and -- a metatable with private fields (if any) merged, both sets of keys From f181ac9d70bbe03028b864fed4b745a4c229ef66 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 14 Apr 2014 20:23:59 -0700 Subject: [PATCH 101/703] specs: fix a typo; there is no 'a' in should_output. * specs/optparse_spec.yaml: Fix a typo. Signed-off-by: Gary V. Vaughan --- specs/optparse_spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index b6539bb..816b0a3 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -176,7 +176,7 @@ specify std.optparse: expect (parse {"-vuname"}). should_output "opts = { name = name, verbose = true }\n" expect (parse {"-vuob"}). - shauld_output "opts = { name = ob, verbose = true }\n" + should_output "opts = { name = ob, verbose = true }\n" - it stops separating at an optional argument option: expect (parse {"-vofilename"}). should_output "opts = { output = filename, verbose = true }\n" From 8450ce7fd1c615896ccd078d9cc255cb59dda39e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 14 Apr 2014 21:10:47 -0700 Subject: [PATCH 102/703] specs: update custom matchers for Specl 11. * bootstrap.conf (buildreq): Bump specl minimum version to 11. * specs/spec_helper.lua (matchers.have_size, matchers.have_member): Matcher object functions require an initial `self` parameter. (matchers.fail_with): Remove. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 2 +- specs/spec_helper.lua.in | 45 ++++++---------------------------------- 2 files changed, 7 insertions(+), 40 deletions(-) diff --git a/bootstrap.conf b/bootstrap.conf index 90eff27..498a071 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -34,7 +34,7 @@ buildreq=' git 1.5.5 http://git-scm.com ldoc 1.4.0 http://luarocks.org/repositories/rocks/ldoc-1.4.2-1.rockspec - specl 8 http://luarocks.org/repositories/rocks/specl-10-1.rockspec + specl 11 http://luarocks.org/repositories/rocks/specl-11-1.rockspec ' # List of slingshot files to link into stdlib tree before autotooling. diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index bb0a7c0..4c120f9 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -134,7 +134,7 @@ do matchers.Matcher, matchers.matchers, matchers.stringify matchers.have_size = Matcher { - function (actual, expect) + function (self, actual, expect) local size = 0 for _ in pairs (actual) do size = size + 1 end return size == expect @@ -142,63 +142,30 @@ do actual = "table", - format_expect = function (expect) + format_expect = function (self, expect) return " a table containing " .. expect .. " elements, " end, - format_any_of = function (alternatives) + format_any_of = function (self, alternatives) return " a table with any of " .. util.concat (alternatives, util.QUOTED) .. " elements, " end, } matchers.have_member = Matcher { - function (actual, expect) + function (self, actual, expect) return set.member (actual, expect) end, actual = "set", - format_expect = function (expect) + format_expect = function (self, expect) return " a set containing " .. q (expect) .. ", " end, - format_any_of = function (alternatives) + format_any_of = function (self, alternatives) return " a set containing any of " .. util.concat (alternatives, util.QUOTED) .. ", " end, } end - - -do - --[[ ======================================= ]]-- - --[[ Remove this after Specl 11 is released. ]]-- - --[[ ======================================= ]]-- - - -- Custom matcher for specl.shell failure with error message. - - local matchers = require "specl.matchers" - local Matcher, matchers, reformat = - matchers.Matcher, matchers.matchers, matchers.reformat - - matchers.fail_with = matchers.fail_with or Matcher { - function (actual, expect) - return (actual.status ~= 0) and (actual.errout == expect) - end, - - actual_type = "Process", - - format_actual = function (process) - return ":" .. reformat (process.errout) - end, - - format_expect = function (expect) - return " error output: " .. reformat (expect) - end, - - format_alternatives = function (adaptor, alternatives) - return " error output:" .. reformat (alternatives, adaptor) - end, - } -end From d4fdc420643675539d44a0c1a8d31fef62ea5cc5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 14 Apr 2014 21:18:06 -0700 Subject: [PATCH 103/703] slingshot: sync with upstream. * slingshot: Sync with latest upstream master. * bootstrap: Update from slingshot. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 10 ++++++---- bootstrap | 2 +- slingshot | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4a5973e..30c24f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ env: - PACKAGE=stdlib - ROCKSPEC=$PACKAGE-git-1.rockspec - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - - LUAROCKS_BASE=luarocks-2.1.1 + - LUAROCKS_BASE=luarocks-2.1.2 - LUAROCKS="$LUA $HOME/bin/luarocks" matrix: - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 LUA_SUFFIX=5.1 @@ -29,11 +29,13 @@ install: # Install a recent luarocks release locally for everything else. - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz - tar zxvpf $LUAROCKS_BASE.tar.gz + # LuaRocks configure --with-lua argument is just a prefix! - ( cd $LUAROCKS_BASE; ./configure - --prefix=$HOME --lua-version=$LUA_SUFFIX --lua-suffix=$LUA_SUFFIX - --with-lua-include=$LUA_INCDIR; - make all install; ) + --prefix=$HOME --with-lua=/usr --lua-version=$LUA_SUFFIX + --lua-suffix=$LUA_SUFFIX --with-lua-include=$LUA_INCDIR; + make build; + make install; ) # Configure and build. script: diff --git a/bootstrap b/bootstrap index 82dcf5c..575ce48 100755 --- a/bootstrap +++ b/bootstrap @@ -2163,7 +2163,7 @@ Slingshot Options: --luarocks-tree=DIR check a non-default tree for prerequisite rocks --skip-rock-checks - ignore Lua rocks in bootstrap.conf:buidreq' + ignore Lua rocks in bootstrap.conf:buildreq' func_quote_for_eval ${1+"$@"} slingshot_options_prep_result=$func_quote_for_eval_result diff --git a/slingshot b/slingshot index 65a3777..4a4ad2f 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 65a3777d86204e5990a95894fa010afd069538be +Subproject commit 4a4ad2f1a7ac754983d3df6a01f146620f260ecf From e49713972a46985c392dd6bdfe57de2340843d1d Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Tue, 15 Apr 2014 19:51:41 +0100 Subject: [PATCH 104/703] Fix a comment typo --- lib/std/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/io.lua b/lib/std/io.lua index 95ff22d..46f0db2 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -49,7 +49,7 @@ local function readlines (h) end --- Write values adding a newline after each. --- @param h file handle (default: `io.output ()` +-- @param h file handle (default: `io.output ()`) -- @param ... values to write (as for write) local function writelines (h, ...) if io.type (h) ~= "file" then From f65494faf19c07baf297fb28b2b2b7fe5c73534f Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Tue, 15 Apr 2014 22:35:54 +0100 Subject: [PATCH 105/703] Fix a comment typo --- lib/std/set.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/set.lua b/lib/std/set.lua index c3911d1..94a13a1 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -3,8 +3,8 @@ Derived from @{std.container}, and inherits Container's metamethods. - Note that Functions listed below are available only available from the - Set prototype returned by requiring this module, because Container + Note that Functions listed below are only available from the Set + prototype returned by requiring this module, because Container objects cannot have object methods. @classmod std.set From 10a12b37f3829896bf120788e4e5fa8084e6ad75 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 16 Apr 2014 01:14:18 -0700 Subject: [PATCH 106/703] specs: set package.path for in tree specl calls. Specl 11 works well when called directly in a project root dir, eg. `specl -freport -v1`, but only if the package path is set appropriately in each `spec_helper.lua` or similar. * specs/spec_helper.lua.in: Use `package.normalize` to safely inject ./lib/?.lua into the head of package.path. Signed-off-by: Gary V. Vaughan --- specs/spec_helper.lua.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 4c120f9..6dc1b18 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -1,4 +1,7 @@ local hell = require "specl.shell" +local std = require "specl.std" + +package.path = std.package.normalize ("lib/?.lua", package.path) -- Substitute configured LUA so that hell.spawn doesn't pick up -- a different Lua binary to the one used by Specl itself. If From 0f17e8d70d0fa8e7b03d67c56eeb383d9c1b9f6c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 16 Apr 2014 01:22:22 -0700 Subject: [PATCH 107/703] maint: remove trailing whitespace in NEWS. * NEWS: Remove trailing whitespace. Signed-off-by: Gary V. Vaughan --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 36db0dc..6c70fcd 100644 --- a/NEWS +++ b/NEWS @@ -42,7 +42,7 @@ Stdlib NEWS - User visible changes package.path = table.concat (paths, package.pathsep) You can now write just: - + package.path = package.normalize ("./lib/?.lua", package.path) - `std.optparse:parse` accepts a second optional parameter, a table of From e0a69c63821e0ba38c9ff81ebe60e7aa682cd584 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 16 Apr 2014 01:18:25 -0700 Subject: [PATCH 108/703] base: provide a deprecation mechanism for gentle API upgrades. * specs/base_spec.yaml: New file. Specify behaviour for a deprecation wrapper. * specs/specs.mk (specl_SPECS): Add base_spec.yaml. * lib/std/base.lua (deprecate): New function. Implement the specification. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 23 +++++++++++++++++++++ specs/base_spec.yaml | 49 ++++++++++++++++++++++++++++++++++++++++++++ specs/specs.mk | 1 + 3 files changed, 73 insertions(+) create mode 100644 specs/base_spec.yaml diff --git a/lib/std/base.lua b/lib/std/base.lua index 0ac5aad..ce2508a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -1,6 +1,28 @@ ------ -- @module std.base + +--- Write a deprecation warning to stderr on first call. +-- @func fn deprecated function +-- @string[opt] name function name for automatic warning message. +-- @string[opt] warnmsg full specified warning message (overrides *name*) +-- @return a function to show the warning on first call, and hand off to *fn* +local function deprecate (fn, name, warnmsg) + assert (name or warnmsg, + "missing argument to 'deprecate', expecting 2 or 3 parameters") + warnmsg = warnmsg or (name .. " is deprecated, and will go away in a future release.") + local warnp = true + return function (...) + if warnp then + local _, where = pcall (function () error ("", 4) end) + io.stderr:write ((string.gsub (where, "(^w%*%.%w*%:%d+)", "%1"))) + io.stderr:write (warnmsg .. "\n") + warnp = false + end + return fn (...) + end +end + -- Doc-commented in table.lua... local function clone (t, nometa) local u = {} @@ -146,6 +168,7 @@ local M = { clone_rename = clone_rename, compare = compare, concat = concat, + deprecate = deprecate, elems = elems, ileaves = ileaves, leaves = leaves, diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml new file mode 100644 index 0000000..cae43d1 --- /dev/null +++ b/specs/base_spec.yaml @@ -0,0 +1,49 @@ +before: + base = require "std.base" + +specify std.base: +- describe deprecate: + - before: | + deprecate = base.deprecate + + function runscript (body, name, args) + return luaproc ( + "require 'std.base'.deprecate (function (...)" .. + " " .. body .. + " end, '" .. (name or "runscript") .. "') " .. + "('" .. table.concat (args or {}, "', '") .. "')" + ) + end + - it diagnoses missing arguments: + expect (deprecate ()).to_error "missing argument" + - it returns a function: + f = deprecate (base.clone_rename, "clone_rename") + expect (type (f)).to_be "function" + - context with deprecated function: + - it executes the deprecated function: + expect (runscript 'error "oh noes!"').to_contain_error "oh noes!" + - it passes arguments to the deprecated function: + expect (runscript ("print (table.concat ({...}, ', '))", nil, + {"foo", "bar", "baz"})).to_output "foo, bar, baz\n" + - it returns deprecated function results: + script = [[ + deprecate = require "std.base".deprecate + fn = deprecate (function () return "foo", "bar", "baz" end, "fn") + print (fn ()) + ]] + expect (luaproc (script)).to_output "foo\tbar\tbaz\n" + - it writes a warning to stderr: + expect (runscript 'error "oh noes!"'). + to_contain_error "deprecated, and will go away" + - it writes the call location to stderr: | + expect (runscript 'error "oh noes!"'). + to_match_error "^%S+:1: " + - it does not repeat the warning on additional calls: + script = [[ + deprecate = require "std.base".deprecate + fn = deprecate (function () error "oh noes!" end, "fn") + fn () -- line 3 + fn () -- line 4 + ]] + expect (luaproc (script)). + not_to_match_error "^%S+:3:.*deprecated.*\n%S+:4:.*deprecated" diff --git a/specs/specs.mk b/specs/specs.mk index 45c1fcc..4e881c4 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -21,6 +21,7 @@ SPECL_OPTS = --unicode ## affected. specl_SPECS = \ + $(srcdir)/specs/base_spec.yaml \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ $(srcdir)/specs/functional_spec.yaml \ From 777061fe416a1656f29b5e45292a60120b855388 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 16 Apr 2014 02:29:21 -0700 Subject: [PATCH 109/703] table: fold `clone_rename` into `clone`. * specs/table_spec.yaml (std.table): Specify new behaviour with optional `map` argument. * lib/std/base.lua (clone): Implement new behaviours. * lib/std/table.lua (clone_rename): Add deprecation warning, and move out of the LDoc export table. (clone): Update doc-comments. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 14 ++++++++++++++ lib/std/base.lua | 12 +++++++++--- lib/std/table.lua | 16 +++++++++++----- specs/table_spec.yaml | 18 ++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index 6c70fcd..e93762a 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,20 @@ Stdlib NEWS - User visible changes - `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: + + 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 diff --git a/lib/std/base.lua b/lib/std/base.lua index ce2508a..79bbe7f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,13 +24,19 @@ local function deprecate (fn, name, warnmsg) end -- Doc-commented in table.lua... -local function clone (t, nometa) +local function clone (t, map, nometa) + map = map or {} + if type (map) ~= "table" then + -- continue to support `clone (t, "nometa")` + map, nometa = {}, map + end + local u = {} if not nometa then setmetatable (u, getmetatable (t)) end - for i, v in pairs (t) do - u[i] = v + for k, v in pairs (t) do + u[map[k] or k] = v end return u end diff --git a/lib/std/table.lua b/lib/std/table.lua index c644a27..2d07835 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,18 +11,22 @@ local func = require "std.functional" -- -- To make deep copies, use @{std.tree.clone}. -- @function clone --- @tparam table t source table +-- @tparam table t source table +-- @tparam[opt={}] table map table of `{old_key=new_key, ...}` -- @tparam boolean nometa if non-nil don't copy metatable --- @return copy of *table* +-- @return copy of *t*, also sharing *t*'s metatable unless *nometa* +-- is `true`, and with keys renamed according to *map* local clone = base.clone ---- Clone a table, renaming some keys. +-- DEPRECATED: Remove in first release following 2015-04-15. +-- Clone a table, renaming some keys. -- @function clone_rename -- @tparam table map table `{old_key=new_key, ...}` -- @tparam table t source table -- @return copy of *table* -local clone_rename = base.clone_rename +local clone_rename = base.deprecate (base.clone_rename, nil, + "table.clone_rename is deprecated, use the new `map` argument to table.clone instead.") --- Destructively merge another table's fields into *table*. @@ -160,7 +164,6 @@ end --- @export local Table = { clone = clone, - clone_rename = clone_rename, empty = empty, invert = invert, keys = keys, @@ -177,6 +180,9 @@ local Table = { _sort = _sort, } +-- Deprecated and undocumented. +Table.clone_rename = clone_rename + for k, v in pairs (table) do Table[k] = Table[k] or v end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index d33cbf9..e28b7a6 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -70,6 +70,24 @@ specify std.table: copy = f (subject) expect (subject).should_equal (target) expect (subject).should_be (subject) + - it treats non-table arg2 as nometa parameter: + mt = setmetatable (f (subject, true), {}) + expect (getmetatable (f (mt, true))).should_be (nil) + - it treats table arg2 as a map parameter: + mt = setmetatable (f (subject, true), {}) + expect (getmetatable (f (mt, {}))).should_be (getmetatable (mt)) + - it supports 3 arguments with nometa as arg3: + mt = setmetatable (f (subject, true), {}) + expect (getmetatable (f (mt, {}, "nometa"))).should_be (nil) + + - context when renaming some keys: + - before: + target = { newkey = subject.k1, k2 = subject.k2, k3 = subject.k3 } + - it renames during cloning: + expect (f (subject, {k1 = "newkey"})).should_equal (target) + - it does not perturb the value in the renamed key field: + expect (f (subject, {k1 = "newkey"}).newkey).should_be (subject.k1) + - "it diagnoses non-table arguments": expect (f ()).should_error ("table expected") expect (f "foo").should_error ("table expected") From e24b471fc6077bf270b21b05d752094818ecc965 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Apr 2014 16:55:07 +0700 Subject: [PATCH 110/703] functional: handle false valued elements correctly in `map`. Fix #34. * lib/std/functional.lua (map): Also insert `false` values into the results table. * specs/list_spec.yaml (std.list): Remove pending call from newly passing example. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 2 +- specs/list_spec.yaml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 29efc52..5f9c7c5 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -142,7 +142,7 @@ local function map (f, i, ...) local t = {} for e in i (...) do local r = f (e) - if r then + if r ~= nil then table.insert (t, r) end end diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 523b058..4bdac86 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -344,8 +344,7 @@ specify std.list: - it projects a list of fields from a list of tables: expect (l:project ("third")). should_equal (List {true, 3, "3rd"}) - - it projects fields with a falsey value correctly: | - pending "see issue #34" + - it projects fields with a falsey value correctly: expect (l:project ("first")). should_equal (List {false, 1, "1st"}) From d3a03941a94cb7c4017134c90492c6edd0070ab8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Apr 2014 17:06:22 +0700 Subject: [PATCH 111/703] tree: always return nil for non-existent key path. Fix #39. * lib/std/functional.lua (function.op["[]"]): Return nil instead of dereferencing a nil valued table argument. * specs/tree_spec.yaml (std.tree): Remove pending call prior to newly passing example. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 2 +- specs/tree_spec.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 5f9c7c5..f7fabee 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -208,7 +208,7 @@ functional = { -- @field == equality -- @field ~= inequality functional.op = { - ["[]"] = function (t, s) return t[s] end, + ["[]"] = function (t, s) return t and t[s] or nil end, ["+"] = function (a, b) return a + b end, ["-"] = function (a, b) return a - b end, ["*"] = function (a, b) return a * b end, diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index ca0c8b7..0babfb8 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -371,7 +371,6 @@ specify std.tree: expect (tr[{"no such key"}]).should_be (nil) - it returns nil for missing multi-element key lists: | expect (tr[{"fnord", "foo"}]).should_be (nil) - pending "see issue #39" expect (tr[{"no", "such", "key"}]).should_be (nil) - it returns a value for the given key: expect (tr["foo"]).should_be "foo" From 8082b0d224cbe5596c312924fd46a6b66a339ac6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Apr 2014 18:13:13 +0700 Subject: [PATCH 112/703] container: discard unmapped positional parameters. Fix #35. * lib/std/container.lua (mapfields): When `map` argument is given, discard unmapped positional parameters. (metatable._init): Remove to imply a `nil` value (copy all fields), because `{}` now discards all positional parameters. (metatable.__call): Reverse type check of `_init`, so that mapfields is still called when _init is `nil`. * specs/object_spec.yaml (std.object): Remove pending call from newly passing examples. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 10 ++++++++++ lib/std/container.lua | 28 ++++++++++++++++++---------- specs/object_spec.yaml | 4 +--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index e93762a..6fef1cb 100644 --- a/NEWS +++ b/NEWS @@ -67,6 +67,11 @@ Stdlib NEWS - User visible changes - `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. + ** Incompatible changes: - `std.optparse` no longer normalizes unhandled options. For example, @@ -77,6 +82,11 @@ Stdlib NEWS - User visible changes 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] diff --git a/lib/std/container.lua b/lib/std/container.lua index e5e1149..97dce77 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -93,16 +93,25 @@ end -- renamed according to `map` -- @see std.object.mapfields local function mapfields (obj, src, map) - map = map or {} local mt = getmetatable (obj) or {} -- Map key pairs. - for k, v in pairs (src) do - local key, dst = map[k] or k, obj - if type (key) == "string" and key:sub (1, 1) == "_" then - dst = mt + -- Copy all pairs when `map == nil`, but discard unmapped src keys + -- when map is provided (i.e. if `map == {}`, copy nothing). + if map == nil or next (map) then + map = map or {} + for k, v in pairs (src) do + local key, dst = map[k] or k, obj + local kind = type (key) + if kind == "string" and key:sub (1, 1) == "_" then + dst = mt + elseif kind == "number" and #dst + 1 < key then + -- When map is given, but has fewer entries than src, stop copying + -- fields when map is exhausted. + break + end + dst[key] = v end - dst[key] = v end -- Quicker to remove this after copying fields than test for it @@ -142,7 +151,6 @@ end -- by @{std.object.__call} local metatable = { _type = "Container", - _init = {}, --- Return a clone of this container. -- @function __call @@ -166,10 +174,10 @@ local metatable = { end end - if type (mt._init) == "table" then - obj = (self.mapfields or mapfields) (obj, x, mt._init) - else + if type (mt._init) == "function" then obj = mt._init (obj, x, ...) + else + obj = (self.mapfields or mapfields) (obj, x, mt._init) end -- If a metatable was set, then merge our fields and use it. diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index c0102f3..7f9e74b 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -117,7 +117,6 @@ specify std.object: - it ignores positional parameters: | instance = Prototype {"foo", "bar"} expect (instance).should_not_be (Prototype) - pending "see issue #35" expect (instance).should_equal (Prototype) - context when _init is a table of field names: @@ -140,9 +139,8 @@ specify std.object: proc = Process {0, "output"} expect (totable (proc)). should_equal {status = 0, output = "output", errout = "no errors"} - - it merges surplus positional parameters into array part of new object: | + - it discards surplus positional parameters: proc = Process {0, "output", "diagnostics", "garbage"} - pending "see issue #35" expect (totable (proc)). should_equal { status = 0, output = "output", errout = "diagnostics" } From 42f96a8c9a65f1e131ba109bd5bd344f896c311d Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 21 Apr 2014 12:45:15 +0100 Subject: [PATCH 113/703] =?UTF-8?q?Remove=20comment=20that=20gives=20a=20f?= =?UTF-8?q?alse=20impression=20that=20the=20behaviour=20is=20there=20to=20?= =?UTF-8?q?support=20backwards-compatibility,=20whereas=20in=20fact=20it?= =?UTF-8?q?=E2=80=99s=20necessary=20for=20the=20current=20API.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/std/base.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 79bbe7f..c56d290 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -27,7 +27,6 @@ end local function clone (t, map, nometa) map = map or {} if type (map) ~= "table" then - -- continue to support `clone (t, "nometa")` map, nometa = {}, map end From f2fa5b08d0588bfce5fb9a337f59d4cd056de030 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 21 Apr 2014 12:46:02 +0100 Subject: [PATCH 114/703] =?UTF-8?q?Don=E2=80=99t=20imply=20that=20clone=20?= =?UTF-8?q?requires=20its=20nometa=20argument=20to=20be=20the=20literal=20?= =?UTF-8?q?`true`,=20thereby=20blessing=20our=20use=20of=20string=20"nomet?= =?UTF-8?q?a"=20argument.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/std/table.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index 2d07835..28a5f1b 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -15,7 +15,7 @@ local func = require "std.functional" -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` -- @tparam boolean 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* +-- is true, and with keys renamed according to *map* local clone = base.clone From c683256b1612217455a6d2c0bd3d635b10d0376f Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 21 Apr 2014 12:46:29 +0100 Subject: [PATCH 115/703] Add clone_select, closing #50. --- lib/std/table.lua | 28 ++++++++++++++++++++++++++++ specs/table_spec.yaml | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index 28a5f1b..91da113 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -29,6 +29,33 @@ local clone_rename = base.deprecate (base.clone_rename, nil, "table.clone_rename is deprecated, use the new `map` argument to table.clone instead.") +--- 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 selection list of keys to copy +-- @return copy of fields in *selection* from *t*, also sharing *t*'s +-- metatable unless *nometa* +local function clone_select (t, map, nometa) + assert (type (t) == "table", + "bad argument #1 to 'clone_select' (table expected, got " .. type (t) .. ")") + map = map or {} + if type (map) ~= "table" then + map, nometa = {}, map + end + + local r = {} + if not nometa then + setmetatable (r, getmetatable (t)) + end + for i in list.elems (map) do + r[i] = t[i] + end + return r +end + + --- Destructively merge another table's fields into *table*. -- @function merge -- @tparam table t destination table @@ -164,6 +191,7 @@ end --- @export local Table = { clone = clone, + clone_select = clone_select, empty = empty, invert = invert, keys = keys, diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index e28b7a6..f5d43b4 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -6,7 +6,7 @@ before: | global_table = "_G" std_globals = { "pack", "ripairs", "totable" } - extend_base = { "clone", "clone_rename", "empty", + extend_base = { "clone", "clone_select", "clone_rename", "empty", "invert", "keys", "merge", "new", "ripairs", "size", "totable", "values", -- make these available after require "std" @@ -93,6 +93,38 @@ specify std.table: expect (f "foo").should_error ("table expected") +- describe clone_select: + - before: + subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } + f = M.clone_select + - it does not just return the subject: + expect (f (subject)).should_not_be (subject) + - it copies the keys selected: + expect (f (subject, {"k1", "k2"})).should_equal ({ k1 = {"v1"}, k2 = {"v2"} }) + - it does copy the subject when supplied with a full list of keys: + expect (f (subject, {"k1", "k2", "k3"})).should_equal (subject) + - it only makes a shallow copy: + expect (f (subject, {"k1"}).k1).should_be (subject.k1) + - the original subject is not perturbed: + target = { k1 = subject.k1, k2 = subject.k2, k3 = subject.k3 } + copy = f (subject, {"k1", "k2", "k3"}) + expect (subject).should_equal (target) + expect (subject).should_be (subject) + - it treats non-table arg2 as nometa parameter: + mt = setmetatable (f (subject, true), {}) + expect (getmetatable (f (mt, true))).should_be (nil) + - it treats table arg2 as a map parameter: + mt = setmetatable (f (subject, true), {}) + expect (getmetatable (f (mt, {}))).should_be (getmetatable (mt)) + - it supports 3 arguments with nometa as arg3: + mt = setmetatable (f (subject, true), {}) + expect (getmetatable (f (mt, {}, "nometa"))).should_be (nil) + + - "it diagnoses non-table arguments": + expect (f ()).should_error ("table expected") + expect (f "foo").should_error ("table expected") + + - describe clone_rename: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } From e46cea399acefdee67818f26042d79cd33d18c20 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 21 Apr 2014 12:54:20 +0100 Subject: [PATCH 116/703] Add a missing require to table.lua --- lib/std/table.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/table.lua b/lib/std/table.lua index 91da113..75a0269 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -5,6 +5,7 @@ local base = require "std.base" local func = require "std.functional" +local list = require "std.list" --- Make a shallow copy of a table, including any metatable. From aba9a3aaf414b3b216719cef04791d955d7b40d1 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 21 Apr 2014 13:24:09 +0100 Subject: [PATCH 117/703] Modify API of functional.bind, and add spec; closes #48 --- lib/std/functional.lua | 15 +++++++++++---- specs/functional_spec.yaml | 9 +++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index f7fabee..e52a349 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -35,12 +35,19 @@ end --- Partially apply a function. -- @param f function to apply partially --- @param ... arguments to bind --- @return function with ai already bound +-- @tparam table {p1=a1, ..., pn=an} table of parameters to bind to given arguments +-- @return function with pi already bound local function bind (f, ...) - local fix = {...} + local fix = {...} -- backwards compatibility with old API; DEPRECATED: remove in first release after 2015-04-21 + if type (fix[1]) == "table" and fix[2] == nil then + fix = fix[1] + end return function (...) - return f (unpack (list.concat (fix, {...}))) + local arg = {...} + for i, v in pairs (fix) do + arg[i] = v + end + return f (unpack (arg)) end end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index e350571..afb5e5c 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -23,6 +23,15 @@ specify std.functional: - describe bind: + - it does not affect normal operation if no arguments are bound: + expect (M.bind (math.min, {}) (2, 3, 4)). + should_equal (2) + - the extra arguments are taken into account: + expect (M.bind (math.min, {1, 0}) (2, 3, 4)). + should_equal (0) + - the extra arguments can be out of order: + expect (M.bind (math.pow, {[2] = 3}) (2)). + should_equal (8) - describe case: From b171cbea3c803e5199ecd02f2673efb0e096cfbb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Apr 2014 19:24:38 +0700 Subject: [PATCH 118/703] table: no need to pull all of std.list into memory. * lib/std/table (elems): Capture base.elems in a local. (clone_select): Use it. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index 75a0269..3a6aefb 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -5,7 +5,9 @@ local base = require "std.base" local func = require "std.functional" -local list = require "std.list" + +-- No need to pull all of std.list into memory. +local elems = base.elems --- Make a shallow copy of a table, including any metatable. @@ -50,7 +52,7 @@ local function clone_select (t, map, nometa) if not nometa then setmetatable (r, getmetatable (t)) end - for i in list.elems (map) do + for i in elems (map) do r[i] = t[i] end return r From 40260855677b4d8de581a757ead944e46a6fb859 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Apr 2014 19:09:31 +0700 Subject: [PATCH 119/703] tree: return tree root from tree[{}]. Fix #41. * specs/tree_spec.yaml (returns tree root for an empty list): New specification. * lib/std/tree.lua (__index): Allow empty list as a valid key. * specs/tree_spec.yaml (std.tree): Remove pending call for newly passing examples. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 6 ++++++ lib/std/tree.lua | 2 +- specs/tree_spec.yaml | 5 +++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 6fef1cb..19a640f 100644 --- a/NEWS +++ b/NEWS @@ -72,6 +72,12 @@ Stdlib NEWS - User visible changes 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.optparse` no longer normalizes unhandled options. For example, diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 643fe49..a809fc7 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -207,7 +207,7 @@ Tree = Container { -- @todo the following doesn't treat list keys correctly -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? __index = function (self, i) - if type (i) == "table" and #i > 0 then + if type (i) == "table" then return List.foldl (func.op["[]"], self, i) else return rawget (self, i) diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 0babfb8..9c72190 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -349,7 +349,6 @@ specify std.tree: {"leaf", {"foo"}, subject["foo"]}, -- bar, {"join", {}, subject}} -- } - it generates path key-lists that are valid __index arguments: | - pending "std.tree.__index handling empty list keys" subject = Tree {"first", Tree {"second"}, "3rd"} expect (traverse (subject)). should_equal {{"branch", {}, subject[{}]}, -- { @@ -369,12 +368,14 @@ specify std.tree: expect (tr["no such key"]).should_be (nil) - it returns nil for missing single element key lists: expect (tr[{"no such key"}]).should_be (nil) - - it returns nil for missing multi-element key lists: | + - it returns nil for missing multi-element key lists: expect (tr[{"fnord", "foo"}]).should_be (nil) expect (tr[{"no", "such", "key"}]).should_be (nil) - it returns a value for the given key: expect (tr["foo"]).should_be "foo" expect (tr["quux"]).should_be "quux" + - it returns tree root for empty key list: + expect (tr[{}]).should_be (tr) - it returns values for single element key lists: expect (tr[{"foo"}]).should_be "foo" expect (tr[{"quux"}]).should_be "quux" From a4da548eaa6959a5cd605cff1a03526db8241201 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 21 Apr 2014 13:45:59 +0100 Subject: [PATCH 120/703] Reverse order of arguments to functional.compose; closes #52 --- lib/std/functional.lua | 10 ++++++++-- lib/std/list.lua | 2 +- specs/functional_spec.yaml | 3 +++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index e52a349..2be45b9 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -89,13 +89,19 @@ end --- Compose functions. -- @param f1...fn functions to compose --- @return composition of f1 ... fn +-- @return composition of fn (... (f1) ...): note that this is the reverse +-- of what you might expect, but means that code like: +-- +-- functional.compose (function (x) return f (x) end, +-- function (x) return g (x) end)) +-- +-- can be read from top to bottom. local function compose (...) local arg = {...} local fns, n = arg, #arg return function (...) local arg = {...} - for i = n, 1, -1 do + for i = 1, n do arg = {fns[i] (unpack (arg))} end return unpack (arg) diff --git a/lib/std/list.lua b/lib/std/list.lua index 1343277..1a5347e 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -228,7 +228,7 @@ end -- @tparam List ls a list of lists -- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` local function map_with (fn, ls) - return List (func.map (func.compose (fn, unpack), elems, ls)) + return List (func.map (func.compose (unpack, fn), elems, ls)) end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index afb5e5c..1bb7aa6 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -62,6 +62,9 @@ specify std.functional: - describe compose: + - it composes functions in the correct order: + expect (M.compose (math.sin, math.cos) (1)). + should_equal (math.cos (math.sin (1))) - describe curry: From 9266883a426b9ffc1535e3742f3d96968d1f80bd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Apr 2014 20:25:32 +0700 Subject: [PATCH 121/703] specs: ignore local LUA_INIT and LUA_INIT_5_2 settings. Fix #53. * specs/specs.mk (SPECL_ENV): Until the next Specl release is available, manually reset LUA_INIT and LUA_INIT_5_2. * bootstrap.conf (buildreq): Add a reminder to clean up after Specl 12. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 3 +++ specs/specs.mk | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bootstrap.conf b/bootstrap.conf index 498a071..19ba632 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -30,6 +30,9 @@ # List of programs, minimum versions, and software urls required to # bootstrap, maintain and release GNU Zile. +## !!WARNING!! Tidy up specs/specs.mk as instructed when buildreq bumps +#@ specl requirement to 12 or higher. + # Build prerequisites buildreq=' git 1.5.5 http://git-scm.com diff --git a/specs/specs.mk b/specs/specs.mk index 4e881c4..ebf547f 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -5,8 +5,11 @@ ## Environment. ## ## ------------ ## +## !!WARNING!! When bootstrap.conf:buildreq specl setting requires specl +## 12 or higher, remove this entire Environment section! + specs_path = $(abs_builddir)/specs/?.lua -SPECL_ENV = LUA_PATH="$(specs_path);$(std_path);$(LUA_PATH)" +SPECL_ENV = LUA_PATH="$(specs_path);$(std_path);$(LUA_PATH)" LUA_INIT= LUA_INIT_5_2= ## ------ ## From c4ef760ba351dab6c3ad40983219a1ff50c7950e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Apr 2014 20:51:43 +0700 Subject: [PATCH 122/703] maint: move repository to its own project. * README.md, configure.ac, rockspec.conf, * specs/optparse_spec.yaml: Replace rrthomas with lua-stdlib. Signed-off-by: Gary V. Vaughan --- README.md | 10 +++++----- configure.ac | 2 +- rockspec.conf | 4 ++-- specs/optparse_spec.yaml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a076bf0..e8c6ae9 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ Standard Lua libraries by the [stdlib project][github] -[github]: http://github.com/rrthomas/lua-stdlib/ "Github repository" +[github]: http://github.com/lua-stdlib/lua-stdlib/ "Github repository" -[![travis-ci status](https://secure.travis-ci.org/rrthomas/lua-stdlib.png?branch=master)](http://travis-ci.org/rrthomas/lua-stdlib/builds) +[![travis-ci status](https://secure.travis-ci.org/lua-stdlib/lua-stdlib.png?branch=master)](http://travis-ci.org/lua-stdlib/lua-stdlib/builds) This is a collection of Lua libraries for Lua 5.1 and 5.2. The @@ -26,7 +26,7 @@ latest release (recommended): To install current git master (for testing): - luarocks install https://raw.github.com/rrthomas/lua-stdlib/release/stdlib-git-1.rockspec + luarocks install https://raw.github.com/lua-stdlib/lua-stdlib/release/stdlib-git-1.rockspec To install without LuaRocks, check out the sources from the [repository][github], and then run the following commands: the @@ -41,7 +41,7 @@ dependencies are listed in the dependencies entry of the file See [INSTALL][] for instructions for `configure`. [luarocks]: http://www.luarocks.org "LuaRocks Project" -[install]: https://raw.github.com/rrthomas/lua-stdlib/master/INSTALL +[install]: https://raw.github.com/lua-stdlib/lua-stdlib/master/INSTALL Use --- @@ -61,7 +61,7 @@ Documentation The libraries are [documented in LDoc][github.io]. Pre-built HTML files are included. -[github.io]: http://rrthomas.github.io/lua-stdlib +[github.io]: http://lua-stdlib.github.io/lua-stdlib Bug reports and code contributions diff --git a/configure.ac b/configure.ac index 83ad307..2965667 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [39], [http://github.com/rrthomas/lua-stdlib/issues]) +AC_INIT([stdlib], [39], [http://github.com/lua-stdlib/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/rockspec.conf b/rockspec.conf index 2e1e688..3376ac9 100644 --- a/rockspec.conf +++ b/rockspec.conf @@ -1,7 +1,7 @@ # stdlib rockspec configuration. description: - homepage: http://rrthomas.github.io/lua-stdlib + homepage: http://lua-stdlib.github.io/lua-stdlib license: MIT/X11 summary: General Lua Libraries detailed: @@ -13,4 +13,4 @@ dependencies: - lua >= 5.1 source: - url: git://github.com/rrthomas/lua-stdlib.git + url: git://github.com/lua-stdlib/lua-stdlib.git diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index 816b0a3..03b7468 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -34,7 +34,7 @@ specify std.optparse: Footer text. - Please report bugs at . + Please report bugs at . ]] -- strip off the leading whitespace required for YAML From bebaaab38aaec9747ac2611c96dce7cc1811b86f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 22 Apr 2014 08:34:56 +0700 Subject: [PATCH 123/703] refactor: restore alphabetical ordering to table specs. * specs/table_spec.yaml (clone_rename): Move back into alpha- betical order. Signed-off-by: Gary V. Vaughan --- specs/table_spec.yaml | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index f5d43b4..d190b56 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -93,6 +93,30 @@ specify std.table: expect (f "foo").should_error ("table expected") +- describe clone_rename: + - before: + subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } + f = M.clone_rename + - it does not just return the subject: + expect (f ({}, subject)).should_not_be (subject) + - it copies the subject: + expect (f ({}, subject)).should_equal (subject) + - it only makes a shallow copy: + expect (f ({}, subject).k2).should_be (subject.k2) + + - context when renaming some keys: + - before: + target = { newkey = subject.k1, k2 = subject.k2, k3 = subject.k3 } + - it renames during cloning: + expect (f ({k1 = "newkey"}, subject)).should_equal (target) + - it does not perturb the value in the renamed key field: + expect (f ({k1 = "newkey"}, subject).newkey).should_be (subject.k1) + + - "it diagnoses non-table arguments": + expect (f {}).should_error ("table expected") + expect (f ({}, "foo")).should_error ("table expected") + + - describe clone_select: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } @@ -125,30 +149,6 @@ specify std.table: expect (f "foo").should_error ("table expected") -- describe clone_rename: - - before: - subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } - f = M.clone_rename - - it does not just return the subject: - expect (f ({}, subject)).should_not_be (subject) - - it copies the subject: - expect (f ({}, subject)).should_equal (subject) - - it only makes a shallow copy: - expect (f ({}, subject).k2).should_be (subject.k2) - - - context when renaming some keys: - - before: - target = { newkey = subject.k1, k2 = subject.k2, k3 = subject.k3 } - - it renames during cloning: - expect (f ({k1 = "newkey"}, subject)).should_equal (target) - - it does not perturb the value in the renamed key field: - expect (f ({k1 = "newkey"}, subject).newkey).should_be (subject.k1) - - - "it diagnoses non-table arguments": - expect (f {}).should_error ("table expected") - expect (f ({}, "foo")).should_error ("table expected") - - - describe empty: - before: f = M.empty From 9cf3c52df69daf5975b1ac45995500af6dbef82d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 22 Apr 2014 11:01:19 +0700 Subject: [PATCH 124/703] specs: elide clone_rename deprecation warning, with Specl 12. Fix #54. * specs/spec_helper.lua.in (capture): Stub inprocess.capture if installed Specl is too old to implement it. * specs/table_spec.yaml (clone_rename): Wrap clone_rename in inprocess.capture to elide deprecation warning on stderr. Signed-off-by: Gary V. Vaughan --- specs/spec_helper.lua.in | 10 ++++++++-- specs/table_spec.yaml | 9 ++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 6dc1b18..b1a7fcf 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -1,5 +1,6 @@ -local hell = require "specl.shell" -local std = require "specl.std" +local hell = require "specl.shell" +local inprocess = require "specl.inprocess" +local std = require "specl.std" package.path = std.package.normalize ("lib/?.lua", package.path) @@ -126,6 +127,11 @@ end totable = (require "std.table").totable +-- Stub inprocess.capture if necessary; new in Specl 12. +capture = inprocess.capture or + function (f, arg) return f (unpack (arg or {})) end + + do -- Custom matcher for set size and set membership. diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index d190b56..5d9b417 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -97,10 +97,13 @@ specify std.table: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } f = M.clone_rename - - it does not just return the subject: - expect (f ({}, subject)).should_not_be (subject) + - it writes a deprecation warning to standard error on first call: + _, err = capture (f, {{}, subject}) + expect (err).should_contain "clone_rename is deprecated" + _, err = capture (f, {{}, subject}) + expect (err).should_be (nil) - it copies the subject: - expect (f ({}, subject)).should_equal (subject) + expect (f ({}, subject)).should_copy (subject) - it only makes a shallow copy: expect (f ({}, subject).k2).should_be (subject.k2) From 9eb0b2892020f362988301e5cc8a837b9e4f3558 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 22 Apr 2014 11:27:39 +0700 Subject: [PATCH 125/703] specs: return empty out and err values from `capture` stub. * specs/spec_helper.lua.in (table.clone_rename): Return empty strings for stderr and stdout from fallback stubbed `capture` implementation. Signed-off-by: Gary V. Vaughan --- specs/spec_helper.lua.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index b1a7fcf..3543b27 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -129,7 +129,7 @@ totable = (require "std.table").totable -- Stub inprocess.capture if necessary; new in Specl 12. capture = inprocess.capture or - function (f, arg) return f (unpack (arg or {})) end + function (f, arg) return "", "", f (unpack (arg or {})) end do From 48f5a7140231639b33a503616e39a85b9cc64fd2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 22 Apr 2014 11:35:47 +0700 Subject: [PATCH 126/703] specs: skip deprecation warning expectation when Specl < 12. * specs/table_spec.yaml (clone_rename): Skip deprecation warning expectation when the ""-returning stub is being used. Signed-off-by: Gary V. Vaughan --- specs/table_spec.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 5d9b417..81a413b 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -97,9 +97,12 @@ specify std.table: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } f = M.clone_rename - - it writes a deprecation warning to standard error on first call: + - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {{}, subject}) - expect (err).should_contain "clone_rename is deprecated" + if err ~= "" then + -- skip this test when using Specl < 12 capture stub + expect (err).should_contain "clone_rename is deprecated" + end _, err = capture (f, {{}, subject}) expect (err).should_be (nil) - it copies the subject: From 82c17fa2c07cf705f6f92f8b839bfcb459ab4a54 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 22 Apr 2014 11:41:04 +0700 Subject: [PATCH 127/703] specs: capture stub should return nil to match Specl 12. * specs/spec_helper.lua.in (capture): Return `nil` for stdout and stderr to match Specl 12 API. * specs/table_spec.yaml (clone_rename): Test for `nil`. Signed-off-by: Gary V. Vaughan --- specs/spec_helper.lua.in | 2 +- specs/table_spec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 3543b27..99a0fae 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -129,7 +129,7 @@ totable = (require "std.table").totable -- Stub inprocess.capture if necessary; new in Specl 12. capture = inprocess.capture or - function (f, arg) return "", "", f (unpack (arg or {})) end + function (f, arg) return nil, nil, f (unpack (arg or {})) end do diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 81a413b..5ad22c9 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -99,7 +99,7 @@ specify std.table: f = M.clone_rename - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {{}, subject}) - if err ~= "" then + if err ~= nil then -- skip this test when using Specl < 12 capture stub expect (err).should_contain "clone_rename is deprecated" end From c914a19b82970dc3bb038300cc06d0675308a968 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 22 Apr 2014 11:22:20 +0700 Subject: [PATCH 128/703] maint: regenerate spec_helper.lua for `make check` if necessary. * specs/specs.mk (specl-check-local): Add dependency on specs/spec_helper.lua, so that it is regenerated before the main `check-local` body is executed if necessary. Signed-off-by: Gary V. Vaughan --- specs/specs.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/specs.mk b/specs/specs.mk index ebf547f..0f5aa9b 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -46,4 +46,6 @@ EXTRA_DIST += \ $(srcdir)/specs/spec_helper.lua.in \ $(NOTHING_ELSE) +specl-check-local: specs/spec_helper.lua + include build-aux/specl.mk From 8135b4eeb0afbdc8223a38b38f40c66e32ab2c88 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 22 Apr 2014 11:51:02 +0700 Subject: [PATCH 129/703] maint: update raw urls to new github url scheme. * README.md: Use new `raw.githubusercontent.com` url scheme throughout. Signed-off-by: Gary V. Vaughan --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8c6ae9..8ba232c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ latest release (recommended): To install current git master (for testing): - luarocks install https://raw.github.com/lua-stdlib/lua-stdlib/release/stdlib-git-1.rockspec + luarocks install https://raw.githubusercontent.com/lua-stdlib/lua-stdlib/release/stdlib-git-1.rockspec To install without LuaRocks, check out the sources from the [repository][github], and then run the following commands: the @@ -41,7 +41,7 @@ dependencies are listed in the dependencies entry of the file See [INSTALL][] for instructions for `configure`. [luarocks]: http://www.luarocks.org "LuaRocks Project" -[install]: https://raw.github.com/lua-stdlib/lua-stdlib/master/INSTALL +[install]: https://raw.githubusercontent.com/lua-stdlib/lua-stdlib/master/INSTALL Use --- From a514932797aded8d870c7fceef5c3b2a0cfb07ba Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 23 Apr 2014 14:46:25 +0700 Subject: [PATCH 130/703] specs: take advantage of improvements to Specl DSL. * specs/container_spec.yaml, specs/debug_spec.yaml, specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/math_spec.yaml, specs/object_spec.yaml, specs/optparse_spec.yaml, specs/package_spec.yaml, specs/set_spec.yaml, specs/std_spec.yaml, specs/strbuf_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml, specs/tree_spec.yaml: Use `not_to_` instead of `should_not`, `to_` instead of `should_` and `to_copy` instead of `should_equal` plus `should_not_be`. Signed-off-by: Gary V. Vaughan --- specs/container_spec.yaml | 67 +++++---- specs/debug_spec.yaml | 6 +- specs/functional_spec.yaml | 22 +-- specs/io_spec.yaml | 66 ++++----- specs/list_spec.yaml | 230 ++++++++++++++--------------- specs/math_spec.yaml | 12 +- specs/object_spec.yaml | 142 +++++++++--------- specs/optparse_spec.yaml | 206 +++++++++++++------------- specs/package_spec.yaml | 90 ++++++------ specs/set_spec.yaml | 196 ++++++++++++------------- specs/std_spec.yaml | 6 +- specs/strbuf_spec.yaml | 54 +++---- specs/string_spec.yaml | 266 ++++++++++++++++----------------- specs/table_spec.yaml | 172 +++++++++++----------- specs/tree_spec.yaml | 291 ++++++++++++++++++------------------- 15 files changed, 909 insertions(+), 917 deletions(-) diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index ab37b67..c29076e 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -8,35 +8,34 @@ specify std.container: - context by name: - it does not touch the global table: expect (show_apis {added_to="_G", by="std.container"}). - should_equal {} + to_equal {} - describe construction: - context from Container prototype: - before: things = Container {"foo", "bar", baz="quux"} - it constructs a new container: - expect (things).should_not_be (Container) - expect (type (things)).should_be "table" - expect (prototype (things)).should_be "Container" + expect (things).not_to_be (Container) + expect (type (things)).to_be "table" + expect (prototype (things)).to_be "Container" - it reuses the container metatable: o, p = things {"o"}, things {"p"} - expect (getmetatable (o)).should_be (getmetatable (p)) + expect (getmetatable (o)).to_be (getmetatable (p)) - it sets container fields from arguments: o = Container {"foo", "bar", baz="quux"} - expect (o).should_equal (things) + expect (o).to_equal (things) - it serves as a prototype for new instances: o = things {} - expect (prototype (o)).should_be "Container" - expect (o).should_not_be (things) - expect (o).should_equal (things) - expect (getmetatable (o)).should_be (getmetatable (things)) + expect (prototype (o)).to_be "Container" + expect (o).to_copy (things) + expect (getmetatable (o)).to_be (getmetatable (things)) - it separates '_' prefixed fields: expect (Container {foo="bar", _baz="quux"}). - should_equal (Container {foo="bar"}) + to_equal (Container {foo="bar"}) - it puts '_' prefixed fields in a new metatable: things = Container {foo="bar", _baz="quux"} - expect (getmetatable (things)).should_not_be (getmetatable (Container)) - expect (getmetatable (things)._baz).should_be "quux" + expect (getmetatable (things)).not_to_be (getmetatable (Container)) + expect (getmetatable (things)._baz).to_be "quux" - context with module functions: - before: fold = (require "std.functional").fold @@ -50,16 +49,16 @@ specify std.container: } - it does not propagate _functions: things = Bag {} - expect (things.count).should_be (nil) + expect (things.count).to_be (nil) - it does not provide object methods: | things = Bag {} - expect (things:count ()).should_error "attempt to call method 'count'" + expect (things:count ()).to_error "attempt to call method 'count'" - it does retain module functions: things = Bag { apples = 1, oranges = 3 } - expect (Bag.count (things)).should_be (4) + expect (Bag.count (things)).to_be (4) - it does allow elements named after module functions: things = Bag { count = 1337 } - expect (Bag.count (things)).should_be (1337) + expect (Bag.count (things)).to_be (1337) - describe field access: @@ -67,40 +66,40 @@ specify std.container: things = Container {"foo", "bar", baz="quux"} - context with bracket notation: - it provides access to existing contents: - expect (things[1]).should_be "foo" - expect (things["baz"]).should_be "quux" + expect (things[1]).to_be "foo" + expect (things["baz"]).to_be "quux" - it assigns new contents: things["new"] = "value" - expect (things["new"]).should_be "value" + expect (things["new"]).to_be "value" - context with dot notation: - it provides access to existing contents: - expect (things.baz).should_be "quux" + expect (things.baz).to_be "quux" - it assigns new contents: things.new = "value" - expect (things.new).should_be "value" + expect (things.new).to_be "value" - describe stringification: - before: things = Container {_type = "Derived", "one", "two", "three"} - it returns a string: - expect (type (tostring (things))).should_be "string" + expect (type (tostring (things))).to_be "string" - it contains the type: - expect (tostring (Container {})).should_contain "Container" - expect (tostring (things)).should_contain (prototype (things)) + expect (tostring (Container {})).to_contain "Container" + expect (tostring (things)).to_contain (prototype (things)) - it contains the ordered array part elements: - expect (tostring (things)).should_contain "one, two, three" + expect (tostring (things)).to_contain "one, two, three" - it contains the ordered dictionary part elements: expect (tostring (Container {one = true, two = true, three = true})). - should_contain "one=true, three=true, two=true" + to_contain "one=true, three=true, two=true" expect (tostring (things {one = true, two = true, three = true})). - should_contain "one=true, three=true, two=true" + to_contain "one=true, three=true, two=true" - it contains a ';' separator only when container has array and dictionary parts: - expect (tostring (things)).should_not_contain ";" + expect (tostring (things)).not_to_contain ";" expect (tostring (Container {one = true, two = true, three = true})). - should_not_contain ";" + not_to_contain ";" expect (tostring (things {one = true, two = true, three = true})). - should_contain ";" + to_contain ";" - describe tablification: @@ -108,8 +107,8 @@ specify std.container: totable = (require "std.table").totable Derived = Container {_type = "Derived", "one", "two", three = true} - it returns a table: - expect (prototype (totable (Derived))).should_be "table" + expect (prototype (totable (Derived))).to_be "table" - it contains all non-hidden fields of container: - expect (totable (Derived)).should_contain.all_of {"one", "two", "three"} + expect (totable (Derived)).to_contain.all_of {"one", "two", "three"} - it does not contain any hidden fields of container: - expect (totable (Derived)).should_equal {"one", "two", three = true} + expect (totable (Derived)).to_equal {"one", "two", three = true} diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 038fe7c..8be39cd 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -15,15 +15,15 @@ specify std.debug: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - should_equal {} + to_equal {} - it contains apis from the core debug table: expect (show_apis {from=base_module, not_in=this_module}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - context via the std module: - it adds apis to the core debug table: expect (show_apis {added_to=base_module, by="std"}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - describe _DEBUG: diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 1bb7aa6..dfe0845 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -14,24 +14,24 @@ specify std.functional: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - should_equal {} + to_equal {} - context via the std module: - it adds apis to the global table: expect (show_apis {added_to=global_table, by="std"}). - should_contain.all_of (std_globals) + to_contain.all_of (std_globals) - describe bind: - it does not affect normal operation if no arguments are bound: expect (M.bind (math.min, {}) (2, 3, 4)). - should_equal (2) + to_equal (2) - the extra arguments are taken into account: expect (M.bind (math.min, {1, 0}) (2, 3, 4)). - should_equal (0) + to_equal (0) - the extra arguments can be out of order: expect (M.bind (math.pow, {[2] = 3}) (2)). - should_equal (8) + to_equal (8) - describe case: @@ -41,12 +41,12 @@ specify std.functional: default = function (s) return s end branches = { yes = yes, no = no, default } - it matches against branch keys: - expect (M.case ("yes", branches)).should_be (true) - expect (M.case ("no", branches)).should_be (false) + expect (M.case ("yes", branches)).to_be (true) + expect (M.case ("no", branches)).to_be (false) - it has a default for unmatched keys: - expect (M.case ("none", branches)).should_be "none" + expect (M.case ("none", branches)).to_be "none" - it returns nil for unmatched keys with no default: - expect (M.case ("none", { yes = yes, no = no })).should_be (nil) + expect (M.case ("none", { yes = yes, no = no })).to_be (nil) - it evaluates `with` exactly once: s = "prince" function acc () s = s .. "s"; return s end @@ -55,7 +55,7 @@ specify std.functional: princes = function () return "many" end, princess = function () return "one" end, function () return "gibberish" end, - })).should_be "many" + })).to_be "many" - describe collect: @@ -64,7 +64,7 @@ specify std.functional: - describe compose: - it composes functions in the correct order: expect (M.compose (math.sin, math.cos) (1)). - should_equal (math.cos (math.sin (1))) + to_equal (math.cos (math.sin (1))) - describe curry: diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index cc76e82..7b89de6 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -21,27 +21,27 @@ specify std.io: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - should_equal {} + to_equal {} - it contains apis from the core io table: expect (show_apis {from=base_module, not_in=this_module}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - it replaces no apis from the core io table: expect (show_apis {from=base_module, enhanced_in=this_module}). - should_equal {} + to_equal {} - context via the std module: - it adds apis to the global table: expect (show_apis {added_to=global_table, by="std"}). - should_contain.all_of (std_globals) + to_contain.all_of (std_globals) - it adds apis to the core io table: expect (show_apis {added_to=base_module, by="std"}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - it adds methods to the file metatable: expect (show_apis {added_to="getmetatable (io.stdin)", by="std"}). - should_contain.a_permutation_of (extend_metamethods) + to_contain.a_permutation_of (extend_metamethods) - it replaces no apis from the core io table: expect (show_apis {from=base_module, enhanced_after='require "std"'}). - should_equal {} + to_equal {} - describe catdir: @@ -54,59 +54,59 @@ specify std.io: - before: script = [[require "std.io".die "By 'eck!"]] - it outputs a message to stderr: - expect (luaproc (script)).should_fail_with "By 'eck!\n" + expect (luaproc (script)).to_fail_with "By 'eck!\n" - it ignores `prog.line` without `prog.file` or `prog.name`: script = [[prog = { line = 125 };]] .. script - expect (luaproc (script)).should_fail_with "By 'eck!\n" + expect (luaproc (script)).to_fail_with "By 'eck!\n" - it ignores `opts.line` without `opts.program`: script = [[opts = { line = 99 };]] .. script - expect (luaproc (script)).should_fail_with "By 'eck!\n" + expect (luaproc (script)).to_fail_with "By 'eck!\n" - it prefixes `prog.name` if any: | script = [[prog = { name = "name" };]] .. script - expect (luaproc (script)).should_fail_with "name: By 'eck!\n" + expect (luaproc (script)).to_fail_with "name: By 'eck!\n" - it appends `prog.line` if any, to `prog.name`: | script = [[prog = { line = 125, name = "name" };]] .. script - expect (luaproc (script)).should_fail_with "name:125: By 'eck!\n" + expect (luaproc (script)).to_fail_with "name:125: By 'eck!\n" - it prefixes `prog.file` if any: | script = [[prog = { file = "file" };]] .. script - expect (luaproc (script)).should_fail_with "file: By 'eck!\n" + expect (luaproc (script)).to_fail_with "file: By 'eck!\n" - it appends `prog.line` if any, to `prog.name`: | script = [[prog = { file = "file", line = 125 };]] .. script - expect (luaproc (script)).should_fail_with "file:125: By 'eck!\n" + expect (luaproc (script)).to_fail_with "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)).should_fail_with "name: By 'eck!\n" + expect (luaproc (script)).to_fail_with "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)).should_fail_with "name:125: By 'eck!\n" + expect (luaproc (script)).to_fail_with "name:125: By 'eck!\n" - it prefers `prog.file` to `opts.program`: | script = [[ prog = { file = "file" }; opts = { program = "program" } ]] .. script - expect (luaproc (script)).should_fail_with "file: By 'eck!\n" + expect (luaproc (script)).to_fail_with "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)).should_fail_with "file:125: By 'eck!\n" + expect (luaproc (script)).to_fail_with "file:125: By 'eck!\n" - it prefixes `opts.program` if any: | script = [[opts = { program = "program" };]] .. script - expect (luaproc (script)).should_fail_with "program: By 'eck!\n" + expect (luaproc (script)).to_fail_with "program: By 'eck!\n" - it appends `opts.line` if any, to `opts.program`: | script = [[opts = { line = 99, program = "program" };]] .. script - expect (luaproc (script)).should_fail_with "program:99: By 'eck!\n" + expect (luaproc (script)).to_fail_with "program:99: By 'eck!\n" - describe process_files: - it is the same function as legacy processFiles call: - expect (M.process_files).should_be (M.processFiles) + expect (M.process_files).to_be (M.processFiles) - describe readlines: @@ -125,51 +125,51 @@ specify std.io: - before: script = [[require "std.io".warn "Ayup!"]] - it outputs a message to stderr: - expect (luaproc (script)).should_output_error "Ayup!\n" + 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)).should_output_error "Ayup!\n" + expect (luaproc (script)).to_output_error "Ayup!\n" - it prefixes `prog.name` if any: | script = [[prog = { name = "name" };]] .. script - expect (luaproc (script)).should_output_error "name: Ayup!\n" + 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)).should_output_error "name:125: Ayup!\n" + expect (luaproc (script)).to_output_error "name:125: Ayup!\n" - it prefixes `prog.file` if any: | script = [[prog = { file = "file" };]] .. script - expect (luaproc (script)).should_output_error "file: Ayup!\n" + 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)).should_output_error "file:125: Ayup!\n" + 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)).should_output_error "name: Ayup!\n" + 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)).should_output_error "name:125: Ayup!\n" + 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)).should_output_error "file: Ayup!\n" + 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)).should_output_error "file:125: Ayup!\n" + expect (luaproc (script)).to_output_error "file:125: Ayup!\n" - it prefixes `opts.program` if any: | script = [[opts = { program = "program" };]] .. script - expect (luaproc (script)).should_output_error "program: Ayup!\n" + 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)).should_output_error "program:99: Ayup!\n" + expect (luaproc (script)).to_output_error "program:99: Ayup!\n" - describe writelines: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 4bdac86..0153f74 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -10,72 +10,72 @@ specify std.list: - context by name: - it does not touch the global table: expect (show_apis {added_to="_G", by="std.list"}). - should_equal {} + to_equal {} - describe construction: - context from List clone method: - it constructs a new list: l = List:clone {} - expect (l).should_not_be (List) - expect (Object.type (l)).should_be "List" + expect (l).not_to_be (List) + expect (Object.type (l)).to_be "List" - it reuses the List metatable: l, m = List:clone {"l"}, List:clone {"m"} - expect (getmetatable (l)).should_be (getmetatable (m)) + expect (getmetatable (l)).to_be (getmetatable (m)) - it initialises list with constructor parameters: m = List:clone {"foo", "bar", "baz"} - expect (m).should_equal (l) + expect (m).to_equal (l) - it serves as a prototype for new instances: obj = l:clone {} - expect (Object.type (obj)).should_be "List" - expect (obj).should_equal (l) - expect (getmetatable (obj)).should_be (getmetatable (l)) + expect (Object.type (obj)).to_be "List" + expect (obj).to_equal (l) + expect (getmetatable (obj)).to_be (getmetatable (l)) # List {args} is just syntactic sugar for List:clone {args} - context from List object prototype: - it constructs a new list: l = List {} - expect (l).should_not_be (List) - expect (Object.type (l)).should_be "List" + expect (l).not_to_be (List) + expect (Object.type (l)).to_be "List" - it reuses the List metatable: l, m = List {"l"}, List {"m"} - expect (getmetatable (l)).should_be (getmetatable (m)) + expect (getmetatable (l)).to_be (getmetatable (m)) - it initialises list with constructor parameters: m = List {"foo", "bar", "baz"} - expect (m).should_equal (l) + expect (m).to_equal (l) - it serves as a prototype for new instances: obj = l {} - expect (Object.type (obj)).should_be "List" - expect (obj).should_equal (l) - expect (getmetatable (obj)).should_be (getmetatable (l)) + expect (Object.type (obj)).to_be "List" + expect (obj).to_equal (l) + expect (getmetatable (obj)).to_be (getmetatable (l)) - describe metatable propagation: - it reuses the metatable for List constructed objects: obj = List {"foo", "bar"} - expect (getmetatable (obj)).should_be (getmetatable (l)) + expect (getmetatable (obj)).to_be (getmetatable (l)) - describe append: - context when called as a list object method: - it returns a list object: l = l:append ("quux") - expect (Object.type (l)).should_be "List" + expect (Object.type (l)).to_be "List" - it works for an empty list: l = List {} - expect (l:append ("quux")).should_equal (List {"quux"}) + expect (l:append ("quux")).to_equal (List {"quux"}) - it appends an item to a list: expect (l:append ("quux")). - should_equal (List {"foo", "bar", "baz", "quux"}) + to_equal (List {"foo", "bar", "baz", "quux"}) - context when called as a list metamethod: - it returns a list object: l = l + "quux" - expect (Object.type (l)).should_be "List" + expect (Object.type (l)).to_be "List" - it works for an empty list: l = List {} - expect (l + "quux").should_equal (List {"quux"}) + expect (l + "quux").to_equal (List {"quux"}) - it appends an item to a list: expect (l + "quux"). - should_equal (List {"foo", "bar", "baz", "quux"}) + to_equal (List {"foo", "bar", "baz", "quux"}) - describe compare: @@ -83,45 +83,45 @@ specify std.list: a, b = List {"foo", "bar"}, List {"foo", "baz"} - context when called as a list object method: - it returns -1 when the first list is less than the second: | - expect (a:compare {"foo", "baz"}).should_be (-1) - expect (a:compare (List {"foo", "baz"})).should_be (-1) + expect (a:compare {"foo", "baz"}).to_be (-1) + expect (a:compare (List {"foo", "baz"})).to_be (-1) - it returns -1 when the second list has additional elements: | b = List {"foo"} - expect (b:compare {"foo", "bar"}).should_be (-1) - expect (List {"foo"}:compare (List {"foo", "bar"})).should_be (-1) + expect (b:compare {"foo", "bar"}).to_be (-1) + expect (List {"foo"}:compare (List {"foo", "bar"})).to_be (-1) - it returns 0 when two lists are the same: | - expect (a:compare {"foo", "bar"}).should_be (0) - expect (a:compare (List {"foo", "bar"})).should_be (0) + expect (a:compare {"foo", "bar"}).to_be (0) + expect (a:compare (List {"foo", "bar"})).to_be (0) - it returns +1 when the first list is greater than the second: | - expect (a:compare {"baz", "quux"}).should_be (1) - expect (a:compare (List {"baz", "quux"})).should_be (1) + expect (a:compare {"baz", "quux"}).to_be (1) + expect (a:compare (List {"baz", "quux"})).to_be (1) - it returns +1 when the first list has additional elements: | - expect (a:compare {"foo"}).should_be (1) - expect (a:compare (List {"foo"})).should_be (1) + expect (a:compare {"foo"}).to_be (1) + expect (a:compare (List {"foo"})).to_be (1) - context when called as a '<' list metamethod: - it succeeds when the first list is less than the second: - expect (a < b).should_be (true) + expect (a < b).to_be (true) - it fails when the first list is not less than the second: - expect (a < a).should_be (false) - expect (b < a).should_be (false) + expect (a < a).to_be (false) + expect (b < a).to_be (false) - context when called as a '>' list metamethod: - it succeeds when the first list is greater than the second: - expect (b > a).should_be (true) + expect (b > a).to_be (true) - it fails when the first list is not greater than the second: - expect (b > b).should_be (false) - expect (a > b).should_be (false) + expect (b > b).to_be (false) + expect (a > b).to_be (false) - context when called as a '<=' list metamethod: - it succeeds when the first list is less than or equal to the second: - expect (a <= b).should_be (true) - expect (a <= a).should_be (true) + expect (a <= b).to_be (true) + expect (a <= a).to_be (true) - it fails when the first list is not less than or equal to the second: - expect (b <= a).should_be (false) + expect (b <= a).to_be (false) - context when called as a '>=' list metamethod: - it succeeds when the first list is greater than or equal to the second: - expect (b >= a).should_be (true) - expect (b >= b).should_be (true) + expect (b >= a).to_be (true) + expect (b >= b).to_be (true) - it fails when the first list is not greater than or equal to the second: - expect (a >= b).should_be (false) + expect (a >= b).to_be (false) - describe concat: @@ -130,40 +130,40 @@ specify std.list: - context when called as a list object method: - it returns a list object: l = l:concat (List {"baz"}) - expect (Object.type (l)).should_be "List" + expect (Object.type (l)).to_be "List" - it works for an empty list: l = List {} - expect (l:concat (List {"baz"})).should_equal (List {"baz"}) + expect (l:concat (List {"baz"})).to_equal (List {"baz"}) - it concatenates lists: expect (l:concat (List {"baz", "quux"})). - should_equal (List {"foo", "bar", "baz", "quux"}) + to_equal (List {"foo", "bar", "baz", "quux"}) expect (l:concat (List {"baz"}, List {"quux"})). - should_equal (List {"foo", "bar", "baz", "quux"}) + to_equal (List {"foo", "bar", "baz", "quux"}) - context whne called as a list metamethod: - it returns a list object: l = l .. List {"baz"} - expect (Object.type (l)).should_be "List" + expect (Object.type (l)).to_be "List" - it works for an empty list: l = List {} - expect (l .. List {"baz"}).should_equal (List {"baz"}) + expect (l .. List {"baz"}).to_equal (List {"baz"}) - it concatenates lists: expect (l .. List {"baz", "quux"}). - should_equal (List {"foo", "bar", "baz", "quux"}) + to_equal (List {"foo", "bar", "baz", "quux"}) expect (l .. List {"baz"} .. List {"quux"}). - should_equal (List {"foo", "bar", "baz", "quux"}) + to_equal (List {"foo", "bar", "baz", "quux"}) - describe cons: - context when called as a list object method: - it returns a list object: l = l:cons "quux" - expect (Object.type (l)).should_be "List" + expect (Object.type (l)).to_be "List" - it works for empty lists: l = List {} - expect (l:cons "quux").should_equal (List {"quux"}) + expect (l:cons "quux").to_equal (List {"quux"}) - it prepends an item to a list: expect (l:cons "quux"). - should_equal (List {"quux", "foo", "bar", "baz"}) + to_equal (List {"quux", "foo", "bar", "baz"}) - describe depair: @@ -174,31 +174,31 @@ specify std.list: - it diagnoses an argument that is not a list of lists: - context when called as a list object method: - it returns a primitive table: - expect (Object.type (l:depair ())).should_be "table" + expect (Object.type (l:depair ())).to_be "table" - it works with an empty list: l = List {} - expect (l:depair ()).should_equal {} + expect (l:depair ()).to_equal {} - it is the inverse of enpair: - expect (l:depair ()).should_equal (t) + expect (l:depair ()).to_equal (t) - describe elems: - it is an iterator over list members: t = {} for e in List.elems (l) do table.insert (t, e) end - expect (t).should_equal {"foo", "bar", "baz"} + expect (t).to_equal {"foo", "bar", "baz"} - it works for an empty list: t = {} for e in List.elems (List {}) do table.insert (t, e) end - expect (t).should_equal {} + expect (t).to_equal {} - it can be called from the list module: t = {} for e in List.elems (l) do table.insert (t, e) end - expect (t).should_equal {"foo", "bar", "baz"} + expect (t).to_equal {"foo", "bar", "baz"} - it can be called as a list object method: t = {} for e in l:elems () do table.insert (t, e) end - expect (t).should_equal {"foo", "bar", "baz"} + expect (t).to_equal {"foo", "bar", "baz"} - describe enpair: @@ -208,12 +208,12 @@ specify std.list: - it diagnoses a missing argument: - it diagnoses a non-table argument: - it returns a list object: - expect (Object.type (List.enpair (t))).should_be "List" + expect (Object.type (List.enpair (t))).to_be "List" - it works for an empty table: - expect (List.enpair {}).should_equal (List {}) + expect (List.enpair {}).to_equal (List {}) - it turns a table into a list of pairs: expect (List.enpair (t)). - should_equal (List {List {1, "first"}, List {2, "second"}, List {"third", 4}}) + to_equal (List {List {1, "first"}, List {2, "second"}, List {"third", 4}}) - describe filter: @@ -224,12 +224,12 @@ specify std.list: - context when called as a list object method: - it returns a list object: m = l:filter (p) - expect (Object.type (m)).should_be "List" + expect (Object.type (m)).to_be "List" - it works for an empty list: l = List {} - expect (l:filter (p)).should_equal (List {}) + expect (l:filter (p)).to_equal (List {}) - it filters a list according to a predicate: - expect (l:filter (p)).should_equal (List {"bar", "baz"}) + expect (l:filter (p)).to_equal (List {"bar", "baz"}) - describe flatten: @@ -239,13 +239,13 @@ specify std.list: - context when called as a list object method: - it returns a list object: m = List.flatten (l) - expect (Object.type (m)).should_be "List" + expect (Object.type (m)).to_be "List" - it works for an empty list: l = List {} - expect (l:flatten ()).should_equal (List {}) + expect (l:flatten ()).to_equal (List {}) - it flattens a list: expect (l:flatten ()). - should_equal (List {"one", "two", "three", "four"}) + to_equal (List {"one", "two", "three", "four"}) - describe foldl: @@ -256,9 +256,9 @@ specify std.list: - context when called as a list object method: - it works with an empty list: l = List {} - expect (l:foldl (op["+"], 10000)).should_be (10000) + expect (l:foldl (op["+"], 10000)).to_be (10000) - it folds a binary function through a list: - expect (l:foldl (op["+"], 10000)).should_be (10111) + expect (l:foldl (op["+"], 10000)).to_be (10111) - describe foldr: @@ -269,9 +269,9 @@ specify std.list: - context when called as a list object method: - it works with an empty list: l = List {} - expect (l:foldl (op["/"], 1)).should_be (1) + expect (l:foldl (op["/"], 1)).to_be (1) - it folds a binary function through a list: - expect (l:foldl (op["/"], 10000)).should_be (10) + expect (l:foldl (op["/"], 10000)).to_be (10) - describe index_key: @@ -288,18 +288,18 @@ specify std.list: - context when called as a list object method: - it returns a list object: m = l:map (f) - expect (Object.type (m)).should_be "List" + expect (Object.type (m)).to_be "List" - it works for an empty list: l = List {} - expect (l:map (f)).should_equal (List {}) + expect (l:map (f)).to_equal (List {}) - it creates a new list: o = l m = l:map (f) - expect (l).should_equal (o) - expect (m).should_not_equal (o) - expect (l).should_equal (List {1, 2, 3, 4, 5}) + expect (l).to_equal (o) + expect (m).not_to_equal (o) + expect (l).to_equal (List {1, 2, 3, 4, 5}) - it maps a function over a list: - expect (l:map (f)).should_equal (List {1, 4, 9, 16, 25}) + expect (l:map (f)).to_equal (List {1, 4, 9, 16, 25}) - describe map_with: @@ -311,18 +311,18 @@ specify std.list: - context when called as a list object method: - it returns a list object: m = l:map_with (f) - expect (Object.type (m)).should_be "List" + expect (Object.type (m)).to_be "List" - it works for an empty list: l = List {} - expect (l:map_with (f)).should_equal (List {}) + expect (l:map_with (f)).to_equal (List {}) - it creates a new list: o = l m = l:map_with (f) - expect (l).should_equal (o) - expect (m).should_not_equal (o) - expect (l).should_equal (List {List {1, 2, 3}, List {4, 5}}) + expect (l).to_equal (o) + expect (m).not_to_equal (o) + expect (l).to_equal (List {List {1, 2, 3}, List {4, 5}}) - it maps a function over a list: - expect (l:map_with (f)).should_equal (List {3, 2}) + expect (l:map_with (f)).to_equal (List {3, 2}) - describe project: @@ -337,35 +337,35 @@ specify std.list: - context when called as a list object method: - it returns a list object: p = l:project ("third") - expect (Object.type (p)).should_be "List" + expect (Object.type (p)).to_be "List" - it works with an empty list: l = List {} - expect (l:project ("third")).should_equal (List {}) + expect (l:project ("third")).to_equal (List {}) - it projects a list of fields from a list of tables: expect (l:project ("third")). - should_equal (List {true, 3, "3rd"}) + to_equal (List {true, 3, "3rd"}) - it projects fields with a falsey value correctly: expect (l:project ("first")). - should_equal (List {false, 1, "1st"}) + to_equal (List {false, 1, "1st"}) - describe relems: - it is a reverse iterator over list members: t = {} for e in List.relems (l) do table.insert (t, e) end - expect (t).should_equal {"baz", "bar", "foo"} + expect (t).to_equal {"baz", "bar", "foo"} - it works for an empty list: t = {} for e in List.relems (List {}) do table.insert (t, e) end - expect (t).should_equal {} + expect (t).to_equal {} - it can be called from the list module: t = {} for e in List.relems (l) do table.insert (t, e) end - expect (t).should_equal {"baz", "bar", "foo"} + expect (t).to_equal {"baz", "bar", "foo"} - it can be called as a list object method: t = {} for e in l:relems () do table.insert (t, e) end - expect (t).should_equal {"baz", "bar", "foo"} + expect (t).to_equal {"baz", "bar", "foo"} - describe rep: @@ -373,13 +373,13 @@ specify std.list: - context when called as a list object method: - it returns a list object: - expect (Object.type (l:rep (3))).should_be "List" + expect (Object.type (l:rep (3))).to_be "List" - it works for an empty list: l = List {} - expect (l:rep (99)).should_equal (List {}) + expect (l:rep (99)).to_equal (List {}) - it repeats the contents of a list: expect (l:rep (3)). - should_equal (List {"foo", "bar", "foo", "bar", "foo", "bar"}) + to_equal (List {"foo", "bar", "foo", "bar", "foo", "bar"}) - describe reverse: @@ -387,16 +387,16 @@ specify std.list: - context when called as a list object method: - it returns a list object: - expect (Object.type (l:reverse ())).should_be "List" + expect (Object.type (l:reverse ())).to_be "List" - it works for an empty list: l = List {} - expect (l:reverse ()).should_equal (List {}) + expect (l:reverse ()).to_equal (List {}) - it makes a new reversed list: m = l expect (l:reverse ()). - should_equal (List {"quux", "baz", "bar", "foo"}) - expect (l).should_equal (List {"foo", "bar", "baz", "quux"}) - expect (l).should_be (m) + to_equal (List {"quux", "baz", "bar", "foo"}) + expect (l).to_equal (List {"foo", "bar", "baz", "quux"}) + expect (l).to_be (m) - describe shape: @@ -407,21 +407,21 @@ specify std.list: - context when called as a list object method: - it returns a list object: | - expect (Object.type (l:sub (1, 1))).should_be "List" + expect (Object.type (l:sub (1, 1))).to_be "List" - it makes a list from a subrange of another list: | - expect (l:sub (2, 5)).should_equal (List {2, 3, 4, 5}) + expect (l:sub (2, 5)).to_equal (List {2, 3, 4, 5}) - it truncates the result if 'to' argument is too large: | - expect (l:sub (5, 10)).should_equal (List {5, 6, 7}) + expect (l:sub (5, 10)).to_equal (List {5, 6, 7}) - it defaults 'to' to the end of the list: | - expect (l:sub (5)).should_equal (List {5, 6, 7}) + expect (l:sub (5)).to_equal (List {5, 6, 7}) - it defaults 'from' to the beginning of the list: | - expect (l:sub ()).should_equal (l) + expect (l:sub ()).to_equal (l) - it returns an empty list when 'from' is greater than 'to': | - expect (l:sub (2, 1)).should_equal (List {}) + expect (l:sub (2, 1)).to_equal (List {}) - it counts from the end of the list for a negative 'from' argument: | - expect (l:sub (-3)).should_equal (List {5, 6, 7}) + expect (l:sub (-3)).to_equal (List {5, 6, 7}) - it counts from the end of the list for a negative 'to' argument: | - expect (l:sub (-5, -2)).should_equal (List {3, 4, 5, 6}) + expect (l:sub (-5, -2)).to_equal (List {3, 4, 5, 6}) - describe tail: @@ -429,15 +429,15 @@ specify std.list: - context when called as a list object method: - it returns a list object: | - expect (Object.type (l:tail ())).should_be "List" + expect (Object.type (l:tail ())).to_be "List" - it makes a new list with the first element removed: | - expect (l:tail ()).should_equal (List {2, 3, 4, 5, 6, 7}) + expect (l:tail ()).to_equal (List {2, 3, 4, 5, 6, 7}) - it works for an empty list: | l = List {} - expect (l:tail ()).should_equal (List {}) + expect (l:tail ()).to_equal (List {}) - it returns an empty list when passed a list with one element: | l = List {1} - expect (l:tail ()).should_equal (List {}) + expect (l:tail ()).to_equal (List {}) - describe transpose: diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 02c2cb0..db66dc8 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -8,7 +8,7 @@ before: | extend_base = { "round", "_floor" } enhance_base = { "floor" } - -- 'should_contain' will match keys as well as values :) + -- 'to_contain' will match keys as well as values :) all_apis = {} for _, s in ipairs (extend_base) do all_apis[s] = true end for _, s in ipairs (enhance_base) do all_apis[s] = true end @@ -20,21 +20,21 @@ specify std.math: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - should_equal {} + to_equal {} - it contains apis from the core math table: expect (show_apis {from=base_module, not_in=this_module}). - should_contain.a_permutation_of (all_apis) + to_contain.a_permutation_of (all_apis) - it enhances some apis from the core math table: expect (show_apis {from=base_module, enhanced_in=this_module}). - should_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (enhance_base) - context via the std module: - it adds apis to the core math table: expect (show_apis {added_to=base_module, by="std"}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - it replaces some apis from the core math table: expect (show_apis {from=base_module, enhanced_after='require "std"'}). - should_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (enhance_base) - describe floor: diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 7f9e74b..068b564 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -9,72 +9,69 @@ specify std.object: - context by name: - it does not touch the global table: expect (show_apis {added_to="_G", by="std.object"}). - should_equal {} + to_equal {} - describe construction: - context from Object clone method: - it constructs a new object: obj = Object:clone {} - expect (obj).should_not_be (Object) - expect (type (obj)).should_be "table" - expect (prototype (obj)).should_be "Object" + expect (obj).not_to_be (Object) + expect (type (obj)).to_be "table" + expect (prototype (obj)).to_be "Object" - it reuses the Object metatable: o = obj:clone {"o"} p = o:clone {"p"} - expect (p).should_not_be (o) - expect (getmetatable (o)).should_be (getmetatable (p)) + expect (p).not_to_be (o) + expect (getmetatable (o)).to_be (getmetatable (p)) - it sets object fields from arguments: - o = obj:clone {} - expect (o).should_not_be (obj) - expect (o).should_equal (obj) + expect (obj:clone {}).to_copy (obj) - it serves as a prototype for new instances: o = obj:clone {} - expect (prototype (o)).should_be "Object" - expect (o).should_not_be (obj) - expect (o).should_equal (obj) - expect (getmetatable (o)).should_be (getmetatable (obj)) + expect (prototype (o)).to_be "Object" + expect (o).to_copy (obj) + expect (getmetatable (o)).to_be (getmetatable (obj)) - it separates '_' prefixed fields: expect (Object:clone {foo="bar", _baz="quux"}). - should_equal (Object:clone {foo="bar"}) + to_equal (Object:clone {foo="bar"}) - it puts '_' prefixed fields in a new metatable: obj = Object:clone {foo="bar", _baz="quux"} - expect (getmetatable (obj)).should_not_be (getmetatable (Object)) - expect (getmetatable (obj)._baz).should_be "quux" + expect (getmetatable (obj)).not_to_be (getmetatable (Object)) + expect (getmetatable (obj)._baz).to_be "quux" - describe prototype: - before: o = Object {} - context when called from the object module: - it reports the prototype stored in the object's metatable: - expect (prototype (o)).should_be "Object" + expect (prototype (o)).to_be "Object" - it reports the type of a cloned object: - expect (prototype (o {})).should_be "Object" + expect (prototype (o {})).to_be "Object" - it reports the type of a derived object: Example = Object {_type = "Example"} - expect (prototype (Example)).should_be "Example" + expect (prototype (Example)).to_be "Example" - it reports the type of a cloned derived object: Portal = Object {_type = "Demon"} p = Portal {} - expect (prototype (p)).should_be "Demon" - expect (prototype (p {})).should_be "Demon" + expect (prototype (p)).to_be "Demon" + expect (prototype (p {})).to_be "Demon" - context when called as an object method: - it reports the type stored in the object's metatable: - expect (o:prototype ()).should_be "Object" + expect (o:prototype ()).to_be "Object" - it reports the type of a cloned object: - expect ((o {}):prototype ()).should_be "Object" + expect ((o {}):prototype ()).to_be "Object" - it reports the type of a subclassed object: Example = Object {_type = "Example"} - expect (Example:prototype ()).should_be "Example" + expect (Example:prototype ()).to_be "Example" - it reports the type of a cloned subclassed object: Portal = Object {_type = "Demon"} p = Portal {} - expect (p:prototype ()).should_be "Demon" - expect ((p {}):prototype ()).should_be "Demon" + expect (p:prototype ()).to_be "Demon" + expect ((p {}):prototype ()).to_be "Demon" - context backwards compatibility: - it reports the prototype stored in the object's metatable: - expect (Object.type (o)).should_be "Object" + expect (Object.type (o)).to_be "Object" - it reports the type stored in the object's metatable: - expect (o:type ()).should_be "Object" + expect (o:type ()).to_be "Object" - describe instantiation from a prototype: @@ -90,19 +87,19 @@ specify std.object: Array._init = nil - it contains user-defined fields: expect (totable (Array)). - should_equal {"foo", "bar", "baz"} + to_equal {"foo", "bar", "baz"} - it sets array part of instance object from positional parameters: array = Array {"first", "second", "third"} expect (totable (array)). - should_equal {"first", "second", "third"} + to_equal {"first", "second", "third"} - it uses prototype values for missing positional parameters: array = Array {"first", "second"} expect (totable (array)). - should_equal {"first", "second", "baz"} + to_equal {"first", "second", "baz"} - it merges surplas positional parameters: array = Array {"first", "second", "third", "fourth"} expect (totable (array)). - should_equal {"first", "second", "third", "fourth"} + to_equal {"first", "second", "third", "fourth"} - context when _init is an empty table: - before: @@ -113,11 +110,10 @@ specify std.object: } - it contains user-defined fields: expect (totable (Prototype)). - should_equal {"first", "second", "third"} + to_equal {"first", "second", "third"} - it ignores positional parameters: | instance = Prototype {"foo", "bar"} - expect (instance).should_not_be (Prototype) - expect (instance).should_equal (Prototype) + expect (instance).to_copy (Prototype) - context when _init is a table of field names: - before: @@ -130,19 +126,19 @@ specify std.object: } - it contains user-defined fields: expect (totable (Process)). - should_equal {status = -1, output = "empty", errout = "no errors"} + to_equal {status = -1, output = "empty", errout = "no errors"} - it sets user-defined fields from positional parameters: proc = Process {0, "output", "diagnostics"} expect (totable (proc)). - should_equal {status = 0, output = "output", errout = "diagnostics"} + to_equal {status = 0, output = "output", errout = "diagnostics"} - it uses prototype values for missing positional parameters: proc = Process {0, "output"} expect (totable (proc)). - should_equal {status = 0, output = "output", errout = "no errors"} + to_equal {status = 0, output = "output", errout = "no errors"} - it discards surplus positional parameters: proc = Process {0, "output", "diagnostics", "garbage"} expect (totable (proc)). - should_equal { status = 0, output = "output", errout = "diagnostics" } + to_equal { status = 0, output = "output", errout = "diagnostics" } - context when _init is a function: - before: @@ -157,7 +153,7 @@ specify std.object: - it passes user defined fields to custom _init function: instance = Prototype {"param1", "param2"} expect ({instance.f1, instance.f2, instance.args}). - should_equal {"proto1", "proto2", {"param1", "param2"}} + to_equal {"proto1", "proto2", {"param1", "param2"}} - describe field access: - before: @@ -177,29 +173,29 @@ specify std.object: } - it provides object field access with dot notation: - expect (instance.field).should_be "in object" + expect (instance.field).to_be "in object" - it provides class field acces with dot notation: - expect (Prototype.field).should_be "in prototype" + expect (Prototype.field).to_be "in prototype" - it provides object method acces with colon notation: expect (instance:method "object method call"). - should_be "Prototype instance, object method call" + to_be "Prototype instance, object method call" - it provides class method access with class dot notation: expect (Prototype.method (instance, "class method call")). - should_be "Prototype class, class method call" + to_be "Prototype class, class method call" - it allows new instance fields to be added: instance.newfield = "new" - expect (instance.newfield).should_be "new" + expect (instance.newfield).to_be "new" - it allows new instance methods to be added: instance.newmethod = function (self) return prototype (self) .. ", new instance method" end - expect (instance:newmethod ()).should_be "Prototype, new instance method" + expect (instance:newmethod ()).to_be "Prototype, new instance method" - it allows new class methods to be added: Prototype.newmethod = function (self) return prototype (self) .. ", new class method" end expect (Prototype.newmethod (instance)). - should_be "Prototype, new class method" + to_be "Prototype, new class method" - describe object method propagation: @@ -207,11 +203,11 @@ specify std.object: # :prototype is a method defined by the root object - it inherits prototype object methods: instance = Object {} - expect (instance:prototype ()).should_be "Object" + expect (instance:prototype ()).to_be "Object" - it propagates prototype methods to derived instances: Derived = Object {_type = "Derived"} instance = Derived {} - expect (instance:prototype ()).should_be "Derived" + expect (instance:prototype ()).to_be "Derived" - context with custom object methods: - before: bag = Object { @@ -225,13 +221,13 @@ specify std.object: } # :prototype is a method defined by the root object - it inherits prototype object methods: - expect (bag:prototype ()).should_be "bag" + expect (bag:prototype ()).to_be "bag" - it propagates prototype methods to derived instances: instance = bag {} - expect (instance:prototype ()).should_be "bag" + expect (instance:prototype ()).to_be "bag" - it supports method calls: - expect (bag:add "foo").should_be (bag) - expect (bag.foo).should_be (1) + expect (bag:add "foo").to_be (bag) + expect (bag.foo).to_be (1) # Metatable propagation is an important property of Object cloning, @@ -245,12 +241,12 @@ specify std.object: - context with no custom metamethods: - it inherits prototype object metatable: instance = Object {} - expect (getmetatable (instance)).should_be (root_mt) + expect (getmetatable (instance)).to_be (root_mt) - it propagates prototype metatable to derived instances: Derived = Object {_type = "Derived"} instance = Derived {} - expect (getmetatable (Derived)).should_not_be (root_mt) - expect (getmetatable (instance)).should_be (getmetatable (Derived)) + expect (getmetatable (Derived)).not_to_be (root_mt) + expect (getmetatable (instance)).to_be (getmetatable (Derived)) - context with custom metamethods: - before: base = require "std.base" @@ -259,15 +255,15 @@ specify std.object: __lt = function (a, b) return base.compare (a, b) < 0 end, } - it has it's own metatable: - expect (getmetatable (bag)).should_not_be (root_mt) + expect (getmetatable (bag)).not_to_be (root_mt) - it propagates prototype metatable to derived instances: instance = bag {} - expect (getmetatable (instance)).should_be (getmetatable (bag)) + expect (getmetatable (instance)).to_be (getmetatable (bag)) - it supports __lt calls: a, b = bag {"a"}, bag {"b"} - expect (a < b).should_be (true) - expect (a < a).should_be (false) - expect (a > b).should_be (false) + expect (a < b).to_be (true) + expect (a < a).to_be (false) + expect (a > b).to_be (false) - describe __totable: @@ -276,31 +272,31 @@ specify std.object: Derived = Object {_type = "Derived", "one", "two", three = true} - it returns a table: - expect (prototype (totable (Derived))).should_be "table" + expect (prototype (totable (Derived))).to_be "table" - it contains all non-hidden fields of object: - expect (totable (Derived)).should_contain.all_of {"one", "two", "three"} + expect (totable (Derived)).to_contain.all_of {"one", "two", "three"} - it does not contain any hidden fields of object: - expect (totable (Derived)).should_equal {"one", "two", three = true} + expect (totable (Derived)).to_equal {"one", "two", three = true} - describe __tostring: - before: obj = Object {_type = "Derived", "one", "two", "three"} - it returns a string: - expect (type (tostring (obj))).should_be "string" + expect (type (tostring (obj))).to_be "string" - it contains the type: - expect (tostring (Object {})).should_contain "Object" - expect (tostring (obj)).should_contain (prototype (obj)) + expect (tostring (Object {})).to_contain "Object" + expect (tostring (obj)).to_contain (prototype (obj)) - it contains the ordered array part elements: - expect (tostring (obj)).should_contain "one, two, three" + expect (tostring (obj)).to_contain "one, two, three" - it contains the ordered dictionary part elements: expect (tostring (Object {one = true, two = true, three = true})). - should_contain "one=true, three=true, two=true" + to_contain "one=true, three=true, two=true" expect (tostring (obj {one = true, two = true, three = true})). - should_contain "one=true, three=true, two=true" + to_contain "one=true, three=true, two=true" - it contains a ';' separator only when object has array and dictionary parts: - expect (tostring (obj)).should_not_contain ";" + expect (tostring (obj)).not_to_contain ";" expect (tostring (Object {one = true, two = true, three = true})). - should_not_contain ";" + not_to_contain ";" expect (tostring (obj {one = true, two = true, three = true})). - should_contain ";" + to_contain ";" diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index 03b7468..1cd3c42 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -44,22 +44,22 @@ specify std.optparse: - context by name: - it does not touch the global table: expect (show_apis {added_to="_G", by="std.optparse"}). - should_equal {} + to_equal {} - describe OptionParser: - it recognises the program name: - expect (parser.program).should_be "parseme" + expect (parser.program).to_be "parseme" - it recognises the version number: - expect (parser.version).should_be "0α1" + expect (parser.version).to_be "0α1" - it recognises the version text: expect (parser.versiontext). - should_match "^parseme .*Copyright .*NO WARRANTY%." + to_match "^parseme .*Copyright .*NO WARRANTY%." - it recognises the help text: | expect (parser.helptext). - should_match ("^Usage: parseme .*Banner .*Long .*Options:.*" .. + to_match ("^Usage: parseme .*Banner .*Long .*Options:.*" .. "Footer .*/issues>%.") - it diagnoses incorrect input text: - expect (OptionParser "garbage in").should_error "argument must match" + expect (OptionParser "garbage in").to_error "argument must match" - describe parser: - before: | @@ -98,108 +98,108 @@ specify std.optparse: - it responds to --version with version text: expect (parse {"--version"}). - should_match_output "^%s*parseme .*Copyright .*NO WARRANTY%.\n$" + to_match_output "^%s*parseme .*Copyright .*NO WARRANTY%.\n$" - it responds to --help with help text: | expect (parse {"--help"}). - should_match_output ("^%s*Usage: parseme .*Banner.*Long.*" .. + to_match_output ("^%s*Usage: parseme .*Banner.*Long.*" .. "Options:.*Footer.*/issues>%.\n$") - it leaves behind unrecognised short options: - expect (parse {"-x"}).should_output "args = { -x }\n" + expect (parse {"-x"}).to_output "args = { -x }\n" - it recognises short options: - expect (parse {"-b"}).should_output "opts = { b = true }\n" + expect (parse {"-b"}).to_output "opts = { b = true }\n" - it leaves behind unrecognised options: expect (parse {"--not-an-option"}). - should_output "args = { --not-an-option }\n" + to_output "args = { --not-an-option }\n" - it recognises long options: - expect (parse {"--long"}).should_output "opts = { long = true }\n" + expect (parse {"--long"}).to_output "opts = { long = true }\n" - it recognises long options with hyphens: expect (parse {"--another-long"}). - should_output "opts = { another_long = true }\n" + to_output "opts = { another_long = true }\n" - it recognises long options named after Lua keywords: - expect (parse {"--true"}).should_output "opts = { true = true }\n" + expect (parse {"--true"}).to_output "opts = { true = true }\n" - it recognises combined short and long option specs: - expect (parse {"-v"}).should_output "opts = { verbose = true }\n" - expect (parse {"--verbose"}).should_output "opts = { verbose = true }\n" + expect (parse {"-v"}).to_output "opts = { verbose = true }\n" + expect (parse {"--verbose"}).to_output "opts = { verbose = true }\n" - it recognises options with several spellings: - expect (parse {"-n"}).should_output "opts = { dry_run = true }\n" - expect (parse {"--dry-run"}).should_output "opts = { dry_run = true }\n" - expect (parse {"--dryrun"}).should_output "opts = { dry_run = true }\n" + expect (parse {"-n"}).to_output "opts = { dry_run = true }\n" + expect (parse {"--dry-run"}).to_output "opts = { dry_run = true }\n" + expect (parse {"--dryrun"}).to_output "opts = { dry_run = true }\n" - it recognises end of options marker: - expect (parse {"-- -n"}).should_output "args = { -n }\n" + expect (parse {"-- -n"}).to_output "args = { -n }\n" - context given an unhandled long option: - it leaves behind unmangled argument: expect (parse {"--not-an-option=with-an-argument"}). - should_output "args = { --not-an-option=with-an-argument }\n" + to_output "args = { --not-an-option=with-an-argument }\n" - context given an option with a required argument: - it records an argument to a long option following an '=' delimiter: expect (parse {"--name=Gary"}). - should_output "opts = { name = Gary }\n" + to_output "opts = { name = Gary }\n" - it records an argument to a short option without a space: expect (parse {"-uGary"}). - should_output "opts = { name = Gary }\n" + to_output "opts = { name = Gary }\n" - it records an argument to a long option following a space: expect (parse {"--name Gary"}). - should_output "opts = { name = Gary }\n" + to_output "opts = { name = Gary }\n" - it records an argument to a short option following a space: expect (parse {"-u Gary"}). - should_output "opts = { name = Gary }\n" + to_output "opts = { name = Gary }\n" - it diagnoses a missing argument: expect (parse {"--name"}). - should_contain_error "'--name' requires an argument" + to_contain_error "'--name' requires an argument" expect (parse {"-u"}). - should_contain_error "'-u' requires an argument" + to_contain_error "'-u' requires an argument" - context given an option with an optional argument: - it records an argument to a long option following an '=' delimiter: expect (parse {"--output=filename"}). - should_output "opts = { output = filename }\n" + to_output "opts = { output = filename }\n" - it records an argument to a short option without a space: expect (parse {"-ofilename"}). - should_output "opts = { output = filename }\n" + to_output "opts = { output = filename }\n" - it records an argument to a long option following a space: expect (parse {"--output filename"}). - should_output "opts = { output = filename }\n" + to_output "opts = { output = filename }\n" - it records an argument to a short option following a space: expect (parse {"-o filename"}). - should_output "opts = { output = filename }\n" + to_output "opts = { output = filename }\n" - it doesn't consume the following option: expect (parse {"--output -v"}). - should_output "opts = { output = true, verbose = true }\n" + to_output "opts = { output = true, verbose = true }\n" expect (parse {"-o -v"}). - should_output "opts = { output = true, verbose = true }\n" + to_output "opts = { output = true, verbose = true }\n" - context when splitting combined short options: - it separates non-argument options: expect (parse {"-bn"}). - should_output "opts = { b = true, dry_run = true }\n" + to_output "opts = { b = true, dry_run = true }\n" expect (parse {"-vbn"}). - should_output "opts = { b = true, dry_run = true, verbose = true }\n" + to_output "opts = { b = true, dry_run = true, verbose = true }\n" - it stops separating at a required argument option: expect (parse {"-vuname"}). - should_output "opts = { name = name, verbose = true }\n" + to_output "opts = { name = name, verbose = true }\n" expect (parse {"-vuob"}). - should_output "opts = { name = ob, verbose = true }\n" + to_output "opts = { name = ob, verbose = true }\n" - it stops separating at an optional argument option: expect (parse {"-vofilename"}). - should_output "opts = { output = filename, verbose = true }\n" + to_output "opts = { output = filename, verbose = true }\n" expect (parse {"-vobn"}). - should_output "opts = { output = bn, verbose = true }\n" + to_output "opts = { output = bn, verbose = true }\n" - it leaves behind unsplittable short options: - expect (parse {"-xvb"}).should_output "args = { -xvb }\n" - expect (parse {"-vxb"}).should_output "args = { -vxb }\n" - expect (parse {"-vbx"}).should_output "args = { -vbx }\n" + expect (parse {"-xvb"}).to_output "args = { -xvb }\n" + expect (parse {"-vxb"}).to_output "args = { -vxb }\n" + expect (parse {"-vbx"}).to_output "args = { -vbx }\n" - it separates short options before unsplittable options: expect (parse {"-vb -xvb"}). - should_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" + to_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" expect (parse {"-vb -vxb"}). - should_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" + to_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" expect (parse {"-vb -vbx"}). - should_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" + to_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" - it separates short options after unsplittable options: expect (parse {"-xvb -vb"}). - should_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" + to_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" expect (parse {"-vxb -vb"}). - should_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" + to_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" expect (parse {"-vbx -vb"}). - should_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" + to_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" - context with option defaults: - before: | @@ -215,18 +215,18 @@ specify std.optparse: end - it prefers supplied argument: expect (main {"-x", "-y"}). - should_equal { arg = {}, opts = { x=true, y=true }} + to_equal { arg = {}, opts = { x=true, y=true }} expect (main {"-x", "-y", "-z"}). - should_equal { arg = {}, opts = { x=true, y=true, z=true }} + to_equal { arg = {}, opts = { x=true, y=true, z=true }} expect (main {"-w", "-x", "-y"}). - should_equal { arg = {"-w"}, opts = { x=true, y=true }} + to_equal { arg = {"-w"}, opts = { x=true, y=true }} - it defers to default value: expect (main {}). - should_equal { arg = {}, opts = { x={"t"}, y=false }} + to_equal { arg = {}, opts = { x={"t"}, y=false }} expect (main {"-z"}). - should_equal { arg = {}, opts = { x={"t"}, y=false, z=true }} + to_equal { arg = {}, opts = { x={"t"}, y=false, z=true }} expect (main {"-w"}). - should_equal { arg = {"-w"}, opts = { x={"t"}, y=false }} + to_equal { arg = {"-w"}, opts = { x={"t"}, y=false }} - context with io.die: - before: | @@ -241,20 +241,20 @@ specify std.optparse: end - it prefers `prog.name` to `opts.program`: | code = [[prog = { file = "file", name = "name" }]] - expect (runscript (code)).should_fail_with "name: By 'eck!\n" + expect (runscript (code)).to_fail_with "name: By 'eck!\n" - it prefers `prog.file` to `opts.program`: | code = [[prog = { file = "file" }]] - expect (runscript (code)).should_fail_with "file: By 'eck!\n" + expect (runscript (code)).to_fail_with "file: By 'eck!\n" - it appends `prog.line` if any to `prog.file` over using `opts`: | code = [[ prog = { file = "file", line = 125 }; opts.line = 99]] - expect (runscript (code)).should_fail_with "file:125: By 'eck!\n" + expect (runscript (code)).to_fail_with "file:125: By 'eck!\n" - it prefixes `opts.program` if any: | - expect (runscript ("")).should_fail_with "program: By 'eck!\n" + expect (runscript ("")).to_fail_with "program: By 'eck!\n" - it appends `opts.line` if any, to `opts.program`: | code = [[opts.line = 99]] expect (runscript (code)). - should_fail_with "program:99: By 'eck!\n" + to_fail_with "program:99: By 'eck!\n" - context with io.warn: - before: | @@ -269,21 +269,21 @@ specify std.optparse: end - it prefers `prog.name` to `opts.program`: | code = [[prog = { file = "file", name = "name" }]] - expect (runscript (code)).should_output_error "name: Ayup!\n" + expect (runscript (code)).to_output_error "name: Ayup!\n" - it prefers `prog.file` to `opts.program`: | code = [[prog = { file = "file" }]] - expect (runscript (code)).should_output_error "file: Ayup!\n" + expect (runscript (code)).to_output_error "file: Ayup!\n" - it appends `prog.line` if any to `prog.file` over using `opts`: | code = [[ prog = { file = "file", line = 125 }; opts.line = 99]] expect (runscript (code)). - should_output_error "file:125: Ayup!\n" + to_output_error "file:125: Ayup!\n" - it prefixes `opts.program` if any: | - expect (runscript ("")).should_output_error "program: Ayup!\n" + expect (runscript ("")).to_output_error "program: Ayup!\n" - it appends `opts.line` if any, to `opts.program`: | code = [[opts.line = 99]] expect (runscript (code)). - should_output_error "program:99: Ayup!\n" + to_output_error "program:99: Ayup!\n" - describe parser:on: - before: | @@ -325,154 +325,154 @@ specify std.optparse: - it recognises short options: expect (parseargs ([["x"]], {"-x"})). - should_output "opts = { x = true }\n" + to_output "opts = { x = true }\n" - it recognises long options: expect (parseargs([["something"]], {"--something"})). - should_output "opts = { something = true }\n" + to_output "opts = { something = true }\n" - it recognises long options with hyphens: expect (parseargs([["some-thing"]], {"--some-thing"})). - should_output "opts = { some_thing = true }\n" + to_output "opts = { some_thing = true }\n" - it recognises long options named after Lua keywords: expect (parseargs ([["if"]], {"--if"})). - should_output "opts = { if = true }\n" + to_output "opts = { if = true }\n" - it recognises combined short and long option specs: expect (parseargs ([[{"x", "if"}]], {"-x"})). - should_output "opts = { if = true }\n" + to_output "opts = { if = true }\n" expect (parseargs ([[{"x", "if"}]], {"--if"})). - should_output "opts = { if = true }\n" + to_output "opts = { if = true }\n" - it recognises options with several spellings: expect (parseargs ([[{"x", "blah", "if"}]], {"-x"})). - should_output "opts = { if = true }\n" + to_output "opts = { if = true }\n" expect (parseargs ([[{"x", "blah", "if"}]], {"--blah"})). - should_output "opts = { if = true }\n" + to_output "opts = { if = true }\n" expect (parseargs ([[{"x", "blah", "if"}]], {"--if"})). - should_output "opts = { if = true }\n" + to_output "opts = { if = true }\n" - it recognises end of options marker: expect (parseargs ([["x"]], {"--", "-x"})). - should_output "args = { -x }\n" + to_output "args = { -x }\n" - context given an option with a required argument: - it records an argument to a short option without a space: expect (parseargs ([["x", parser.required]], {"-y", "-xarg", "-b"})). - should_contain_output "opts = { b = true, x = arg }" + to_contain_output "opts = { b = true, x = arg }" - it records an argument to a short option following a space: expect (parseargs ([["x", parser.required]], {"-y", "-x", "arg", "-b"})). - should_contain_output "opts = { b = true, x = arg }\n" + to_contain_output "opts = { b = true, x = arg }\n" - it records an argument to a long option following a space: expect (parseargs ([["this", parser.required]], {"--this", "arg"})). - should_output "opts = { this = arg }\n" + to_output "opts = { this = arg }\n" - it records an argument to a long option following an '=' delimiter: expect (parseargs ([["this", parser.required]], {"--this=arg"})). - should_output "opts = { this = arg }\n" + to_output "opts = { this = arg }\n" - it diagnoses a missing argument: expect (parseargs ([[{"x", "this"}, parser.required]], {"-x"})). - should_contain_error "'-x' requires an argument" + to_contain_error "'-x' requires an argument" expect (parseargs ([[{"x", "this"}, parser.required]], {"--this"})). - should_contain_error "'--this' requires an argument" + to_contain_error "'--this' requires an argument" - context with a boolean handler function: - it records a truthy argument: for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} do expect (parseargs ([["x", parser.required, parser.boolean]], {"-x", optarg})). - should_output "opts = { x = true }\n" + to_output "opts = { x = true }\n" end - it records a falsey argument: for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} do expect (parseargs ([["x", parser.required, parser.boolean]], {"-x", optarg})). - should_output "opts = { x = false }\n" + to_output "opts = { x = false }\n" end - context with a file handler function: - it records an existing file: expect (parseargs ([["x", parser.required, parser.file]], {"-x", "/dev/null"})). - should_output "opts = { x = /dev/null }\n" + to_output "opts = { x = /dev/null }\n" - it diagnoses a missing file: | expect (parseargs ([["x", parser.required, parser.file]], {"-x", "/this/file/does/not/exist"})). - should_contain_error "error: /this/file/does/not/exist: " + to_contain_error "error: /this/file/does/not/exist: " - context with a custom handler function: - it calls the handler: expect (parseargs ([["x", parser.required, function (p,o,a) return "custom" end ]], {"-x", "ignored"})). - should_output "opts = { x = custom }\n" + to_output "opts = { x = custom }\n" - it diagnoses a missing argument: expect (parseargs ([["x", parser.required, function (p,o,a) return "custom" end ]], {"-x"})). - should_contain_error "option '-x' requires an argument" + to_contain_error "option '-x' requires an argument" - context given an option with an optional argument: - it records an argument to a short option without a space: expect (parseargs ([["x", parser.optional]], {"-y", "-xarg", "-b"})). - should_contain_output "opts = { b = true, x = arg }" + to_contain_output "opts = { b = true, x = arg }" - it records an argument to a short option following a space: expect (parseargs ([["x", parser.optional]], {"-y", "-x", "arg", "-b"})). - should_contain_output "opts = { b = true, x = arg }\n" + to_contain_output "opts = { b = true, x = arg }\n" - it records an argument to a long option following a space: expect (parseargs ([["this", parser.optional]], {"--this", "arg"})). - should_output "opts = { this = arg }\n" + to_output "opts = { this = arg }\n" - it records an argument to a long option following an '=' delimiter: expect (parseargs ([["this", parser.optional]], {"--this=arg"})). - should_output "opts = { this = arg }\n" + to_output "opts = { this = arg }\n" - it does't consume the following option: expect (parseargs ([[{"x", "this"}, parser.optional]], {"-x", "-b"})). - should_output "opts = { b = true, this = true }\n" + to_output "opts = { b = true, this = true }\n" expect (parseargs ([[{"x", "this"}, parser.optional]], {"--this", "-b"})). - should_output "opts = { b = true, this = true }\n" + to_output "opts = { b = true, this = true }\n" - context with a boolean handler function: - it records a truthy argument: for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} do expect (parseargs ([["x", parser.optional, parser.boolean]], {"-x", optarg})). - should_output "opts = { x = true }\n" + to_output "opts = { x = true }\n" end - it records a falsey argument: for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} do expect (parseargs ([["x", parser.optional, parser.boolean]], {"-x", optarg})). - should_output "opts = { x = false }\n" + to_output "opts = { x = false }\n" end - it defaults to a truthy value: expect (parseargs ([["x", parser.optional, parser.boolean]], {"-x", "-b"})). - should_output "opts = { b = true, x = true }\n" + to_output "opts = { b = true, x = true }\n" - context with a file handler function: - it records an existing file: expect (parseargs ([["x", parser.optional, parser.file]], {"-x", "/dev/null"})). - should_output "opts = { x = /dev/null }\n" + to_output "opts = { x = /dev/null }\n" - it diagnoses a missing file: | expect (parseargs ([["x", parser.optional, parser.file]], {"-x", "/this/file/does/not/exist"})). - should_contain_error "error: /this/file/does/not/exist: " + to_contain_error "error: /this/file/does/not/exist: " - context with a custom handler function: - it calls the handler: expect (parseargs ([["x", parser.optional, function (p,o,a) return "custom" end ]], {"-x", "ignored"})). - should_output "opts = { x = custom }\n" + to_output "opts = { x = custom }\n" - it does not consume a following option: expect (parseargs ([["x", parser.optional, function (p,o,a) return a or "default" end ]], {"-x", "-b"})). - should_output "opts = { b = true, x = default }\n" + to_output "opts = { b = true, x = default }\n" - context when splitting combined short options: - it separates non-argument options: expect (parseargs ([["x"]], {"-xb"})). - should_output "opts = { b = true, x = true }\n" + to_output "opts = { b = true, x = true }\n" expect (parseargs ([["x"]], {"-vxb"})). - should_output "opts = { b = true, verbose = true, x = true }\n" + to_output "opts = { b = true, verbose = true, x = true }\n" - it stops separating at a required argument option: expect (parseargs ([[{"x", "this"}, parser.required]], {"-bxbit"})). - should_output "opts = { b = true, this = bit }\n" + to_output "opts = { b = true, this = bit }\n" - it stops separating at an optional argument option: expect (parseargs ([[{"x", "this"}, parser.optional]], {"-bxbit"})). - should_output "opts = { b = true, this = bit }\n" + to_output "opts = { b = true, this = bit }\n" diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index bb4d47f..f229b46 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -22,77 +22,77 @@ specify std.package: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - should_equal {} + to_equal {} - it contains apis from the core package table: expect (show_apis {from=base_module, not_in=this_module}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - it replaces no apis from the core package table: expect (show_apis {from=base_module, enhanced_in=this_module}). - should_equal {} + to_equal {} - context via the std module: - it adds apis to the core package table: expect (show_apis {added_to=base_module, by="std"}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - it replaces no apis from the core package table: expect (show_apis {from=base_module, enhanced_after='require "std"'}). - should_equal {} + to_equal {} - describe find: - before: path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) - it diagnoses missing arguments: | - expect (M.find ()).should_error "bad argument #1 to find" - expect (M.find (path)).should_error "bad argument #2 to find" + expect (M.find ()).to_error "bad argument #1 to find" + expect (M.find (path)).to_error "bad argument #2 to find" - it returns nil for unmatched element: - expect (M.find (path, "unmatchable")).should_be (nil) + expect (M.find (path, "unmatchable")).to_be (nil) - it returns the element index for a matched element: - expect (M.find (path, "end")).should_be (3) + expect (M.find (path, "end")).to_be (3) - it returns the element text for a matched element: i, element = M.find (path, "e.*n") - expect ({i, element}).should_equal {1, "begin"} + expect ({i, element}).to_equal {1, "begin"} - it accepts a search start element argument: i, element = M.find (path, "e.*n", 2) - expect ({i, element}).should_equal {3, "end"} + expect ({i, element}).to_equal {3, "end"} - it works with plain text search strings: - expect (M.find (path, "m%ddl.")).should_be (nil) + expect (M.find (path, "m%ddl.")).to_be (nil) i, element = M.find (path, "%ddl.", 1, ":plain") - expect ({i, element}).should_equal {2, "m%ddl."} + expect ({i, element}).to_equal {2, "m%ddl."} - describe insert: - it diagnoses missing arguments: | - expect (M.insert ()).should_error "bad argument #1 to insert" - expect (M.insert (path)).should_error "wrong number of arguments" + expect (M.insert ()).to_error "bad argument #1 to insert" + expect (M.insert (path)).to_error "wrong number of arguments" - it appends by default: expect (M.insert (path, "new")). - should_be (M.normalize ("begin", "middle", "end", "new")) + to_be (M.normalize ("begin", "middle", "end", "new")) - it prepends with pos set to 1: expect (M.insert (path, 1, "new")). - should_be (M.normalize ("new", "begin", "middle", "end")) + to_be (M.normalize ("new", "begin", "middle", "end")) - it can insert in the middle too: expect (M.insert (path, 2, "new")). - should_be (M.normalize ("begin", "new", "middle", "end")) + to_be (M.normalize ("begin", "new", "middle", "end")) expect (M.insert (path, 3, "new")). - should_be (M.normalize ("begin", "middle", "new", "end")) + to_be (M.normalize ("begin", "middle", "new", "end")) - it normalizes the returned path: path = table.concat ({"begin", "middle", "end"}, M.pathsep) expect (M.insert (path, "new")). - should_be (M.normalize ("begin", "middle", "end", "new")) + to_be (M.normalize ("begin", "middle", "end", "new")) expect (M.insert (path, 1, "./x/../end")). - should_be (M.normalize ("end", "begin", "middle")) + to_be (M.normalize ("end", "begin", "middle")) - describe mappath: - before: expected = require "std.string".split (path, M.pathsep) - it diagnoses bad arguments: | - expect (M.mappath ()).should_error "bad argument #1 to mappath" - expect (M.mappath ("")).should_error "bad argument #2 to mappath" + expect (M.mappath ()).to_error "bad argument #1 to mappath" + expect (M.mappath ("")).to_error "bad argument #2 to mappath" - it calls a function with each path element: t = {} M.mappath (path, function (e) t[#t + 1] = e end) - expect (t).should_equal (expected) + expect (t).to_equal (expected) - it passes additional arguments through: | reversed = {} for i = #expected, 1, -1 do @@ -100,73 +100,73 @@ specify std.package: end t = {} M.mappath (path, function (e, pos) table.insert (t, pos, e) end, 1) - expect (t).should_equal (reversed) + expect (t).to_equal (reversed) - describe normalize: - it diagnoses bad arguments: - expect (M.normalize ()).should_error "wrong number of arguments" + expect (M.normalize ()).to_error "wrong number of arguments" - context with a single element: - it strips redundant . directories: - expect (M.normalize "./x/./y/.").should_be (catfile (".", "x", "y")) + expect (M.normalize "./x/./y/.").to_be (catfile (".", "x", "y")) - it strips redundant .. directories: - expect (M.normalize "../x/../y/z/..").should_be (catfile ("..", "y")) + expect (M.normalize "../x/../y/z/..").to_be (catfile ("..", "y")) - it normalizes / to platform dirsep: - expect (M.normalize "/foo/bar").should_be (catfile ("", "foo", "bar")) + expect (M.normalize "/foo/bar").to_be (catfile ("", "foo", "bar")) - it normalizes ? to platform path_mark: expect (M.normalize "?.lua"). - should_be (catfile (".", M.path_mark .. ".lua")) + to_be (catfile (".", M.path_mark .. ".lua")) - it strips redundant trailing /: - expect (M.normalize "/foo/bar/").should_be (catfile ("", "foo", "bar")) + expect (M.normalize "/foo/bar/").to_be (catfile ("", "foo", "bar")) - it inserts missing ./ for relative paths: for _, path in ipairs {"x", "./x"} do - expect (M.normalize (path)).should_be (catfile (".", "x")) + expect (M.normalize (path)).to_be (catfile (".", "x")) end - context with multiple elements: - it strips redundant . directories: expect (M.normalize ("./x/./y/.", "x")). - should_be (catpath (catfile (".", "x", "y"), catfile (".", "x"))) + to_be (catpath (catfile (".", "x", "y"), catfile (".", "x"))) - it strips redundant .. directories: expect (M.normalize ("../x/../y/z/..", "x")). - should_be (catpath (catfile ("..", "y"), catfile (".", "x"))) + to_be (catpath (catfile ("..", "y"), catfile (".", "x"))) - it normalizes / to platform dirsep: expect (M.normalize ("/foo/bar", "x")). - should_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) + to_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) - it normalizes ? to platform path_mark: expect (M.normalize ("?.lua", "x")). - should_be (catpath (catfile (".", M.path_mark .. ".lua"), catfile (".", "x"))) + to_be (catpath (catfile (".", M.path_mark .. ".lua"), catfile (".", "x"))) - it strips redundant trailing /: expect (M.normalize ("/foo/bar/", "x")). - should_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) + to_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) - it inserts missing ./ for relative paths: for _, path in ipairs {"x", "./x"} do expect (M.normalize (path, "a")). - should_be (catpath (catfile (".", "x"), catfile (".", "a"))) + to_be (catpath (catfile (".", "x"), catfile (".", "a"))) end - it eliminates all but the first equivalent elements: expect (M.normalize (catpath ("1", "x", "2", "./x", "./2", "./x/../x"))). - should_be (catpath ("./1", "./x", "./2")) + to_be (catpath ("./1", "./x", "./2")) - describe remove: - it diagnoses bad arguments: | - expect (M.remove ()).should_error "bad argument #1 to remove" + expect (M.remove ()).to_error "bad argument #1 to remove" - it removes the last item by default: - expect (M.remove (path)).should_be (M.normalize ("begin", "middle")) + expect (M.remove (path)).to_be (M.normalize ("begin", "middle")) - it pops the first item with pos set to 1: - expect (M.remove (path, 1)).should_be (M.normalize ("middle", "end")) + expect (M.remove (path, 1)).to_be (M.normalize ("middle", "end")) - it can remove from the middle too: - expect (M.remove (path, 2)).should_be (M.normalize ("begin", "end")) + expect (M.remove (path, 2)).to_be (M.normalize ("begin", "end")) - it does not normalize the returned path: path = table.concat ({"begin", "middle", "end"}, M.pathsep) expect (M.remove (path)). - should_be (table.concat ({"begin", "middle"}, M.pathsep)) + 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.path_mark, M.execdir, M.igmark) - ).should_contain (package.config) + ).to_contain (package.config) diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index 9f96e3a..cce3680 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -9,22 +9,22 @@ specify std.set: - describe require: - it does not perturb the global namespace: expect (show_apis {added_to="_G", by="std.set"}). - should_equal {} + to_equal {} - describe construction: - it constructs a new set: s = Set {} - expect (s).should_not_be (Set) - expect (prototype (s)).should_be "Set" + expect (s).not_to_be (Set) + expect (prototype (s)).to_be "Set" - it initialises set with constructor parameters: t = Set {"foo", "bar", "bar"} - expect (t).should_equal (s) + expect (t).to_equal (s) - it serves as a prototype for new instances: obj = s {} - expect (prototype (obj)).should_be "Set" - expect (obj).should_equal (s) - expect (getmetatable (obj)).should_be (getmetatable (s)) + expect (prototype (obj)).to_be "Set" + expect (obj).to_equal (s) + expect (getmetatable (obj)).to_be (getmetatable (s)) - describe delete: @@ -32,21 +32,21 @@ specify std.set: - before: s = Set {"foo", "bar", "baz"} - it returns a set object: - expect (prototype (Set.delete (s, "foo"))).should_be "Set" + expect (prototype (Set.delete (s, "foo"))).to_be "Set" - it is destructive: Set.delete (s, "bar") - expect (s).should_not_have_member "bar" + expect (s).not_to_have_member "bar" - it returns the modified set: - expect (Set.delete (s, "baz")).should_not_have_member "baz" + expect (Set.delete (s, "baz")).not_to_have_member "baz" - it ignores removal of non-members: | clone = s {} - expect (Set.delete (s, "quux")).should_equal (clone) + expect (Set.delete (s, "quux")).to_equal (clone) - it deletes a member from the set: - expect (s).should_have_member "bar" + expect (s).to_have_member "bar" Set.delete (s, "bar") - expect (s).should_not_have_member "bar" + expect (s).not_to_have_member "bar" - it works with an empty set: - expect (Set.delete (Set {}, "quux")).should_equal (Set {}) + expect (Set.delete (Set {}, "quux")).to_equal (Set {}) - describe difference: @@ -56,26 +56,26 @@ specify std.set: - context when called as a Set module function: - it returns a set object: - expect (prototype (Set.difference (r, s))).should_be "Set" + expect (prototype (Set.difference (r, s))).to_be "Set" - it is non-destructive: Set.difference (r, s) - expect (r).should_equal (Set {"foo", "bar", "baz"}) - expect (s).should_equal (Set {"bar", "baz", "quux"}) + expect (r).to_equal (Set {"foo", "bar", "baz"}) + expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members of the first that are not in the second: - expect (Set.difference (r, s)).should_equal (Set {"foo"}) + expect (Set.difference (r, s)).to_equal (Set {"foo"}) - it coerces a table argument to a set: - expect (Set.difference (r, {"bar"})).should_equal (Set {"baz", "foo"}) + expect (Set.difference (r, {"bar"})).to_equal (Set {"baz", "foo"}) - context when called as a set metamethod: - it returns a set object: - expect (prototype (r - s)).should_be "Set" + expect (prototype (r - s)).to_be "Set" - it is non-destructive: q = r - s - expect (r).should_equal (Set {"foo", "bar", "baz"}) - expect (s).should_equal (Set {"bar", "baz", "quux"}) + expect (r).to_equal (Set {"foo", "bar", "baz"}) + expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members of the first that are not in the second: - expect (r - s).should_equal (Set {"foo"}) + expect (r - s).to_equal (Set {"foo"}) - it coerces a table argument to a set: - expect (r - {"bar"}).should_equal (Set {"baz", "foo"}) + expect (r - {"bar"}).to_equal (Set {"baz", "foo"}) - describe elems: @@ -87,11 +87,11 @@ specify std.set: t = {} for e in Set.elems (s) do table.insert (t, e) end table.sort (t) - expect (t).should_equal {"bar", "baz", "foo"} + expect (t).to_equal {"bar", "baz", "foo"} - it works for an empty set: t = {} for e in Set.elems (Set {}) do table.insert (t, e) end - expect (t).should_equal {} + expect (t).to_equal {} - describe insert: @@ -99,40 +99,40 @@ specify std.set: - before: s = Set {"foo"} - it returns a set object: - expect (prototype (Set.insert (s, "bar"))).should_be "Set" + expect (prototype (Set.insert (s, "bar"))).to_be "Set" - it is destructive: Set.insert (s, "bar") - expect (s).should_have_member "bar" + expect (s).to_have_member "bar" - it returns the modified set: - expect (Set.insert (s, "baz")).should_have_member "baz" + expect (Set.insert (s, "baz")).to_have_member "baz" - it ignores insertion of existing members: - expect (Set.insert (s, "foo")).should_equal (Set {"foo"}) + expect (Set.insert (s, "foo")).to_equal (Set {"foo"}) - it inserts a new member into the set: - expect (s).should_not_have_member "bar" + expect (s).not_to_have_member "bar" Set.insert (s, "bar") - expect (s).should_have_member "bar" + expect (s).to_have_member "bar" - it works with an empty set: - expect (Set.insert (Set {}, "foo")).should_equal (s) + expect (Set.insert (Set {}, "foo")).to_equal (s) - context when called as a set metamethod: - before: s = Set {"foo"} - it returns a set object: s["bar"] = true - expect (prototype (s)).should_be "Set" + expect (prototype (s)).to_be "Set" - it is destructive: s["bar"] = true - expect (s).should_have_member "bar" + expect (s).to_have_member "bar" - it ignores insertion of existing members: s["foo"] = true - expect (s).should_equal (Set {"foo"}) + expect (s).to_equal (Set {"foo"}) - it inserts a new member into the set: - expect (s).should_not_have_member "bar" + expect (s).not_to_have_member "bar" s["bar"] = true - expect (s).should_have_member "bar" + expect (s).to_have_member "bar" - it works with an empty set: s = Set {} s.foo = true - expect (s).should_equal (s) + expect (s).to_equal (s) - describe intersection: @@ -142,29 +142,29 @@ specify std.set: - context when called as a Set module function: - it returns a set object: - expect (prototype (Set.intersection (r, s))).should_be "Set" + expect (prototype (Set.intersection (r, s))).to_be "Set" - it is non-destructive: Set.intersection (r, s) - expect (r).should_equal (Set {"foo", "bar", "baz"}) - expect (s).should_equal (Set {"bar", "baz", "quux"}) + expect (r).to_equal (Set {"foo", "bar", "baz"}) + expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members common to both arguments: expect (Set.intersection (r, s)). - should_equal (Set {"bar", "baz"}) + to_equal (Set {"bar", "baz"}) - it coerces a table argument to a set: expect (Set.intersection (r, {"bar", "quux"})). - should_equal (Set {"bar"}) + to_equal (Set {"bar"}) - context when called as a set metamethod: - it returns a set object: q = r * s - expect (prototype (q)).should_be "Set" + expect (prototype (q)).to_be "Set" - it is non-destructive: q = r * s - expect (r).should_equal (Set {"foo", "bar", "baz"}) - expect (s).should_equal (Set {"bar", "baz", "quux"}) + expect (r).to_equal (Set {"foo", "bar", "baz"}) + expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members common to both arguments: - expect (r * s).should_equal (Set {"bar", "baz"}) + expect (r * s).to_equal (Set {"bar", "baz"}) - it coerces a table argument to a set: - expect (r * {"bar", "quux"}).should_equal (Set {"bar"}) + expect (r * {"bar", "quux"}).to_equal (Set {"bar"}) - describe member: @@ -172,20 +172,20 @@ specify std.set: - context when called as a Set module function: - it succeeds when set contains the given member: - expect (Set.member (s, "foo")).should_be (true) + expect (Set.member (s, "foo")).to_be (true) - it fails when set does not contain the given member: - expect (Set.member (s, "baz")).should_not_be (true) + expect (Set.member (s, "baz")).not_to_be (true) - it works with the empty set: s = Set {} - expect (Set.member (s, "foo")).should_not_be (true) + expect (Set.member (s, "foo")).not_to_be (true) - context when called as a set metamethod: - it succeeds when set contains the given member: - expect (s["foo"]).should_be (true) + expect (s["foo"]).to_be (true) - it fails when set does not contain the given member: - expect (s["baz"]).should_not_be (true) + expect (s["baz"]).not_to_be (true) - it works with the empty set: s = Set {} - expect (s["foo"]).should_not_be (true) + expect (s["foo"]).not_to_be (true) - describe proper_subset: @@ -195,25 +195,25 @@ specify std.set: - context when called as a Set module function: - it succeeds when set contains all elements of another: - expect (Set.proper_subset (s, r)).should_be (true) + expect (Set.proper_subset (s, r)).to_be (true) - it fails when two sets are equal: r = s {} - expect (Set.proper_subset (s, r)).should_be (false) + expect (Set.proper_subset (s, r)).to_be (false) - it fails when set does not contain all elements of another: s = s + Set {"quux"} - expect (Set.proper_subset (r, s)).should_be (false) + expect (Set.proper_subset (r, s)).to_be (false) - it coerces a table argument to a set: - expect (Set.proper_subset (s, {"foo", "bar", "baz"})).should_be (true) - expect (Set.proper_subset (s, {"foo"})).should_be (false) + expect (Set.proper_subset (s, {"foo", "bar", "baz"})).to_be (true) + expect (Set.proper_subset (s, {"foo"})).to_be (false) - context when called as a set metamethod: - it succeeds when set contains all elements of another: - expect (s < r).should_be (true) + expect (s < r).to_be (true) - it fails when two sets are equal: r = s {} - expect (s < r).should_be (false) + expect (s < r).to_be (false) - it fails when set does not contain all elements of another: s = s + Set {"quux"} - expect (r < s).should_be (false) + expect (r < s).to_be (false) - describe subset: @@ -223,25 +223,25 @@ specify std.set: - context when called as a Set module function: - it succeeds when set contains all elements of another: - expect (Set.subset (s, r)).should_be (true) + expect (Set.subset (s, r)).to_be (true) - it succeeds when two sets are equal: r = s {} - expect (Set.subset (s, r)).should_be (true) + expect (Set.subset (s, r)).to_be (true) - it fails when set does not contain all elements of another: s = s + Set {"quux"} - expect (Set.subset (r, s)).should_be (false) + expect (Set.subset (r, s)).to_be (false) - it coerces a table argument to a set: - expect (Set.subset (s, {"foo", "bar", "baz"})).should_be (true) - expect (Set.subset (s, {"foo"})).should_be (false) + expect (Set.subset (s, {"foo", "bar", "baz"})).to_be (true) + expect (Set.subset (s, {"foo"})).to_be (false) - context when called as a set metamethod: - it succeeds when set contains all elements of another: - expect (s <= r).should_be (true) + expect (s <= r).to_be (true) - it succeeds when two sets are equal: r = s {} - expect (s <= r).should_be (true) + expect (s <= r).to_be (true) - it fails when set does not contain all elements of another: s = s + Set {"quux"} - expect (r <= s).should_be (false) + expect (r <= s).to_be (false) - describe symmetric_difference: @@ -252,28 +252,28 @@ specify std.set: - context when called as a Set module function: - it returns a set object: expect (prototype (Set.symmetric_difference (r, s))). - should_be "Set" + to_be "Set" - it is non-destructive: Set.symmetric_difference (r, s) - expect (r).should_equal (Set {"foo", "bar", "baz"}) - expect (s).should_equal (Set {"bar", "baz", "quux"}) + expect (r).to_equal (Set {"foo", "bar", "baz"}) + expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: expect (Set.symmetric_difference (r, s)). - should_equal (Set {"foo", "quux"}) + to_equal (Set {"foo", "quux"}) - it coerces a table argument to a set: expect (Set.symmetric_difference (r, {"bar"})). - should_equal (Set {"baz", "foo"}) + to_equal (Set {"baz", "foo"}) - context when called as a set metamethod: - it returns a set object: - expect (prototype (r / s)).should_be "Set" + expect (prototype (r / s)).to_be "Set" - it is non-destructive: q = r / s - expect (r).should_equal (Set {"foo", "bar", "baz"}) - expect (s).should_equal (Set {"bar", "baz", "quux"}) + expect (r).to_equal (Set {"foo", "bar", "baz"}) + expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: - expect (r / s).should_equal (Set {"foo", "quux"}) + expect (r / s).to_equal (Set {"foo", "quux"}) - it coerces a table argument to a set: - expect (r / {"bar"}).should_equal (Set {"baz", "foo"}) + expect (r / {"bar"}).to_equal (Set {"baz", "foo"}) - describe union: @@ -283,29 +283,29 @@ specify std.set: - context when called as a Set module function: - it returns a set object: - expect (prototype (Set.union (r, s))).should_be "Set" + expect (prototype (Set.union (r, s))).to_be "Set" - it is non-destructive: Set.union (r, s) - expect (r).should_equal (Set {"foo", "bar", "baz"}) - expect (s).should_equal (Set {"bar", "baz", "quux"}) + expect (r).to_equal (Set {"foo", "bar", "baz"}) + expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: expect (Set.union (r, s)). - should_equal (Set {"foo", "bar", "baz", "quux"}) + to_equal (Set {"foo", "bar", "baz", "quux"}) - it coerces a table argument to a set: expect (Set.union (r, {"quux"})). - should_equal (Set {"foo", "bar", "baz", "quux"}) + to_equal (Set {"foo", "bar", "baz", "quux"}) - context when called as a set metamethod: - it returns a set object: - expect (prototype (r + s)).should_be "Set" + expect (prototype (r + s)).to_be "Set" - it is non-destructive: q = r + s - expect (r).should_equal (Set {"foo", "bar", "baz"}) - expect (s).should_equal (Set {"bar", "baz", "quux"}) + expect (r).to_equal (Set {"foo", "bar", "baz"}) + expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: - expect (r + s).should_equal (Set {"foo", "bar", "baz", "quux"}) + expect (r + s).to_equal (Set {"foo", "bar", "baz", "quux"}) - it coerces a table argument to a set: expect (r + {"quux"}). - should_equal (Set {"foo", "bar", "baz", "quux"}) + to_equal (Set {"foo", "bar", "baz", "quux"}) - describe __totable: @@ -313,13 +313,13 @@ specify std.set: s = Set {"foo", "bar", "baz"} - it returns a table: - expect (prototype (totable (s))).should_be "table" + expect (prototype (totable (s))).to_be "table" - it contains all non-hidden fields of object: - expect (totable (s)).should_contain.all_of {"foo", "bar", "baz"} + expect (totable (s)).to_contain.all_of {"foo", "bar", "baz"} - it contains fields of set in order: - expect (totable (s)).should_equal {"bar", "baz", "foo"} + expect (totable (s)).to_equal {"bar", "baz", "foo"} - it does not contain any hidden fields of object: - expect (totable (s)).should_equal {"bar", "baz", "foo"} + expect (totable (s)).to_equal {"bar", "baz", "foo"} - describe __tostring: @@ -327,8 +327,8 @@ specify std.set: s = Set {"foo", "bar", "baz"} - it returns a string: - expect (type (tostring (s))).should_be "string" + expect (type (tostring (s))).to_be "string" - it shows the type name: - expect (tostring (s)).should_contain "Set" + expect (tostring (s)).to_contain "Set" - it contains the ordered set elements: - expect (tostring (s)).should_contain "bar, baz, foo" + expect (tostring (s)).to_contain "bar, baz, foo" diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index f8f758c..e4754b4 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -3,10 +3,10 @@ specify std: - before: std = require "std" - it has no submodules on initial load: - expect (std).should_equal {version = std.version} + expect (std).to_equal {version = std.version} - it loads submodules on demand: lazy = std.set - expect (lazy).should_be (require "std.set") + expect (lazy).to_be (require "std.set") - it loads submodule functions on demand: expect (std.object.prototype (std.set {"Lazy"})). - should_be "Set" + to_be "Set" diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index 6851dcd..2e0539d 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -8,53 +8,53 @@ specify std.strbuf: - describe require: - it does not perturb the global namespace: expect (show_apis {added_to="_G", by="std.strbuf"}). - should_equal {} + to_equal {} - describe construction: - context from StrBuf clone method: - it constructs a new strbuf: b = StrBuf:clone {} - expect (b).should_not_be (StrBuf) - expect (object.type (b)).should_be "StrBuf" + expect (b).not_to_be (StrBuf) + expect (object.type (b)).to_be "StrBuf" - it reuses the StrBuf metatable: a, b = StrBuf:clone {"a"}, StrBuf:clone {"b"} - expect (getmetatable (a)).should_be (getmetatable (b)) + expect (getmetatable (a)).to_be (getmetatable (b)) - it initialises strbuf with constructor parameters: a = StrBuf:clone {"foo", "bar"} - expect (a).should_equal (b) + expect (a).to_equal (b) - it serves as a prototype for new instances: obj = b:clone {} - expect (object.type (obj)).should_be "StrBuf" - expect (obj).should_equal (b) - expect (getmetatable (obj)).should_be (getmetatable (b)) + expect (object.type (obj)).to_be "StrBuf" + expect (obj).to_equal (b) + expect (getmetatable (obj)).to_be (getmetatable (b)) # StrBuf {args} is just syntactic sugar for StrBuf:clone {args} - context from StrBuf object prototype: - it constructs a new strbuf: b = StrBuf {} - expect (b).should_not_be (StrBuf) - expect (object.type (b)).should_be "StrBuf" + expect (b).not_to_be (StrBuf) + expect (object.type (b)).to_be "StrBuf" - it reuses the StrBuf metatable: a, b = StrBuf {"a"}, StrBuf {"b"} - expect (getmetatable (a)).should_be (getmetatable (b)) + expect (getmetatable (a)).to_be (getmetatable (b)) - it initialises strbuf with constructor parameters: a = StrBuf:clone {"foo", "bar"} - expect (a).should_equal (b) + expect (a).to_equal (b) - it serves as a prototype for new instances: obj = b {} - expect (object.type (obj)).should_be "StrBuf" - expect (obj).should_equal (b) - expect (getmetatable (obj)).should_be (getmetatable (b)) + expect (object.type (obj)).to_be "StrBuf" + expect (obj).to_equal (b) + expect (getmetatable (obj)).to_be (getmetatable (b)) - describe tostring: - it can be called from strbuf module: - expect (StrBuf.tostring (b)).should_be "foobar" + expect (StrBuf.tostring (b)).to_be "foobar" - it can be called as a strbuf object method: - expect (b:tostring ()).should_be "foobar" + expect (b:tostring ()).to_be "foobar" - it can be called as a strbuf metabethod: - expect (tostring (b)).should_be "foobar" + expect (tostring (b)).to_be "foobar" - describe concat: @@ -62,16 +62,16 @@ specify std.strbuf: b = StrBuf {"foo", "bar"} - it can be called from strbuf module: b = StrBuf.concat (b, "baz") - expect (object.type (b)).should_be "StrBuf" - expect (StrBuf.tostring (b)).should_be "foobarbaz" + expect (object.type (b)).to_be "StrBuf" + expect (StrBuf.tostring (b)).to_be "foobarbaz" - it can be called as a strbuf object method: b:concat "baz" - expect (object.type (b)).should_be "StrBuf" - expect (b:tostring()).should_be "foobarbaz" + expect (object.type (b)).to_be "StrBuf" + expect (b:tostring()).to_be "foobarbaz" - it can be called as a strbuf metamethod: b = b .. "baz" - expect (object.type (b)).should_be "StrBuf" - expect (tostring (b)).should_be "foobarbaz" + expect (object.type (b)).to_be "StrBuf" + expect (tostring (b)).to_be "foobarbaz" - describe __totable: @@ -79,8 +79,8 @@ specify std.strbuf: totable = (require "std.table").totable - it returns a table: - expect (object.type (totable (b))).should_be "table" + expect (object.type (totable (b))).to_be "table" - it contains all non-hidden fields of object: - expect (totable (b)).should_contain.all_of {"foo", "bar"} + expect (totable (b)).to_contain.all_of {"foo", "bar"} - it does not contain any hidden fields of object: - expect (totable (b)).should_equal {"foo", "bar"} + expect (totable (b)).to_equal {"foo", "bar"} diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index e061eab..ac53ffb 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -24,7 +24,7 @@ before: | extend_metamethods = { "__append", "__concat" } enhance_metamethods = { "__index" } - -- 'should_contain' will match keys as well as values :) + -- 'to_contain' will match keys as well as values :) all_apis = {} for _, s in ipairs (std_globals) do all_apis[s] = true end for _, s in ipairs (enhance_globals) do all_apis[s] = true end @@ -41,46 +41,46 @@ specify std.string: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - should_equal {} + to_equal {} - it contains apis from the core string table: expect (show_apis {from=base_module, not_in=this_module}). - should_contain.a_permutation_of (all_apis) + to_contain.a_permutation_of (all_apis) - it enhances some apis from the core string table: expect (show_apis {from=base_module, enhanced_in=this_module}). - should_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (enhance_base) - context via the std module: - it adds apis to the global table: expect (show_apis {added_to=global_table, by="std"}). - should_contain.all_of (std_globals) + to_contain.all_of (std_globals) - it adds apis to the core string table: expect (show_apis {added_to=base_module, by="std"}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - it adds methods to the string metatable: expect (show_apis {added_to="getmetatable ('')", by="std"}). - should_contain.a_permutation_of (extend_metamethods) + to_contain.a_permutation_of (extend_metamethods) - it replaces some entries in the string metatable: expect (show_apis {from="getmetatable ('')", enhanced_after='require "std"'}). - should_contain.a_permutation_of (enhance_metamethods) + to_contain.a_permutation_of (enhance_metamethods) - it replaces some apis in the core string table: expect (show_apis {from=base_module, enhanced_after='require "std"'}). - should_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (enhance_base) - describe ..: - it concatenates string arguments: target = "a string \n\n another string" - expect (subject .. " another string").should_be (target) + expect (subject .. " another string").to_be (target) - "it stringifies non-string arguments": argument = { "a table" } - expect (subject .. argument).should_match (string.format ("%s{1=a table}", subject)) + expect (subject .. argument).to_match (string.format ("%s{1=a table}", subject)) - it stringifies nil arguments: argument = nil - expect (subject .. argument).should_be (string.format ("%s%s", subject, tostring (argument))) + expect (subject .. argument).to_be (string.format ("%s%s", subject, tostring (argument))) - the original subject is not perturbed: original = subject newstring = subject .. " concatenate something" - expect (subject).should_be (original) + expect (subject).to_be (original) - describe assert: @@ -91,18 +91,18 @@ specify std.string: f = M.caps - it capitalises words of a string: target = "A String \n\n" - expect (f (subject)).should_be (target) + expect (f (subject)).to_be (target) - it changes only the first letter of each word: - expect (f "a stRiNg").should_be "A StRiNg" + expect (f "a stRiNg").to_be "A StRiNg" - it is available as a string metamethod: - expect (("a stRiNg"):caps ()).should_be "A StRiNg" + expect (("a stRiNg"):caps ()).to_be "A StRiNg" - the original subject is not perturbed: original = subject newstring = f (subject) - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") - describe chomp: @@ -110,19 +110,19 @@ specify std.string: f = M.chomp target = "a string \n" - it removes a single trailing newline from a string: - expect (f (subject)).should_be (target) + expect (f (subject)).to_be (target) - it does not change a string with no trailing newline: subject = "a string " - expect (f (subject)).should_be (subject) + expect (f (subject)).to_be (subject) - it is available as a string metamethod: - expect (subject:chomp ()).should_be (target) + expect (subject:chomp ()).to_be (target) - the original subject is not perturbed: original = subject newstring = f (subject) - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") - describe escape_pattern: @@ -145,18 +145,18 @@ specify std.string: target = target .. s end - "it inserts a % before any non-alphanumeric in a string": - expect (f (subject)).should_be (target) + expect (f (subject)).to_be (target) - it is available as a string metamethod: - expect (subject:escape_pattern ()).should_be (target) + expect (subject:escape_pattern ()).to_be (target) - legacy escapePattern call is the same function: - expect (M.escapePattern).should_be (f) + expect (M.escapePattern).to_be (f) - the original subject is not perturbed: original = subject newstring = f (subject) - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") - describe escape_shell: @@ -172,18 +172,18 @@ specify std.string: target = target .. s end - "it inserts a \\ before any shell metacharacters": - expect (f (subject)).should_be (target) + expect (f (subject)).to_be (target) - it is available as a string metamethod: - expect (subject:escape_shell ()).should_be (target) + expect (subject:escape_shell ()).to_be (target) - legacy escapeShell call is the same function: - expect (M.escapeShell).should_be (f) + expect (M.escapeShell).to_be (f) - the original subject is not perturbed: original = subject newstring = f (subject) - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") - describe finds: @@ -194,25 +194,25 @@ specify std.string: - before: target = { { 1, 2; capt = { "a", "b" } }, { 3, 4; capt = { "c", "d" } } } - it creates a list of pattern captures: - expect ({f (subject, "(.)(.)")}).should_equal ({ target }) + expect ({f (subject, "(.)(.)")}).to_equal ({ target }) - it is available as a string metamethod: - expect ({subject:finds ("(.)(.)")}).should_equal ({ target }) + expect ({subject:finds ("(.)(.)")}).to_equal ({ target }) - it creates an empty list where no captures are matched: target = {} - expect ({f (subject, "(x)")}).should_equal ({ 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")}).should_equal ({ target }) + 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)}).should_equal ({ target }) + expect ({f ("garbage" .. subject, "(.)(.)", 8)}).to_equal ({ target }) - the original subject is not perturbed: original = subject newstring = f (subject, "...") - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") # FIXME: This looks like a misfeature to me, let's remove it! @@ -221,16 +221,16 @@ specify std.string: subject = "string: %s, number: %d" f = M.format - it returns a single argument without attempting formatting: - expect (f (subject)).should_be (subject) + expect (f (subject)).to_be (subject) - it is available as a string metamethod: - expect (subject:format ()).should_be (subject) + expect (subject:format ()).to_be (subject) - the original subject is not perturbed: original = subject newstring = f (subject) - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f (nil, "arg")).should_error ("string expected") - expect (f ({"a table"}, "arg")).should_error ("string expected") + expect (f (nil, "arg")).to_error ("string expected") + expect (f ({"a table"}, "arg")).to_error ("string expected") - describe ltrim: @@ -239,20 +239,20 @@ specify std.string: f = M.ltrim - it removes whitespace from the start of a string: target = "a short string \t\r\n " - expect (f (subject)).should_equal (target) + 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]+")).should_equal (target) + 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]+")).should_equal (target) + expect (subject:ltrim ("[ \t\n]+")).to_equal (target) - the original subject is not perturbed: original = subject newstring = f (subject, "%W") - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") - describe numbertosi: @@ -266,12 +266,12 @@ specify std.string: m = 10 * (10 ^ n) table.insert (subject, f (m)) end - expect (subject).should_equal (target) + expect (subject).to_equal (target) - it coerces string arguments to a number: - expect (f "1000").should_be "1k" + expect (f "1000").to_be "1k" - "it diagnoses non-numeric arguments": - expect (f ()).should_error ("attempt to perform arithmetic") - expect (f {"a table"}).should_error ("number expected") + expect (f ()).to_error ("attempt to perform arithmetic") + expect (f {"a table"}).to_error ("number expected") - describe ordinal_suffix: @@ -289,14 +289,14 @@ specify std.string: table.insert (target, n .. suffix) table.insert (subject, n .. f (n)) end - expect (subject).should_equal (target) + expect (subject).to_equal (target) - legacy ordinalSuffix call is the same function: - expect (M.ordinalSuffix).should_be (f) + expect (M.ordinalSuffix).to_be (f) - it coerces string arguments to a number: - expect (f "-91").should_be "st" + expect (f "-91").to_be "st" - "it diagnoses non-numeric arguments": - expect (f ()).should_error ("number expected") - expect (f {"a table"}).should_error ("number expected") + expect (f ()).to_error ("number expected") + expect (f {"a table"}).to_error ("number expected") - describe pad: @@ -309,38 +309,38 @@ specify std.string: subject = "short string" - it right pads a string to the given width with spaces: target = "short string " - expect (f (subject, width)).should_be (target) + 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)).should_be (target) + expect (f (subject, width)).to_be (target) - it is available as a string metamethod: target = "short string " - expect (subject:pad (width)).should_be (target) + 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)).should_be (target) + 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)).should_be (target) + expect (f (subject, width)).to_be (target) - it is available as a string metamethod: target = "a string that's long" - expect (subject:pad (width)).should_be (target) + expect (subject:pad (width)).to_be (target) - the original subject is not perturbed: original = subject newstring = f (subject, width) - expect (subject).should_be (original) + expect (subject).to_be (original) - "it coerces non-string arguments to a string": - expect (f ({ "a table" }, width)).should_contain "a table" + expect (f ({ "a table" }, width)).to_contain "a table" - "it diagnoses non-numeric width arguments": - expect (f (subject, nil)).should_error ("number expected") - expect (f (subject, {"a table"})).should_error ("number expected") + expect (f (subject, nil)).to_error ("number expected") + expect (f (subject, {"a table"})).to_error ("number expected") - describe pickle: @@ -350,36 +350,36 @@ specify std.string: - before: f = M.prettytostring - it renders nil exactly like system tostring: - expect (f (nil)).should_be (tostring (nil)) + expect (f (nil)).to_be (tostring (nil)) - it renders booleans exactly like system tostring: - expect (f (true)).should_be (tostring (true)) - expect (f (false)).should_be (tostring (false)) + 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)).should_be (tostring (n)) + expect (f (n)).to_be (tostring (n)) - it renders functions exactly like system tostring: - expect (f (f)).should_be (tostring (f)) + expect (f (f)).to_be (tostring (f)) - it renders strings with format "%q" styling: s = "a string" - expect (f (s)).should_be (string.format ("%q", s)) + expect (f (s)).to_be (string.format ("%q", s)) - it renders empty tables as a pair of braces: - expect (f {}).should_be ("{\n}") + expect (f {}).to_be ("{\n}") - it renders an array prettily: a = {"one", "two", "three"} expect (f (a, "")). - should_be '{\n[1] = "one",\n[2] = "two",\n[3] = "three",\n}' + 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, "")). - should_be '{\none = true,\nthree =\n{\n[1] = 3,\n},\ntwo = 2,\n}' + 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, "")). - should_be '{\nfive = 1,\nfour = 2,\none = 3,\nthree = 4,\ntwo = 5,\n}' + 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, "")). - should_be '{\n["?"] = 1,\n["[]"] = 1,\n_ = 0,\n["a-key"] = 1,\nword = 0,\n}' + to_be '{\n["?"] = 1,\n["[]"] = 1,\n_ = 0,\n["a-key"] = 1,\nword = 0,\n}' - describe render: @@ -394,20 +394,20 @@ specify std.string: f = M.rtrim - it removes whitespace from the end of a string: target = " \t\r\n a short string" - expect (f (subject)).should_equal (target) + 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]+")).should_equal (target) + 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]+")).should_equal (target) + expect (subject:rtrim ("[ \t\n]+")).to_equal (target) - the original subject is not perturbed: original = subject newstring = f (subject, "%W") - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") - describe split: @@ -416,37 +416,37 @@ specify std.string: subject = table.concat (target, ", ") f = M.split - it returns a one-element list for an empty string: - expect (f ("", ", ")).should_equal {""} + expect (f ("", ", ")).to_equal {""} - it makes a table of substrings delimited by a separator: - expect (f (subject, ", ")).should_equal (target) + expect (f (subject, ", ")).to_equal (target) - it returns n+1 elements for n separators: - expect (f (subject, "zero")).should_have_size (1) - expect (f (subject, "c")).should_have_size (2) - expect (f (subject, "s")).should_have_size (3) - expect (f (subject, "t")).should_have_size (4) - expect (f (subject, "e")).should_have_size (5) + 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")).should_equal {"x", "", "xy"} + expect (f ("xyzyzxy", "yz")).to_equal {"x", "", "xy"} - it returns an empty string element when starting with separator: - expect (f ("xyzyzxy", "xyz")).should_equal {"", "yzxy"} + expect (f ("xyzyzxy", "xyz")).to_equal {"", "yzxy"} - it returns an empty string element when ending with separator: - expect (f ("xyzyzxy", "zxy")).should_equal {"xyzy", ""} + expect (f ("xyzyzxy", "zxy")).to_equal {"xyzy", ""} - it returns a table of 1-character strings for "" separator: - expect (f ("abcdef", "")).should_equal {"", "a", "b", "c", "d", "e", "f", ""} + expect (f ("abcdef", "")).to_equal {"", "a", "b", "c", "d", "e", "f", ""} - it is available as a string metamethod: - expect (subject:split ", ").should_equal (target) + expect (subject:split ", ").to_equal (target) expect (("/foo/bar/baz.quux"):split "/"). - should_equal {"", "foo", "bar", "baz.quux"} + to_equal {"", "foo", "bar", "baz.quux"} - the original subject is not perturbed: original = subject newstring = f (subject, "e") - expect (subject).should_be (original) + expect (subject).to_be (original) - it takes a Lua pattern as a separator: expect (f (subject, "%s+")). - should_equal {"first,", "the", "second", "one,", "final", "entry"} + to_equal {"first,", "the", "second", "one,", "final", "entry"} - it diagnoses non-string arguments: - expect (f (nil, ",")).should_error ("string expected") - expect (f ({"a table"}, ",")).should_error ("string expected") + expect (f (nil, ",")).to_error ("string expected") + expect (f ({"a table"}, ",")).to_error ("string expected") - describe tfind: @@ -455,26 +455,26 @@ specify std.string: f = M.tfind - it creates a list of pattern captures: target = { 1, 3, { "a", "b", "c" } } - expect ({f (subject, "(.)(.)(.)")}).should_equal (target) + 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)")}).should_equal (target) + 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")}).should_equal (target) + 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)}).should_equal (target) + 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)}).should_equal (target) + expect ({("garbage" .. subject):tfind ("(.)(.)(.)", 8)}).to_equal (target) - the original subject is not perturbed: original = subject newstring = f (subject, "...") - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") - describe tostring: @@ -486,20 +486,20 @@ specify std.string: f = M.trim - it removes whitespace from each end of a string: target = "a short string" - expect (f (subject)).should_equal (target) + expect (f (subject)).to_equal (target) - it supports custom removal patterns: target = "\r\n a short string \t\r" - expect (f (subject, "[ \t\n]+")).should_equal (target) + 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]+")).should_equal (target) + expect (subject:trim ("[ \t\n]+")).to_equal (target) - the original subject is not perturbed: original = subject newstring = f (subject, "%W") - expect (subject).should_be (original) + expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") - describe wrap: @@ -516,21 +516,21 @@ specify std.string: "-2013 (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)).should_be (target) + 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" .. "-2013 (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)).should_be (target) + 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-2013 (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)).should_be (target) + 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" .. @@ -539,16 +539,16 @@ specify std.string: "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)).should_be (target) + expect (f (subject, 64, 2, 4)).to_be (target) - it is available as a string metamethod: - expect (subject:wrap (64, 2, 4)).should_be (target) + expect (subject:wrap (64, 2, 4)).to_be (target) - the original subject is not perturbed: original = subject newstring = f (subject, 55, 5) - expect (subject).should_be (original) + expect (subject).to_be (original) - it diagnoses indent greater than line width: - expect (f (subject, 10, 12)).should_error ("less than the line width") - expect (f (subject, 99, 99)).should_error ("less than the line width") + expect (f (subject, 10, 12)).to_error ("less than the line width") + expect (f (subject, 99, 99)).to_error ("less than the line width") - it diagnoses non-string arguments: - expect (f ()).should_error ("string expected") - expect (f {"a table"}).should_error ("string expected") + expect (f ()).to_error ("string expected") + expect (f {"a table"}).to_error ("string expected") diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 5ad22c9..3096b92 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -14,7 +14,7 @@ before: | enhance_base = { "sort" } - -- 'should_contain' will match keys as well as values :) + -- 'to_contain' will match keys as well as values :) all_apis = {} for _, s in ipairs (std_globals) do all_apis[s] = true end for _, s in ipairs (extend_base) do all_apis[s] = true end @@ -35,24 +35,24 @@ specify std.table: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - should_equal {} + to_equal {} - it contains apis from the core table table: expect (show_apis {from=base_module, not_in=this_module}). - should_contain.a_permutation_of (all_apis) + to_contain.a_permutation_of (all_apis) - it enhances some apis from the core table table: expect (show_apis {from=base_module, enhanced_in=this_module}). - should_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (enhance_base) - context via the std module: - it adds apis to the global table: expect (show_apis {added_to=global_table, by="std"}). - should_contain.all_of (std_globals) + to_contain.all_of (std_globals) - it adds apis to the core table table: expect (show_apis {added_to=base_module, by="std"}). - should_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (extend_base) - it replaces some apis in the core table table: expect (show_apis {from=base_module, enhanced_after='require "std"'}). - should_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (enhance_base) - describe clone: @@ -60,37 +60,37 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } f = M.clone - it does not just return the subject: - expect (f (subject)).should_not_be (subject) + expect (f (subject)).not_to_be (subject) - it does copy the subject: - expect (f (subject)).should_equal (subject) + expect (f (subject)).to_equal (subject) - it only makes a shallow copy: - expect (f (subject).k1).should_be (subject.k1) + expect (f (subject).k1).to_be (subject.k1) - the original subject is not perturbed: target = { k1 = subject.k1, k2 = subject.k2, k3 = subject.k3 } copy = f (subject) - expect (subject).should_equal (target) - expect (subject).should_be (subject) + expect (subject).to_equal (target) + expect (subject).to_be (subject) - it treats non-table arg2 as nometa parameter: mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, true))).should_be (nil) + expect (getmetatable (f (mt, true))).to_be (nil) - it treats table arg2 as a map parameter: mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, {}))).should_be (getmetatable (mt)) + expect (getmetatable (f (mt, {}))).to_be (getmetatable (mt)) - it supports 3 arguments with nometa as arg3: mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, {}, "nometa"))).should_be (nil) + expect (getmetatable (f (mt, {}, "nometa"))).to_be (nil) - context when renaming some keys: - before: target = { newkey = subject.k1, k2 = subject.k2, k3 = subject.k3 } - it renames during cloning: - expect (f (subject, {k1 = "newkey"})).should_equal (target) + 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).should_be (subject.k1) + expect (f (subject, {k1 = "newkey"}).newkey).to_be (subject.k1) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") - describe clone_rename: @@ -101,26 +101,26 @@ specify std.table: _, err = capture (f, {{}, subject}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).should_contain "clone_rename is deprecated" + expect (err).to_contain "clone_rename is deprecated" end _, err = capture (f, {{}, subject}) - expect (err).should_be (nil) + expect (err).to_be (nil) - it copies the subject: - expect (f ({}, subject)).should_copy (subject) + expect (f ({}, subject)).to_copy (subject) - it only makes a shallow copy: - expect (f ({}, subject).k2).should_be (subject.k2) + expect (f ({}, subject).k2).to_be (subject.k2) - context when renaming some keys: - before: target = { newkey = subject.k1, k2 = subject.k2, k3 = subject.k3 } - it renames during cloning: - expect (f ({k1 = "newkey"}, subject)).should_equal (target) + expect (f ({k1 = "newkey"}, subject)).to_equal (target) - it does not perturb the value in the renamed key field: - expect (f ({k1 = "newkey"}, subject).newkey).should_be (subject.k1) + expect (f ({k1 = "newkey"}, subject).newkey).to_be (subject.k1) - "it diagnoses non-table arguments": - expect (f {}).should_error ("table expected") - expect (f ({}, "foo")).should_error ("table expected") + expect (f {}).to_error ("table expected") + expect (f ({}, "foo")).to_error ("table expected") - describe clone_select: @@ -128,46 +128,46 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } f = M.clone_select - it does not just return the subject: - expect (f (subject)).should_not_be (subject) + expect (f (subject)).not_to_be (subject) - it copies the keys selected: - expect (f (subject, {"k1", "k2"})).should_equal ({ k1 = {"v1"}, k2 = {"v2"} }) + 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"})).should_equal (subject) + expect (f (subject, {"k1", "k2", "k3"})).to_equal (subject) - it only makes a shallow copy: - expect (f (subject, {"k1"}).k1).should_be (subject.k1) + expect (f (subject, {"k1"}).k1).to_be (subject.k1) - the original subject is not perturbed: target = { k1 = subject.k1, k2 = subject.k2, k3 = subject.k3 } copy = f (subject, {"k1", "k2", "k3"}) - expect (subject).should_equal (target) - expect (subject).should_be (subject) + expect (subject).to_equal (target) + expect (subject).to_be (subject) - it treats non-table arg2 as nometa parameter: mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, true))).should_be (nil) + expect (getmetatable (f (mt, true))).to_be (nil) - it treats table arg2 as a map parameter: mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, {}))).should_be (getmetatable (mt)) + expect (getmetatable (f (mt, {}))).to_be (getmetatable (mt)) - it supports 3 arguments with nometa as arg3: mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, {}, "nometa"))).should_be (nil) + expect (getmetatable (f (mt, {}, "nometa"))).to_be (nil) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") - describe empty: - before: f = M.empty - it returns true for an empty table: - expect (f {}).should_be (true) - expect (f {nil}).should_be (true) + expect (f {}).to_be (true) + expect (f {nil}).to_be (true) - "it returns false for a non-empty table": - expect (f {"stuff"}).should_be (false) - expect (f {{}}).should_be (false) - expect (f {false}).should_be (false) + expect (f {"stuff"}).to_be (false) + expect (f {{}}).to_be (false) + expect (f {false}).to_be (false) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") - describe invert: @@ -175,18 +175,17 @@ specify std.table: subject = { k1 = 1, k2 = 2, k3 = 3 } f = M.invert - it returns a new table: - expect (f (subject)).should_not_be (subject) + expect (f (subject)).not_to_be (subject) - it inverts keys and values in the returned table: - expect (f (subject)).should_equal { "k1", "k2", "k3" } + expect (f (subject)).to_equal { "k1", "k2", "k3" } - it is reversible: - expect (f (f (subject))).should_equal (subject) + expect (f (f (subject))).to_equal (subject) - "it seems to copy a list of 1..n numbers": subject = { 1, 2, 3 } - expect (f (subject)).should_equal (subject) - expect (f (subject)).should_not_be (subject) + expect (f (subject)).to_copy (subject) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") - describe keys: @@ -194,19 +193,19 @@ specify std.table: subject = { k1 = 1, k2 = 2, k3 = 3 } f = M.keys - it returns an empty list when subject is empty: - expect (f {}).should_equal {} + 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)).should_equal {"k1", "k2", "k3"} + 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)).should_not_equal (subject) + expect (f (subject)).not_to_equal (subject) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") - describe merge: @@ -220,22 +219,21 @@ specify std.table: for k, v in pairs (t1) do target[k] = v end for k, v in pairs (t2) do target[k] = v end - it does not create a whole new table: - expect (f (t1, t2)).should_be (t1) + expect (f (t1, t2)).to_be (t1) - it does not change t1 when t2 is empty: - expect (f (t1, {})).should_be (t1) + expect (f (t1, {})).to_be (t1) - it copies t2 when t1 is empty: - expect (f ({}, t1)).should_not_be (t1) - expect (f ({}, t1)).should_equal (t1) + expect (f ({}, t1)).to_copy (t1) - it merges keys from t2 into t1: - expect (f (t1, t2)).should_equal (target) + 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).should_be (t2.k3) - expect (m.k3).should_not_be (original.k3) + expect (m.k3).to_be (t2.k3) + expect (m.k3).not_to_be (original.k3) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f ("foo", "bar")).should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f ("foo", "bar")).to_error ("table expected") - describe new: @@ -245,34 +243,34 @@ specify std.table: - context when not setting a default: - before: default = nil - it returns a new table when nil is passed: - expect (f (default, nil)).should_equal {} + expect (f (default, nil)).to_equal {} - it returns any table passed in: t = { "unique table" } - expect (f (default, t)).should_be (t) + 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)).should_equal {} + expect (f (default, nil)).to_equal {} - it returns any table passed in: t = { "unique table" } - expect (f (default, t)).should_be (t) + 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]).should_be (v) + expect (t[1]).to_be (v) - it returns the constructor default for unset keys: t = f ("default") - expect (t[1]).should_be "default" + expect (t[1]).to_be "default" - it returns the actual default object: default = { "unique object" } t = f (default) - expect (t[1]).should_be (default) + expect (t[1]).to_be (default) - "it diagnoses non-tables/non-nil in the second argument": - expect (f (nil, "foo")).should_error ("table expected") + expect (f (nil, "foo")).to_error ("table expected") - describe pack: @@ -287,12 +285,12 @@ specify std.table: subject = { "one", { { "two" }, "three" }, four = 5 } f = M.size - it counts the number of keys in a table: - expect (f (subject)).should_be (3) + expect (f (subject)).to_be (3) - it counts no keys in an empty table: - expect (f {}).should_be (0) + expect (f {}).to_be (0) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") - describe sort: @@ -303,12 +301,12 @@ specify std.table: f = M.sort - it sorts elements in place: f (subject, cmp) - expect (subject).should_equal (target) + expect (subject).to_equal (target) - it returns the sorted table: - expect (f (subject, cmp)).should_equal (target) + expect (f (subject, cmp)).to_equal (target) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") - describe totable: @@ -319,15 +317,15 @@ specify std.table: subject = { k1 = {1}, k2 = {2}, k3 = {3} } f = M.values - it returns an empty list when subject is empty: - expect (f {}).should_equal {} + 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)).should_equal {{1}, {2}, {3}} + 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)).should_equal (subject) + expect (f (subject)).to_equal (subject) - "it diagnoses non-table arguments": - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 9c72190..ac0fa4d 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -18,31 +18,31 @@ specify std.tree: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - should_equal {} + to_equal {} - context via the std module: - it adds apis to the global table: expect (show_apis {added_to=global_table, by="std"}). - should_contain.all_of (std_globals) + to_contain.all_of (std_globals) - describe construction: - it constructs a new tree: tr = Tree {} - expect (tr).should_not_be (Tree) - expect (prototype (tr)).should_be "Tree" + expect (tr).not_to_be (Tree) + expect (prototype (tr)).to_be "Tree" - it turns a table argument into a tree: - expect (prototype (Tree (tr))).should_be "Tree" + expect (prototype (Tree (tr))).to_be "Tree" - it does not turn table argument values into sub-Trees: - expect (prototype (tr["fnord"])).should_be "table" + expect (prototype (tr["fnord"])).to_be "table" - it understands branched nodes: - expect (tr).should_equal (Tree (t)) - expect (tr[{"fnord"}]).should_equal (t.fnord) - expect (tr[{"fnord", "branch", "bar"}]).should_equal (t.fnord.branch.bar) + expect (tr).to_equal (Tree (t)) + expect (tr[{"fnord"}]).to_equal (t.fnord) + expect (tr[{"fnord", "branch", "bar"}]).to_equal (t.fnord.branch.bar) - it serves as a prototype for new instances: obj = tr {} - expect (prototype (obj)).should_be "Tree" - expect (obj).should_equal (tr) - expect (getmetatable (obj)).should_be (getmetatable (tr)) + expect (prototype (obj)).to_be "Tree" + expect (obj).to_equal (tr) + expect (getmetatable (obj)).to_be (getmetatable (tr)) - describe clone: @@ -50,19 +50,19 @@ specify std.tree: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } f = Tree.clone - it does not just return the subject: - expect (f (subject)).should_not_be (subject) + expect (f (subject)).not_to_be (subject) - it does copy the subject: - expect (f (subject)).should_equal (subject) + expect (f (subject)).to_equal (subject) - it makes a deep copy: - expect (f (subject).k1).should_not_be (subject.k1) + expect (f (subject).k1).not_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).should_equal (target) - expect (subject).should_be (subject) + expect (subject).to_equal (target) + expect (subject).to_be (subject) - it diagnoses non-table arguments: - expect (f ()).should_error ("table expected") - expect (f "foo").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "foo").to_error ("table expected") - describe ileaves: @@ -71,20 +71,20 @@ specify std.tree: l = {} - it iterates over array part of a table argument: for v in f {"first", "second", "3rd"} do l[1+#l]=v end - expect (l).should_equal {"first", "second", "3rd"} + expect (l).to_equal {"first", "second", "3rd"} - it iterates over array parts of nested table argument: for v in f {{"one", {"two"}, {{"three"}, "four"}}, "five"} do l[1+#l]=v end - expect (l).should_equal {"one", "two", "three", "four", "five"} + expect (l).to_equal {"one", "two", "three", "four", "five"} - it skips hash part of a table argument: for v in f {"first", "second"; third = "2rd"} do l[1+#l]=v end - expect (l).should_equal {"first", "second"} + expect (l).to_equal {"first", "second"} - it skips hash parts of nested table argument: for v in f {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} do l[1+#l]=v end - expect (l).should_equal {"one", "three", "five"} + expect (l).to_equal {"one", "three", "five"} - it works on trees too: for v in f (Tree {Tree {"one", Tree {two=2}, @@ -94,10 +94,10 @@ specify std.tree: do l[1+#l]=v end - expect (l).should_equal {"one", "three", "five"} + expect (l).to_equal {"one", "three", "five"} - it diagnoses non-table arguments: - expect (f ()).should_error ("table expected") - expect (f "string").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "string").to_error ("table expected") - describe inodes: @@ -115,52 +115,52 @@ specify std.tree: - it iterates over array part of a table argument: | subject = {"first", "second", "3rd"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {3}, subject[3]}, -- 3rd, - {"join", {}, subject}} -- } + to_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {3}, subject[3]}, -- 3rd, + {"join", {}, subject}} -- } - it iterates over array parts of nested table argument: | subject = {{"one", {"two"}, {{"three"}, "four"}}, "five"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,1}, subject[1][2][1]}, -- two, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,2}, subject[1][3][2]}, -- four, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"join", {}, subject}} -- } + to_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,1}, subject[1][2][1]}, -- two, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,2}, subject[1][3][2]}, -- four, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"join", {}, subject}} -- } - it skips hash part of a table argument: | subject = {"first", "second"; third = "3rd"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"join", {}, subject}} -- } + to_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"join", {}, subject}} -- } - it skips hash parts of nested table argument: | subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"join", {}, subject}} -- } + to_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"join", {}, subject}} -- } - it works on trees too: | subject = Tree {Tree {"one", Tree {two=2}, @@ -168,22 +168,22 @@ specify std.tree: foo="bar", "five"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"join", {}, subject}} -- } + to_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"join", {}, subject}} -- } - it diagnoses non-table arguments: - expect (f ()).should_error ("table expected") - expect (f "string").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "string").to_error ("table expected") - describe leaves: @@ -192,20 +192,20 @@ specify std.tree: l = {} - it iterates over elements of a table argument: for v in f {"first", "second", "3rd"} do l[1+#l]=v end - expect (l).should_equal {"first", "second", "3rd"} + expect (l).to_equal {"first", "second", "3rd"} - it iterates over elements of a nested table argument: for v in f {{"one", {"two"}, {{"three"}, "four"}}, "five"} do l[1+#l]=v end - expect (l).should_equal {"one", "two", "three", "four", "five"} + expect (l).to_equal {"one", "two", "three", "four", "five"} - it includes the hash part of a table argument: for v in f {"first", "second"; third = "3rd"} do l[1+#l]=v end - expect (l).should_equal {"first", "second", "3rd"} + expect (l).to_equal {"first", "second", "3rd"} - it includes hash parts of a nested table argument: for v in f {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} do l[1+#l]=v end - expect (l).should_contain. + expect (l).to_contain. a_permutation_of {"one", 2, "three", 4, "bar", "five"} - it works on trees too: for v in f (Tree {Tree {"one", @@ -216,11 +216,11 @@ specify std.tree: do l[1+#l]=v end - expect (l).should_contain. + expect (l).to_contain. a_permutation_of {"one", 2, "three", 4, "bar", "five"} - it diagnoses non-table arguments: - expect (f ()).should_error ("table expected") - expect (f "string").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "string").to_error ("table expected") - describe merge: @@ -236,22 +236,21 @@ specify std.tree: if ty == "leaf" then target[p] = n end end - it does not create a whole new table: - expect (f (t1, t2)).should_be (t1) + expect (f (t1, t2)).to_be (t1) - it does not change t1 when t2 is empty: - expect (f (t1, Tree {})).should_be (t1) + expect (f (t1, Tree {})).to_be (t1) - it copies t2 when t1 is empty: | - expect (f (Tree {}, t1)).should_not_be (t1) - expect (f (Tree {}, t1)).should_equal (t1) + expect (f (Tree {}, t1)).to_copy (t1) - it merges keys from t2 into t1: | - expect (f (t1, t2)).should_equal (target) + expect (f (t1, t2)).to_equal (target) - it gives precedence to values from t2: original = Tree.clone (t1) m = f (t1, t2) -- Merge is destructive, do it once only. - expect (m.k3).should_be (t2.k3) - expect (m.k3).should_not_be (original.k3) + expect (m.k3).to_be (t2.k3) + expect (m.k3).not_to_be (original.k3) - it diagnoses non-table arguments: - expect (f (nil, {})).should_error ("table expected") - expect (f ({}, nil)).should_error ("table expected") + expect (f (nil, {})).to_error ("table expected") + expect (f ({}, nil)).to_error ("table expected") - describe nodes: @@ -266,35 +265,35 @@ specify std.tree: - it iterates over the elements of a table argument: | subject = {"first", "second", "3rd"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {3}, subject[3]}, -- 3rd, - {"join", {}, subject}} -- } + to_equal {{"branch", {}, subject}, -- { + {"leaf", {1}, subject[1]}, -- first, + {"leaf", {2}, subject[2]}, -- second, + {"leaf", {3}, subject[3]}, -- 3rd, + {"join", {}, subject}} -- } - it iterates over the elements of nested a table argument: | subject = {{"one", {"two"}, {{"three"}, "four"}}, "five"} expect (traverse (subject)). - should_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,1}, subject[1][2][1]}, -- two, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,2}, subject[1][3][2]}, -- four, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"join", {}, subject}} -- } + to_equal {{"branch", {}, subject}, -- { + {"branch", {1}, subject[1]}, -- { + {"leaf", {1,1}, subject[1][1]}, -- one, + {"branch", {1,2}, subject[1][2]}, -- { + {"leaf", {1,2,1}, subject[1][2][1]}, -- two, + {"join", {1,2}, subject[1][2]}, -- }, + {"branch", {1,3}, subject[1][3]}, -- { + {"branch", {1,3,1}, subject[1][3][1]}, -- { + {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, + {"join", {1,3,1}, subject[1][3][1]}, -- }, + {"leaf", {1,3,2}, subject[1][3][2]}, -- four, + {"join", {1,3}, subject[1][3]}, -- }, + {"join", {1}, subject[1]}, -- }, + {"leaf", {2}, subject[2]}, -- five, + {"join", {}, subject}} -- } - it includes the hash part of a table argument: | -- like `pairs`, `nodes` can visit elements in any order, so we cannot -- guarantee the array part is always visited before the hash part, or -- even that the array elements are visited in order! subject = {"first", "second"; third = "3rd"} - expect (traverse (subject)).should_contain. + expect (traverse (subject)).to_contain. a_permutation_of {{"branch", {}, subject}, -- { {"leaf", {1}, subject[1]}, -- first, {"leaf", {2}, subject[2]}, -- second, @@ -305,7 +304,7 @@ specify std.tree: -- guarantee the array part is always visited before the hash part, or -- even that the array elements are visited in order! subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} - expect (traverse (subject)).should_contain. + expect (traverse (subject)).to_contain. a_permutation_of {{"branch", {}, subject}, -- { {"branch", {1}, subject[1]}, -- { {"leaf", {1,1}, subject[1][1]}, -- one, @@ -331,7 +330,7 @@ specify std.tree: Tree {Tree {"three"}, four=4}}, foo="bar", "five"} - expect (traverse (subject)).should_contain. + expect (traverse (subject)).to_contain. a_permutation_of {{"branch", {}, subject}, -- { {"branch", {1}, subject[1]}, -- { {"leaf", {1,1}, subject[1][1]}, -- one, @@ -351,37 +350,37 @@ specify std.tree: - it generates path key-lists that are valid __index arguments: | subject = Tree {"first", Tree {"second"}, "3rd"} expect (traverse (subject)). - should_equal {{"branch", {}, subject[{}]}, -- { - {"leaf", {1}, subject[{1}]}, -- first, - {"branch", {2}, subject[{2}]}, -- { - {"leaf", {2,1}, subject[{2,1}]}, -- second - {"join", {2}, subject[{2}]}, -- } - {"leaf", {3}, subject[{3}]}, -- 3rd, - {"join", {}, subject[{}]}} -- } + to_equal {{"branch", {}, subject[{}]}, -- { + {"leaf", {1}, subject[{1}]}, -- first, + {"branch", {2}, subject[{2}]}, -- { + {"leaf", {2,1}, subject[{2,1}]}, -- second + {"join", {2}, subject[{2}]}, -- } + {"leaf", {3}, subject[{3}]}, -- 3rd, + {"join", {}, subject[{}]}} -- } - it diagnoses non-table arguments: - expect (f ()).should_error ("table expected") - expect (f "string").should_error ("table expected") + expect (f ()).to_error ("table expected") + expect (f "string").to_error ("table expected") - describe __index: - it returns nil for a missing key: - expect (tr["no such key"]).should_be (nil) + expect (tr["no such key"]).to_be (nil) - it returns nil for missing single element key lists: - expect (tr[{"no such key"}]).should_be (nil) + expect (tr[{"no such key"}]).to_be (nil) - it returns nil for missing multi-element key lists: - expect (tr[{"fnord", "foo"}]).should_be (nil) - expect (tr[{"no", "such", "key"}]).should_be (nil) + expect (tr[{"fnord", "foo"}]).to_be (nil) + expect (tr[{"no", "such", "key"}]).to_be (nil) - it returns a value for the given key: - expect (tr["foo"]).should_be "foo" - expect (tr["quux"]).should_be "quux" + expect (tr["foo"]).to_be "foo" + expect (tr["quux"]).to_be "quux" - it returns tree root for empty key list: - expect (tr[{}]).should_be (tr) + expect (tr[{}]).to_be (tr) - it returns values for single element key lists: - expect (tr[{"foo"}]).should_be "foo" - expect (tr[{"quux"}]).should_be "quux" + expect (tr[{"foo"}]).to_be "foo" + expect (tr[{"quux"}]).to_be "quux" - it returns values for multi-element key lists: - expect (tr[{"fnord", "branch", "bar"}]).should_be "bar" - expect (tr[{"fnord", "branch", "baz"}]).should_be "baz" + expect (tr[{"fnord", "branch", "bar"}]).to_be "bar" + expect (tr[{"fnord", "branch", "baz"}]).to_be "baz" - describe __newindex: @@ -389,31 +388,31 @@ specify std.tree: tr = Tree {} - it stores values for simple keys: tr["foo"] = "foo" - expect (tr).should_equal (Tree {foo="foo"}) + expect (tr).to_equal (Tree {foo="foo"}) - it stores values for single element key lists: tr[{"foo"}] = "foo" - expect (tr).should_equal (Tree {foo="foo"}) + expect (tr).to_equal (Tree {foo="foo"}) - it stores values for multi-element key lists: tr[{"foo", "bar"}] = "baz" - expect (tr).should_equal (Tree {foo=Tree {bar="baz"}}) + expect (tr).to_equal (Tree {foo=Tree {bar="baz"}}) - it separates branches for diverging key lists: tr[{"foo", "branch", "bar"}] = "leaf1" tr[{"foo", "branch", "baz"}] = "leaf2" - expect (tr).should_equal (Tree {foo=Tree {branch=Tree {bar="leaf1", baz="leaf2"}}}) + expect (tr).to_equal (Tree {foo=Tree {branch=Tree {bar="leaf1", baz="leaf2"}}}) - describe __totable: - it returns a table: - expect (prototype (totable (tr))).should_be "table" + expect (prototype (totable (tr))).to_be "table" - it contains all non-hidden fields of object: - expect (totable (tr)).should_contain. + expect (totable (tr)).to_contain. all_of {"foo", branch={bar="bar", baz="baz"}, "quux"} - describe __tostring: - it returns a string: - expect (prototype (tostring (tr))).should_be "string" + expect (prototype (tostring (tr))).to_be "string" - it shows the type name: - expect (tostring (tr)).should_contain "Tree" + expect (tostring (tr)).to_contain "Tree" - it shows the contents in order: | pending "see issue #44" expect (tostring (tr)). - should_contain 'fnord={branch={bar=bar, baz=baz}}, foo=foo, quux=quux' + to_contain 'fnord={branch={bar=bar, baz=baz}}, foo=foo, quux=quux' From 7fe83269953e133b6c35aa29188ddacd567375a1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 23 Apr 2014 16:38:50 +0700 Subject: [PATCH 131/703] specs: no need to explicitly require spec_helper any more. Since Specl 11, every spec-file automatically loads the spec_helper.lua from the same directory, if any. * specs/container_spec.yaml, specs/debug_spec.yaml, specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/math_spec.yaml, specs/object_spec.yaml, specs/optparse_spec.yaml, specs/package_spec.yaml, specs/set_spec.yaml, specs/strbuf_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml, specs/tree_spec.yaml: Remove explicit `require "spec_helper"`. Signed-off-by: Gary V. Vaughan --- specs/container_spec.yaml | 1 - specs/debug_spec.yaml | 2 -- specs/functional_spec.yaml | 2 -- specs/io_spec.yaml | 2 -- specs/list_spec.yaml | 1 - specs/math_spec.yaml | 2 -- specs/object_spec.yaml | 1 - specs/optparse_spec.yaml | 1 - specs/package_spec.yaml | 2 -- specs/set_spec.yaml | 1 - specs/strbuf_spec.yaml | 1 - specs/string_spec.yaml | 2 -- specs/table_spec.yaml | 2 -- specs/tree_spec.yaml | 2 -- 14 files changed, 22 deletions(-) diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index c29076e..9e8d85c 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -1,5 +1,4 @@ before: - require "spec_helper" Container = require "std.container" prototype = (require "std.object").prototype diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 8be39cd..4fbcf3a 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -1,6 +1,4 @@ before: | - require "spec_helper" - this_module = "std.debug" global_table = "_G" diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index dfe0845..e44a957 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -1,6 +1,4 @@ before: | - require "spec_helper" - global_table = "_G" this_module = "std.functional" std_globals = { "bind", "collect", "compose", "curry", "eval", diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 7b89de6..1c26e36 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -1,6 +1,4 @@ before: | - require "spec_helper" - this_module = "std.io" global_table = "_G" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 0153f74..3ebf355 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1,5 +1,4 @@ before: - require "spec_helper" Object = require "std.object" List = require "std.list" l = List {"foo", "bar", "baz"} diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index db66dc8..8c7895b 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -1,6 +1,4 @@ before: | - require "spec_helper" - this_module = "std.math" global_table = "_G" diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 068b564..acf06ba 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -1,5 +1,4 @@ before: - require "spec_helper" Object = require "std.object" obj = Object {"foo", "bar", baz="quux"} prototype = Object.prototype diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index 1cd3c42..feae149 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -1,5 +1,4 @@ before: - require "spec_helper" hell = require "specl.shell" specify std.optparse: diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index f229b46..f93683e 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -1,6 +1,4 @@ before: | - require "spec_helper" - this_module = "std.package" global_table = "_G" diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index cce3680..e401aef 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -1,5 +1,4 @@ before: - require "spec_helper" Set = require "std.set" prototype = (require "std.object").prototype totable = (require "std.table").totable diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index 2e0539d..efdff04 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -1,5 +1,4 @@ before: - require "spec_helper" object = require "std.object" StrBuf = require "std.strbuf" b = StrBuf {"foo", "bar"} diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index ac53ffb..311ca51 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -1,6 +1,4 @@ before: | - require "spec_helper" - this_module = "std.string" global_table = "_G" diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 3096b92..3f9073d 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -1,6 +1,4 @@ before: | - require "spec_helper" - base_module = "table" this_module = "std.table" global_table = "_G" diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index ac0fa4d..6f02ea1 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -1,6 +1,4 @@ before: | - require "spec_helper" - global_table = "_G" this_module = "std.tree" std_globals = { "ileaves", "inodes", "leaves", "nodes" } From d47631785941087306ea3b13ab6e1dde608a0f0e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 23 Apr 2014 17:06:58 +0700 Subject: [PATCH 132/703] string: leave global string metatable alone. Close #30. * lib/std/string.lua (__append, __concat, __index): Move these metamethods together at the start of the file, and store them in the returned module table rather than setting them in the global string metatable. Improve header comments, to describe namespace issues. * lib/std.lua.in: Set string metatable elements from string module as before. * specs/string_spec.yaml: Adjust to compensate for changes in returned string module table. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++ lib/std.lua.in | 6 +++ lib/std/string.lua | 100 ++++++++++++++++++++++++++--------------- specs/string_spec.yaml | 14 +++--- 4 files changed, 81 insertions(+), 43 deletions(-) diff --git a/NEWS b/NEWS index 19a640f..acfb3cc 100644 --- a/NEWS +++ b/NEWS @@ -80,6 +80,10 @@ Stdlib NEWS - User visible changes ** 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 diff --git a/lib/std.lua.in b/lib/std.lua.in index da0fb77..6b0d0f1 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -49,6 +49,12 @@ local file_metatable = getmetatable (io.stdin) file_metatable.readlines = io.readlines file_metatable.writelines = io.writelines +-- Add string metamethods to the string metatable. +local string_metatable = getmetatable "" +string_metatable.__append = string.__append +string_metatable.__concat = string.__concat +string_metatable.__index = string.__index + -- Maintain old global interface access points. for _, api in ipairs { --- Partially apply a function. diff --git a/lib/std/string.lua b/lib/std/string.lua index b11f135..20b8367 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -1,5 +1,33 @@ --[[-- Additions to the string module. + + If you `require "std"`, the contents of this module are all available + in the `std.string` table. + + However, this module also contains references to the Lua core string + table entries, so it's safe to load it like this: + + local string = require "std.string" + + Of course, if you do that you'll lose references to any core string + functions overwritten by `std.string`, so you might want to save any + that you want access to before you overwrite them. + + If your code does not `require "std"` anywhere, then you'll also need + to manually overwrite string functions in the global namespace if you + want to use them from there: + + local assert, tostring = string.assert, string.tostring + + And finally, to use the string metatable improvements with all core + strings, you'll need to merge this module's metatable into the core + string metatable (again, `require "std"` does this automatically): + + local string_metatable = getmetatable "" + string_metatable.__append = string.__append + string_metatable.__concat = string.__concat + string_metatable.__index = string.__index + @module std.string ]] @@ -8,13 +36,43 @@ local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" -local _assert = _G.assert local _format = string.format local _tostring = _G.tostring -local old__index = getmetatable ("").__index local M = {} +--- String append operation. +-- @param s string +-- @param c character (1-character string) +-- @return `s .. c` +local function __append (s, c) + return s .. c +end + +--- String concatenation operation. +-- @param s string +-- @param o object +-- @return s .. tostring (o) +local function __concat (s, o) + return M.tostring (s) .. M.tostring (o) +end + +--- String subscript operation. +-- @param s string +-- @param i index +-- @return `s:sub (i, i)` if i is a number, otherwise +-- fall back to a `std.string` metamethod (if any). +local function __index (s, i) + if type (i) == "number" then + return s:sub (i, i) + else + -- Fall back to module metamethods + return M[i] + end +end + + + --- Extend to work better with one argument. -- If only one argument is passed, no formatting is attempted. -- @param f format @@ -335,36 +393,6 @@ local function pickle (x) end ---- Give strings a subscription operator. --- @param s string --- @param i index --- @return `string.sub (s, i, i)` if i is a number, or --- falls back to any previous metamethod (by default, string methods) -getmetatable ("").__index = function (s, i) - if type (i) == "number" then - return s:sub (i, i) - -- Fall back to module metamethods - else - return M[i] - end -end - ---- Give strings an append metamethod. --- @param s string --- @param c character (1-character string) --- @return `s .. c` -getmetatable ("").__append = function (s, c) - return s .. c -end - ---- Give strings a concat metamethod. --- @param s string --- @param o object --- @return s .. tostring (o) -getmetatable ("").__concat = function (s, o) - return tostring (s) .. tostring (o) -end - --- Capitalise each word in a string. -- @param s string -- @return capitalised string @@ -517,7 +545,9 @@ end --- @export local String = { - __index = old__index, + __append = __append, + __concat = __concat, + __index = __index, assert = assert, caps = caps, chomp = chomp, @@ -547,10 +577,6 @@ for k,v in pairs (table.merge (String, { escapePattern = escape_pattern, escapeShell = escape_shell, ordinalSuffix = ordinal_suffix, - - -- Core Lua function implementations. - _format = _format, - _tostring = _tostring, })) do M[k] = v end diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 311ca51..942f7e5 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -7,14 +7,13 @@ before: | enhance_globals = { "assert", "tostring" } base_module = "string" - extend_base = { "assert", "caps", "chomp", "escape_pattern", + extend_base = { "__append", "__concat", "__index", + "assert", "caps", "chomp", "escape_pattern", "escape_shell", "finds", "ltrim", "numbertosi", "ordinal_suffix", "pad", "pickle", "prettytostring", "render", "require_version", "rtrim", "split", "tfind", "tostring", "trim", "wrap", - -- make these available after require "std" - "__index", "_format", "_tostring", -- camelCase compatibility: "escapePattern", "escapeShell", "ordinalSuffix" } @@ -30,6 +29,8 @@ before: | for _, s in ipairs (enhance_base) do all_apis[s] = true end M = require "std.string" + getmetatable("").__index = M.__index + getmetatable ("").__concat = M.__concat specify std.string: - before: @@ -71,10 +72,10 @@ specify std.string: expect (subject .. " another string").to_be (target) - "it stringifies non-string arguments": argument = { "a table" } - expect (subject .. argument).to_match (string.format ("%s{1=a table}", subject)) + expect (subject .. argument).to_match (string.format ("%s%s", subject, M.tostring (argument))) - it stringifies nil arguments: argument = nil - expect (subject .. argument).to_be (string.format ("%s%s", subject, tostring (argument))) + expect (subject .. argument).to_be (string.format ("%s%s", subject, M.tostring (argument))) - the original subject is not perturbed: original = subject newstring = subject .. " concatenate something" @@ -335,7 +336,8 @@ specify std.string: newstring = f (subject, width) expect (subject).to_be (original) - "it coerces non-string arguments to a string": - expect (f ({ "a table" }, width)).to_contain "a table" + argument = { "a table " } + expect (f (argument, width)).to_contain (M.tostring (argument)) - "it diagnoses non-numeric width arguments": expect (f (subject, nil)).to_error ("number expected") expect (f (subject, {"a table"})).to_error ("number expected") From 43defbb377a4086b5b1717fcc74783455ee06087 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 23 Apr 2014 17:51:51 +0700 Subject: [PATCH 133/703] Release version 39 * NEWS: Record release date. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index acfb3cc..6dc0fce 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ Stdlib NEWS - User visible changes -* Noteworthy changes in release ?.? (????-??-??) [?] +* Noteworthy changes in release 39 (2014-04-23) [stable] ** New features: From 8569702d83703bfb5d97d324ffac3243c0714c17 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 23 Apr 2014 17:52:16 +0700 Subject: [PATCH 134/703] maint: post-release administrivia. * configure.ac (AC_INIT): Bump release number to 40. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS | 3 +++ configure.ac | 2 +- local.mk | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.prev-version b/.prev-version index e522732..a272009 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -38 +39 diff --git a/NEWS b/NEWS index 6dc0fce..f43ed5d 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Stdlib NEWS - User visible changes +* Noteworthy changes in release ?.? (????-??-??) [?] + + * Noteworthy changes in release 39 (2014-04-23) [stable] ** New features: diff --git a/configure.ac b/configure.ac index 2965667..e767700 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [39], [http://github.com/lua-stdlib/lua-stdlib/issues]) +AC_INIT([stdlib], [40], [http://github.com/lua-stdlib/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/local.mk b/local.mk index c3176b8..8713c99 100644 --- a/local.mk +++ b/local.mk @@ -29,7 +29,7 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" ## Bootstrap. ## ## ---------- ## -old_NEWS_hash = 1c4d1bfae2d511327b83800043bc19c7 +old_NEWS_hash = 606609f9586288cfe6d9df676719570a update_copyright_env = \ UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ From d68ce8a8bba833be6d38a30a2f2ac0dfe41344da Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Apr 2014 15:47:30 +0700 Subject: [PATCH 135/703] table: add merge_select, and support map and nometa args to merge. Close #56. * specs/table_spec.yaml (extend_base): Add merge_select. (merge): Specify behaviour of new `map` and `nometa` args. (merge_select): Specify behaviour of new `merge_select` api. (clone, clone_select): Refactor for clarity and orthogonality. * lib/std/base.lua (merge): Rewrite to support clone-like `map` and `nometa` parameters, according to improved specifications. (clone): Rewrite as a call to `merge`. * lib/std/table.lua (merge_select): New `clone_select` like api satisfying specs. (clone_select): Rewrite as a call to `merge_select`. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 +++ lib/std/base.lua | 30 ++++---- lib/std/table.lua | 65 ++++++++++------- specs/table_spec.yaml | 157 +++++++++++++++++++++++++++++++----------- 4 files changed, 184 insertions(+), 76 deletions(-) diff --git a/NEWS b/NEWS index f43ed5d..2b84fef 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,14 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] +** New features: + + - `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. + * Noteworthy changes in release 39 (2014-04-23) [stable] diff --git a/lib/std/base.lua b/lib/std/base.lua index c56d290..c4704d0 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,20 +24,30 @@ local function deprecate (fn, name, warnmsg) end -- Doc-commented in table.lua... -local function clone (t, map, nometa) +local function merge (t, u, map, nometa) + assert (type (t) == "table", + "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") + assert (type (u) == "table", + "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") map = map or {} if type (map) ~= "table" then map, nometa = {}, map end - local u = {} if not nometa then - setmetatable (u, getmetatable (t)) + setmetatable (t, getmetatable (u)) end - for k, v in pairs (t) do - u[map[k] or k] = v + for k, v in pairs (u) do + t[map[k] or k] = v end - return u + return t +end + +-- Doc-commented in table.lua... +local function clone (t, map, nometa) + assert (type (t) == "table", + "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") + return merge ({}, t, map, nometa) end -- Doc-commented in table.lua... @@ -50,14 +60,6 @@ local function clone_rename (map, t) return r end --- Doc-commented in table.lua... -local function merge (t, u) - for i, v in pairs (u) do - t[i] = v - end - return t -end - local new -- forward declaration -- Doc-commented in list.lua... diff --git a/lib/std/table.lua b/lib/std/table.lua index 3a6aefb..bc75ee4 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -10,6 +10,44 @@ local func = require "std.functional" local elems = base.elems +--- Destructively merge another table's fields into *table*. +-- @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, ...}` +-- @tparam boolean nometa if non-nil don't copy metatable +-- @return table `t` with fields from `u` merged in +local merge = base.merge + + +--- Destructively merge another table's named fields into *table*. +-- +-- Like `merge`, but does not merge any fields by default. +-- @tparam table t destination table +-- @tparam table u table with fields to merge +-- @tparam[opt={}] table keys list of keys to copy +-- @return copy of fields in *selection* from *t*, also sharing *t*'s +-- metatable unless *nometa* +local function merge_select (t, u, keys, nometa) + assert (type (t) == "table", + "bad argument #1 to 'merge_select' (table expected, got " .. type (t) .. ")") + assert (type (u) == "table", + "bad argument #2 to 'merge_select' (table expected, got " .. type (u) .. ")") + keys = keys or {} + if type (keys) ~= "table" then + keys, nometa = {}, keys + end + + if not nometa then + setmetatable (t, getmetatable (u)) + end + for k in elems (keys) do + t[k] = u[k] + end + return t +end + + --- Make a shallow copy of a table, including any metatable. -- -- To make deep copies, use @{std.tree.clone}. @@ -37,36 +75,16 @@ local clone_rename = base.deprecate (base.clone_rename, nil, -- Like `clone`, but does not copy any fields by default. -- @function clone_select -- @tparam table t source table --- @tparam[opt={}] table selection list of keys to copy +-- @tparam[opt={}] table keys list of keys to copy -- @return copy of fields in *selection* from *t*, also sharing *t*'s -- metatable unless *nometa* -local function clone_select (t, map, nometa) +local function clone_select (t, keys, nometa) assert (type (t) == "table", "bad argument #1 to 'clone_select' (table expected, got " .. type (t) .. ")") - map = map or {} - if type (map) ~= "table" then - map, nometa = {}, map - end - - local r = {} - if not nometa then - setmetatable (r, getmetatable (t)) - end - for i in elems (map) do - r[i] = t[i] - end - return r + return merge_select ({}, t, keys, nometa) end ---- Destructively merge another table's fields into *table*. --- @function merge --- @tparam table t destination table --- @tparam table u table with fields to merge --- @return table `t` with fields from `u` merged in -local merge = base.merge - - -- Preserve core table sort function. local _sort = table.sort @@ -199,6 +217,7 @@ local Table = { invert = invert, keys = keys, merge = merge, + merge_select = merge_select, new = new, pack = pack, ripairs = ripairs, diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 3f9073d..b17dff1 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -5,7 +5,7 @@ before: | std_globals = { "pack", "ripairs", "totable" } extend_base = { "clone", "clone_select", "clone_rename", "empty", - "invert", "keys", "merge", "new", + "invert", "keys", "merge", "merge_select", "new", "ripairs", "size", "totable", "values", -- make these available after require "std" "_sort" } @@ -56,37 +56,38 @@ specify std.table: - describe clone: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } + withmt = setmetatable (M.clone (subject), {"meta!"}) f = M.clone - 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: + - it only makes a shallow copy of field values: expect (f (subject).k1).to_be (subject.k1) - the original subject is not perturbed: target = { k1 = subject.k1, k2 = subject.k2, k3 = subject.k3 } copy = f (subject) expect (subject).to_equal (target) expect (subject).to_be (subject) - - it treats non-table arg2 as nometa parameter: - mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, true))).to_be (nil) - - it treats table arg2 as a map parameter: - mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, {}))).to_be (getmetatable (mt)) - - it supports 3 arguments with nometa as arg3: - mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, {}, "nometa"))).to_be (nil) + + - 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: - - before: - target = { newkey = subject.k1, k2 = subject.k2, k3 = subject.k3 } - 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) - - "it diagnoses non-table arguments": + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f "foo").to_error ("table expected") @@ -116,7 +117,7 @@ specify std.table: - it does not perturb the value in the renamed key field: expect (f ({k1 = "newkey"}, subject).newkey).to_be (subject.k1) - - "it diagnoses non-table arguments": + - it diagnoses non-table arguments: expect (f {}).to_error ("table expected") expect (f ({}, "foo")).to_error ("table expected") @@ -124,7 +125,9 @@ specify std.table: - describe clone_select: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } + withmt = setmetatable (M.clone (subject), {"meta!"}) f = M.clone_select + - it does not just return the subject: expect (f (subject)).not_to_be (subject) - it copies the keys selected: @@ -138,17 +141,18 @@ specify std.table: copy = f (subject, {"k1", "k2", "k3"}) expect (subject).to_equal (target) expect (subject).to_be (subject) - - it treats non-table arg2 as nometa parameter: - mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, true))).to_be (nil) - - it treats table arg2 as a map parameter: - mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, {}))).to_be (getmetatable (mt)) - - it supports 3 arguments with nometa as arg3: - mt = setmetatable (f (subject, true), {}) - expect (getmetatable (f (mt, {}, "nometa"))).to_be (nil) - - - "it diagnoses non-table arguments": + + - 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) + + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f "foo").to_error ("table expected") @@ -159,11 +163,11 @@ specify std.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": + - 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) - - "it diagnoses non-table arguments": + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f "foo").to_error ("table expected") @@ -178,10 +182,10 @@ specify std.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": + - it seems to copy a list of 1..n numbers: subject = { 1, 2, 3 } expect (f (subject)).to_copy (subject) - - "it diagnoses non-table arguments": + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f "foo").to_error ("table expected") @@ -201,7 +205,7 @@ specify std.table: -- 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) - - "it diagnoses non-table arguments": + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f "foo").to_error ("table expected") @@ -209,9 +213,10 @@ specify std.table: - describe merge: - before: | -- Additional merge keys which are moderately unusual - t1 = { k1 = {"v1"}, k2 = "if", k3 = {"?"} } - t2 = { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = t1.k1 } - f = M.merge + t1 = { k1 = {"v1"}, k2 = "if", k3 = {"?"} } + t2 = { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = t1.k1 } + t1mt = setmetatable (M.clone (t1), {"meta!"}) + f = M.merge target = {} for k, v in pairs (t1) do target[k] = v end @@ -229,7 +234,81 @@ specify std.table: 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 diagnoses non-table arguments": + - 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) + + - it diagnoses non-table arguments: + expect (f ()).to_error ("table expected") + expect (f ("foo", "bar")).to_error ("table expected") + + +- 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" } + f = M.merge_select + + target = {} + for k, v in pairs (t1) do target[k] = v end + for k, v in pairs (t2) do target[k] = v 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) + + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f ("foo", "bar")).to_error ("table expected") @@ -267,7 +346,7 @@ specify std.table: default = { "unique object" } t = f (default) expect (t[1]).to_be (default) - - "it diagnoses non-tables/non-nil in the second argument": + - it diagnoses non-tables/non-nil in the second argument: expect (f (nil, "foo")).to_error ("table expected") @@ -286,7 +365,7 @@ specify std.table: expect (f (subject)).to_be (3) - it counts no keys in an empty table: expect (f {}).to_be (0) - - "it diagnoses non-table arguments": + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f "foo").to_error ("table expected") @@ -302,7 +381,7 @@ specify std.table: expect (subject).to_equal (target) - it returns the sorted table: expect (f (subject, cmp)).to_equal (target) - - "it diagnoses non-table arguments": + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f "foo").to_error ("table expected") @@ -324,6 +403,6 @@ specify std.table: -- 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) - - "it diagnoses non-table arguments": + - it diagnoses non-table arguments: expect (f ()).to_error ("table expected") expect (f "foo").to_error ("table expected") From a1645a9c2ef2abdb386d4263d5dc3374ba8ec8e3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Apr 2014 17:48:41 +0700 Subject: [PATCH 136/703] docs: add missing doc for nometa arg of merge_select. * lib/std/table.lua (merge_select): Add missing nometa doc. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/table.lua b/lib/std/table.lua index bc75ee4..fa14f79 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -26,6 +26,7 @@ local merge = base.merge -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table keys list of keys to copy +-- @tparam boolean nometa if non-nil don't copy metatable -- @return copy of fields in *selection* from *t*, also sharing *t*'s -- metatable unless *nometa* local function merge_select (t, u, keys, nometa) From 0b51d787ca91b6a9ee7c7163728525047e3452f7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 26 Apr 2014 17:08:17 +0700 Subject: [PATCH 137/703] specs: fix a garbage-in, garbage-out example. Close #44. * specs/tree_spec.yaml (tostring): The assumption that tostring should recurse by itself, or that the Tree constructor should massage subtables on instantiation were both flawed. Fix the input to be properly nested tree, and `tostring` will indeed output a properly nested tree. Signed-off-by: Gary V. Vaughan --- specs/tree_spec.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 6f02ea1..9fbb862 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -411,6 +411,8 @@ specify std.tree: - it shows the type name: expect (tostring (tr)).to_contain "Tree" - it shows the contents in order: | - pending "see issue #44" + tr = Tree {foo = "foo", + fnord = Tree {branch = Tree {bar="bar", baz="baz"}}, + quux = "quux"} expect (tostring (tr)). - to_contain 'fnord={branch={bar=bar, baz=baz}}, foo=foo, quux=quux' + to_contain 'fnord=Tree {branch=Tree {bar=bar, baz=baz}}, foo=foo, quux=quux' From b612ae505df29bf2bbefe2c2a6261e2c73057492 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Apr 2014 16:34:01 +0700 Subject: [PATCH 138/703] refactor: break std.container dependency on std.base. * lib/std/container.lua (instantiate): New function; a faster equivalent to `merge (clone (proto), t or {})`. Adjust all callers. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 97dce77..5e50535 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -58,9 +58,27 @@ ]] -local base = require "std.base" - -local clone, merge = base.clone, base.merge +-- Instantiate a new object based on *proto*. +-- +-- This is equivalent to: +-- +-- base.merge (base.clone (proto), t or {}) +-- +-- But, not typechecking arguments or checking for metatables, is +-- slightly faster. +-- @tparam table proto base object to copy from +-- @tparam[opt={}] table t additional fields to merge in +-- @treturn table a new table with fields from proto and t merged in. +local function instantiate (proto, t) + local obj = {} + for k, v in pairs (proto) do + obj[k] = v + end + for k, v in pairs (t or {}) do + obj[k] = v + end + return obj +end local ModuleFunction = { @@ -182,13 +200,13 @@ local metatable = { -- If a metatable was set, then merge our fields and use it. if next (getmetatable (obj) or {}) then - obj_mt = merge (clone (mt), getmetatable (obj)) + obj_mt = instantiate (mt, getmetatable (obj)) -- Merge object methods. if type (obj_mt.__index) == "table" and type ((mt or {}).__index) == "table" then - obj_mt.__index = merge (clone (mt.__index), obj_mt.__index) + obj_mt.__index = instantiate (mt.__index, obj_mt.__index) end end @@ -202,8 +220,8 @@ local metatable = { -- @see std.object.__tostring __tostring = function (self) local totable = getmetatable (self).__totable - local array = clone (totable (self), "nometa") - local other = clone (array, "nometa") + local array = instantiate (totable (self)) + local other = instantiate (array) local s = "" if #other > 0 then for i in ipairs (other) do other[i] = nil end From 09c0d96ee3a4f897576f6a43f021b2e76e240ac4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Apr 2014 16:47:44 +0700 Subject: [PATCH 139/703] refactor: move clone and clone_rename back into std.table. * lib/std/base.lua (clone, clone_rename): Move from here... * lib/std/table.lua (clone, clone_rename): ...to here. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 19 ------------------- lib/std/table.lua | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index c4704d0..b94ab11 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -43,23 +43,6 @@ local function merge (t, u, map, nometa) return t end --- Doc-commented in table.lua... -local function clone (t, map, nometa) - assert (type (t) == "table", - "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") - return merge ({}, t, map, nometa) -end - --- Doc-commented in table.lua... -local function clone_rename (map, t) - local r = clone (t) - for i, v in pairs (map) do - r[v] = t[i] - r[i] = nil - end - return r -end - local new -- forward declaration -- Doc-commented in list.lua... @@ -171,8 +154,6 @@ end local M = { append = append, - clone = clone, - clone_rename = clone_rename, compare = compare, concat = concat, deprecate = deprecate, diff --git a/lib/std/table.lua b/lib/std/table.lua index fa14f79..d7e5330 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -58,7 +58,11 @@ end -- @tparam boolean 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* -local clone = base.clone +local function clone (t, map, nometa) + assert (type (t) == "table", + "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") + return base.merge ({}, t, map, nometa) +end -- DEPRECATED: Remove in first release following 2015-04-15. @@ -67,7 +71,14 @@ local clone = base.clone -- @tparam table map table `{old_key=new_key, ...}` -- @tparam table t source table -- @return copy of *table* -local clone_rename = base.deprecate (base.clone_rename, nil, +local clone_rename = base.deprecate (function (map, t) + local r = clone (t) + for i, v in pairs (map) do + r[v] = t[i] + r[i] = nil + end + return r + end, nil, "table.clone_rename is deprecated, use the new `map` argument to table.clone instead.") From 6bfe753175af6267bb1f0b5cb1fb578757a87b3d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Apr 2014 17:04:06 +0700 Subject: [PATCH 140/703] refactor: move `metamethod` from `functional` to `table` module. * lib/std/functional.lua (metamethod): Move from here... * lib/std/base.lua (metamethod): ...to here; and... * lib/std/table.lua (metamethod): ...re-export from here. Break dependency on `std.functional`. Adjust all callers. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 6 ++++++ lib/std.lua.in | 10 +++++----- lib/std/base.lua | 15 +++++++++++++++ lib/std/functional.lua | 18 ------------------ lib/std/object.lua | 2 +- lib/std/string.lua | 5 +++-- lib/std/table.lua | 13 +++++++++++-- specs/table_spec.yaml | 5 +++-- 8 files changed, 44 insertions(+), 30 deletions(-) diff --git a/NEWS b/NEWS index 2b84fef..5f54397 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,12 @@ Stdlib NEWS - User visible changes - New `table.merge_select` function, orthogonal to `table.clone_select`. See LDocs for details. +** Incompatible changes: + + - 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. + * Noteworthy changes in release 39 (2014-04-23) [stable] diff --git a/lib/std.lua.in b/lib/std.lua.in index 6b0d0f1..c564077 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -107,11 +107,6 @@ for _, api in ipairs { -- @see std.functional.memoize "functional.memoize", - --- Return given metamethod, if any, else nil. - -- @function _G.metamethod - -- @see std.functional.metamethod - "functional.metamethod", - --- Functional forms of infix operators. -- @table _G.op -- @see std.functional.op @@ -163,6 +158,11 @@ for _, api in ipairs { + --- Return given metamethod, if any, else nil. + -- @function _G.metamethod + -- @see std.table.metamethod + "table.metamethod", + --- Turn a tuple into a list. -- @function _G.pack -- @see std.table.pack diff --git a/lib/std/base.lua b/lib/std/base.lua index b94ab11..477569d 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,6 +23,20 @@ local function deprecate (fn, name, warnmsg) end end + +-- Doc-commented in table.lua... +local function metamethod (x, n) + local _, m = pcall (function (x) + return getmetatable (x)[n] + end, + x) + if type (m) ~= "function" then + m = nil + end + return m +end + + -- Doc-commented in table.lua... local function merge (t, u, map, nometa) assert (type (t) == "table", @@ -161,6 +175,7 @@ local M = { ileaves = ileaves, leaves = leaves, merge = merge, + metamethod = metamethod, new = new, -- list metatable diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 2be45b9..fc85f91 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,23 +8,6 @@ local list = require "std.base" local functional -- forward declaration ---- Return given metamethod, if any, or nil. --- @param x object to get metamethod of --- @param n name of metamethod to get --- @return metamethod function or nil if no metamethod or not a --- function -local function metamethod (x, n) - local _, m = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if type (m) ~= "function" then - m = nil - end - return m -end - - --- Identity function. -- @param ... -- @return the arguments passed to the function @@ -204,7 +187,6 @@ functional = { id = id, map = map, memoize = memoize, - metamethod = metamethod, } --- Functional forms of infix operators. diff --git a/lib/std/object.lua b/lib/std/object.lua index eea9635..34eef40 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -65,7 +65,7 @@ local Container = require "std.container" -local metamethod = (require "std.functional").metamethod +local metamethod = (require "std.base").metamethod --- Root object. diff --git a/lib/std/string.lua b/lib/std/string.lua index 20b8367..d4cc377 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -31,11 +31,12 @@ @module std.string ]] -local func = require "std.functional" local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" +local metamethod = (require "std.base").metamethod + local _format = string.format local _tostring = _G.tostring @@ -228,7 +229,7 @@ local function render (x, open, close, elem, pair, sep, roots) return roots[x] or render (x, open, close, elem, pair, sep, table.clone (roots)) end roots = roots or {} - if type (x) ~= "table" or func.metamethod (x, "__tostring") then + if type (x) ~= "table" or metamethod (x, "__tostring") then return elem (x) else local s = StrBuf {} diff --git a/lib/std/table.lua b/lib/std/table.lua index d7e5330..626afdb 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -4,7 +4,6 @@ ]] local base = require "std.base" -local func = require "std.functional" -- No need to pull all of std.list into memory. local elems = base.elems @@ -190,11 +189,20 @@ local function ripairs (t) end +--- Return given metamethod, if any, or nil. +-- @function metamethod +-- @param x object to get metamethod of +-- @param n name of metamethod to get +-- @return metamethod function or nil if no metamethod or not a +-- function +local metamethod = base.metamethod + + --- Turn an object into a table according to __totable metamethod. -- @tparam std.object x object to turn into a table -- @treturn table resulting table or `nil` local function totable (x) - local m = func.metamethod (x, "__totable") + local m = metamethod (x, "__totable") if m then return m (x) elseif type (x) == "table" then @@ -230,6 +238,7 @@ local Table = { keys = keys, merge = merge, merge_select = merge_select, + metamethod = metamethod, new = new, pack = pack, ripairs = ripairs, diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index b17dff1..27e0681 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -5,8 +5,9 @@ before: | std_globals = { "pack", "ripairs", "totable" } extend_base = { "clone", "clone_select", "clone_rename", "empty", - "invert", "keys", "merge", "merge_select", "new", - "ripairs", "size", "totable", "values", + "invert", "keys", "merge", "merge_select", + "metamethod", "new", "ripairs", "size", "totable", + "values", -- make these available after require "std" "_sort" } enhance_base = { "sort" } From 9722ee31698404ba6821d6cc413a18c9b2a96015 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 26 Apr 2014 16:13:36 +0700 Subject: [PATCH 141/703] std: barrel of monkey(patche)s! Close #56. Segregate monkey patching into module functions that have to be called explicitly. * lib/std.lua.in: Don't clobber any core metatables, or change any global symbols on load. * specs/io_spec.lua (monkey_patch): Specify behaviour of std.io.monkey_patch function. * lib/std/io.lua (monkey_patch): Add readlines and writelines methods to core file objects. (processFiles): Remove. * specs/math_spec.lua (monkey_patch): Specify behaviour of std.math.monkey_patch function. * lib/std/math.lua (monkey_patch): Overwrite core math.floor. * specs/string_spec.lua (monkey_patch): Specify behaviour of std.string.monkey_patch function. * lib/std/string.lua (monkey_patch): Overwrite core assert and tostring functions, and add methods and metamethods to core string objects. (escapePattern, escapeShell, ordinalSuffix): Remove. * specs/table_spec.lua (monkey_patch): Specify behaviour of std.table.monkey_patch function. * lib/std/table.lua (monkey_patch): Overwrite core table.sort. * specs/table_spec.lua (barrel, monkey_patch): Specify behaviour of std.barrel and std.monkey_patch functions. * lib/std.lua.in (monkey_patch): New function for patching core symbols and metatables by calling submodule `monkey_patch` functions. (barrel): New function for scribbling all over the given namespace, as well as installing all std monkey_patches. * specs/debug_spec.yaml, specs/functional_spec.yaml, specs/io_spec.yaml, specs/math_spec.yaml, specs/package_spec.yaml, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml, specs/tree_spec.yaml: Update to reflect removal of default monkey patching. Signed-off-by: Gary V. Vaughan --- NEWS | 30 +++++ lib/std.lua.in | 262 +++++++++++-------------------------- lib/std/io.lua | 27 +++- lib/std/math.lua | 31 +++-- lib/std/string.lua | 39 ++++-- lib/std/table.lua | 30 ++++- specs/debug_spec.yaml | 13 +- specs/functional_spec.yaml | 13 +- specs/io_spec.yaml | 54 ++++---- specs/math_spec.yaml | 41 +++--- specs/package_spec.yaml | 18 +-- specs/std_spec.yaml | 108 ++++++++++++++- specs/string_spec.yaml | 129 +++++++++--------- specs/table_spec.yaml | 59 ++++----- specs/tree_spec.yaml | 5 +- 15 files changed, 481 insertions(+), 378 deletions(-) diff --git a/NEWS b/NEWS index 5f54397..4ee4cc1 100644 --- a/NEWS +++ b/NEWS @@ -12,10 +12,40 @@ Stdlib NEWS - User visible changes ** 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: + + 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: + + local std = require "std".monkey_patch () + + - The global namespace is no longer clobbered by `require "std"`. To + get the old behaviour back: + + 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.string.escapePattern`, + `std.string. escapeShell`, `std.string.ordinalSuffix`. * Noteworthy changes in release 39 (2014-04-23) [stable] diff --git a/lib/std.lua.in b/lib/std.lua.in index c564077..74b8eb3 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -1,14 +1,19 @@ --[[-- - Global namespace scribbler. + Submodule lazy loader. - For backwards compatibility with older releases, `require "std"` - will inject the same functions into the global namespace as it - has done previously, even though it is now deprecated. + After requiring this module, simply referencing symbols in the submodule + hierarchy will load the necessary modules on demand. - For new code, much better than scribbling all over the global - namespace, it's more hygienic to explicitly assign the results of - requiring just the submodules you actually use to a local variable, - and access its functions via that table. + Clients of older releases might be surprised by this new-found hygiene, + expecting the various changes that used to be automatically installed as + global symbols, or monkey patched into the core module tables and + metatables. Sometimes, it's still convenient to do that... when using + stdlib from the REPL, or in a prototype where you want to throw caution + to the wind and compatibility with other modules be damned, for example. + In that case, you can give stdlib permission to scribble all over your + namespaces with: + + local std = require "std".monkey_patch () @todo Write a style guide (indenting/wrapping, capitalisation, function and variable names); library functions should call @@ -20,7 +25,70 @@ ]] +local M -- forward declaration + +--- Overwrite core methods and metamethods with `std` enhanced versions. +-- +-- Loads all `std` submodules with a `monkey_patch` method, and runs +-- them. +-- @function monkey_patch +-- @tparam[opt=_G] table namespace where to install global functions +-- @treturn table the module table +local function monkey_patch (namespace) + namespace = namespace or _G + + assert (type (namespace) == "table", + "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") + + require "std.io".monkey_patch (namespace) + require "std.math".monkey_patch (namespace) + require "std.string".monkey_patch (namespace) + require "std.table".monkey_patch (namespace) + + return M +end + + +--- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). +-- +-- Scribble all over the given namespace, and apply all available +-- `monkey_patch` functions. +-- @function barrel +-- @tparam[opt=_G] table namespace where to install global functions +-- @treturn table module table +local function barrel (namespace) + namespace = namespace or _G + + assert (type (namespace) == "table", + "bad argument #1 to 'barrel' (table expected, got " .. type (namespace) .. ")") + + -- Older releases installed the following into _G by default. + for _, v in pairs { + "functional.bind", "functional.collect", "functional.compose", + "functional.curry", "functional.eval", "functional.filter", + "functional.fold", "functional.id", "functional.map", + "functional.memoize", "functional.op", + + "io.die", "io.warn", + + "string.assert", "string.pickle", "string.prettytostring", + "string.render", "string.require_version", "string.tostring", + + "table.metamethod", "table.pack", "table.ripairs", + "table.totable", + + "tree.ileaves", "tree.inodes", "tree.leaves", "tree.nodes", + } do + local module, method = v:match "^(.*)%.(.-)$" + namespace[method] = M[module][method] + end + + return monkey_patch (namespace) +end + + --- Module table. +-- -- Lazy load 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: @@ -31,181 +99,11 @@ -- @field version release version string local version = "General Lua libraries / @VERSION@" -local modules = require "std.modules" - -for m, globally in pairs (modules) do - if globally == true then - -- Inject stdlib extensions directly into global package namespaces. - for k, v in pairs (require ("std." .. m)) do - _G[m][k] = v - end - else - _G[m] = require ("std." .. m) - end -end - --- Add io functions to the file handle metatable. -local file_metatable = getmetatable (io.stdin) -file_metatable.readlines = io.readlines -file_metatable.writelines = io.writelines - --- Add string metamethods to the string metatable. -local string_metatable = getmetatable "" -string_metatable.__append = string.__append -string_metatable.__concat = string.__concat -string_metatable.__index = string.__index - --- Maintain old global interface access points. -for _, api in ipairs { - --- Partially apply a function. - -- @function _G.bind - -- @see std.functional.bind - "functional.bind", - - --- Collect the results of an iterator. - -- @function _G.collect - -- @see std.functional.collect - "functional.collect", - - --- Compose functions. - -- @function _G.compose - -- @see std.functional.compose - "functional.compose", - - --- Curry a function. - -- @function _G.curry - -- @see std.functional.curry - "functional.curry", - - --- Evaluate a string. - -- @function _G.eval - -- @see std.functional.eval - "functional.eval", - - --- Filter an iterator with a predicate. - -- @function _G.filter - -- @see std.functional.filter - "functional.filter", - - --- Fold a binary function into an iterator. - -- @function _G.fold - -- @see std.functional.fold - "functional.fold", - - --- Identity function. - -- @function _G.id - -- @see std.functional.id - "functional.id", - - --- Map a function over an iterator. - -- @function _G.map - -- @see std.functional.map - "functional.map", - - --- Memoize a function, by wrapping it in a functable. - -- @function _G.memoize - -- @see std.functional.memoize - "functional.memoize", - - --- Functional forms of infix operators. - -- @table _G.op - -- @see std.functional.op - "functional.op", - - - - --- Die with an error. - -- @function _G.die - -- @see std.io.die - "io.die", - - --- Give a warning with the name of program and file (if any). - -- @function _G.warn - -- @see std.io.warn - "io.warn", - - - - --- Extend to allow formatted arguments. - -- @function _G.assert - -- @see std.string.assert - "string.assert", - - --- Convert a value to a string. - -- @function _G.pickle - -- @see std.string.pickle - "string.pickle", - - --- Pretty-print a table. - -- @function _G.prettytostring - -- @see std.string.prettytostring - "string.prettytostring", - - --- Turn tables into strings with recursion detection. - -- @function _G.render - -- @see std.string.render - "string.render", - - --- Require a module with a particular version. - -- @function _G.require_version - -- @see std.string.require_version - "string.require_version", - - --- Extend `tostring` to work better on tables. - -- @function _G.tostring - -- @see std.string.tostring - "string.tostring", - - - - --- Return given metamethod, if any, else nil. - -- @function _G.metamethod - -- @see std.table.metamethod - "table.metamethod", - - --- Turn a tuple into a list. - -- @function _G.pack - -- @see std.table.pack - "table.pack", - - --- An iterator like ipairs, but in reverse. - -- @function _G.ripairs - -- @see std.table.ripairs - "table.ripairs", - - --- Turn an object into a table, according to `__totable` metamethod. - -- @function _G.totable - -- @see std.table.totable - "table.totable", - - - - --- Tree iterator which returns just numbered leaves, in order. - -- @function _G.ileaves - -- @see std.tree.ileaves - "tree.ileaves", - - --- Tree iterator over numbered nodes, in order. - -- @function _G.inodes - -- @see std.tree.inodes - "tree.inodes", - - --- Tree iterator which returns just leaves. - -- @function _G.leaves - -- @see std.tree.leaves - "tree.leaves", - - --- Tree iterator. - -- @function _G.nodes - -- @see std.tree.nodes - "tree.nodes", -} do - local module, method = api:match "^(.*)%.(.-)$" - _G[method] = _G[module][method] -end -local M = { - version = version, +M = { + barrel = barrel, + monkey_patch = monkey_patch, + version = version, } diff --git a/lib/std/io.lua b/lib/std/io.lua index 46f0db2..d0a151f 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -10,6 +10,9 @@ local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } +local M -- forward declaration + + -- Get an input file handle. -- @param h file handle or name (default: `io.input ()`) -- @return file handle, or nil on error @@ -61,6 +64,24 @@ local function writelines (h, ...) end end +--- Overwrite core methods and metamethods with `std` enhanced versions. +-- +-- Adds `readlines` and `writelines` metamethods to core file objects. +-- @tparam[opt=_G] table namespace where to install global functions +-- @treturn table the module table +local function monkey_patch (namespace) + namespace = namespace or _G + + assert (type (namespace) == "table", + "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") + + local file_metatable = getmetatable (namespace.io.stdin) + file_metatable.readlines = readlines + file_metatable.writelines = writelines + + return M +end + --- Split a directory path into components. -- Empty components are retained: the root directory becomes `{"", ""}`. -- @param path path @@ -162,10 +183,11 @@ end --- @export -local M = { +M = { catdir = catdir, catfile = catfile, die = die, + monkey_patch = monkey_patch, process_files = process_files, readlines = readlines, shell = shell, @@ -175,9 +197,6 @@ local M = { writelines = writelines, } --- camelCase compatibility. -M.processFiles = process_files - for k, v in pairs (io) do M[k] = M[k] or v end diff --git a/lib/std/math.lua b/lib/std/math.lua index a50859f..b595bb1 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -3,6 +3,8 @@ @module std.math ]] +local M -- forward declaration + local _floor = math.floor @@ -21,6 +23,21 @@ local function floor (n, p) end +--- Overwrite core methods with `std` enhanced versions. +-- +-- Replaces core `math.floor` with `std.math` version. +-- @tparam[opt=_G] table namespace where to install global functions +-- @treturn table the module table +local function monkey_patch (namespace) + namespace = namespace or _G + assert (type (namespace) == "table", + "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") + + namespace.math.floor = floor + return M +end + + --- Round a number to a given number of decimal places -- @function round -- @param n number @@ -32,16 +49,14 @@ local function round (n, p) end -local Math = { - floor = floor, - round = round, - - -- Core Lua function implementations. - _floor = _floor, +local M = { + floor = floor, + monkey_patch = monkey_patch, + round = round, } for k, v in pairs (math) do - Math[k] = Math[k] or v + M[k] = M[k] or v end -return Math +return M diff --git a/lib/std/string.lua b/lib/std/string.lua index d4cc377..73882ae 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -366,6 +366,32 @@ local function prettytostring (t, indent, spacing) end +--- Overwrite core methods and metamethods with `std` enhanced versions. +-- +-- Adds auto-stringification to `..` operator on core strings, and +-- integer indexing of strings with `[]` dereferencing. +-- +-- Also replaces core `assert` and `tostring` functions with +-- `std.string` versions. +-- @tparam[opt=_G] table namespace where to install global functions +-- @treturn table the module table +local function monkey_patch (namespace) + namespace = namespace or _G + + assert (type (namespace) == "table", + "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") + + namespace.assert, namespace.tostring = assert, tostring + + local string_metatable = getmetatable "" + string_metatable.__append = __append + string_metatable.__concat = __concat + string_metatable.__index = __index + + return M +end + + --- Convert a value to a string. -- The string can be passed to dostring to retrieve the value. -- @todo Make it work for recursive tables. @@ -545,7 +571,7 @@ end --- @export -local String = { +M = { __append = __append, __concat = __concat, __index = __index, @@ -557,6 +583,7 @@ local String = { finds = finds, format = format, ltrim = ltrim, + monkey_patch = monkey_patch, numbertosi = numbertosi, ordinal_suffix = ordinal_suffix, pad = pad, @@ -572,16 +599,6 @@ local String = { wrap = wrap, } --- Merge non-@export functions: -for k,v in pairs (table.merge (String, { - -- camelCase compatibility: - escapePattern = escape_pattern, - escapeShell = escape_shell, - ordinalSuffix = ordinal_suffix, -})) do - M[k] = v -end - for k, v in pairs (string) do M[k] = M[k] or v end diff --git a/lib/std/table.lua b/lib/std/table.lua index 626afdb..217a3c6 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -5,6 +5,9 @@ local base = require "std.base" + +local M -- forward declaration + -- No need to pull all of std.list into memory. local elems = base.elems @@ -109,6 +112,21 @@ local function sort (t, c) end +--- Overwrite core methods with `std` enhanced versions. +-- +-- Replaces core `table.sort` with `std.table` version. +-- @tparam[opt=_G] table namespace where to install global functions +-- @treturn table the module table +local function monkey_patch (namespace) + namespace = namespace or _G + assert (type (namespace) == "table", + "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") + + namespace.table.sort = sort + return M +end + + --- Return whether table is empty. -- @tparam table t any table -- @return `true` if `t` is empty, otherwise `false` @@ -230,7 +248,7 @@ end --- @export -local Table = { +M = { clone = clone, clone_select = clone_select, empty = empty, @@ -239,6 +257,7 @@ local Table = { merge = merge, merge_select = merge_select, metamethod = metamethod, + monkey_patch = monkey_patch, new = new, pack = pack, ripairs = ripairs, @@ -246,16 +265,13 @@ local Table = { sort = sort, totable = totable, values = values, - - -- Core Lua table.sort function - _sort = _sort, } -- Deprecated and undocumented. -Table.clone_rename = clone_rename +M.clone_rename = clone_rename for k, v in pairs (table) do - Table[k] = Table[k] or v + M[k] = M[k] or v end -return Table +return M diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 4fbcf3a..2520d5c 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -1,9 +1,8 @@ before: | + base_module = "debug" this_module = "std.debug" - global_table = "_G" - base_module = "debug" extend_base = { "say", "trace" } M = require "std.debug" @@ -14,14 +13,20 @@ specify std.debug: - 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 adds apis to the core debug table: + - 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_contain.a_permutation_of (extend_base) + to_equal {} - describe _DEBUG: diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index e44a957..a3f75dd 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -1,9 +1,6 @@ before: | - global_table = "_G" this_module = "std.functional" - std_globals = { "bind", "collect", "compose", "curry", "eval", - "filter", "fold", "id", "map", "memoize", - "metamethod", "op" } + global_table = "_G" M = require (this_module) @@ -15,19 +12,19 @@ specify std.functional: to_equal {} - context via the std module: - - it adds apis to the global table: + - it does not touch the global table: expect (show_apis {added_to=global_table, by="std"}). - to_contain.all_of (std_globals) + to_equal {} - describe bind: - it does not affect normal operation if no arguments are bound: expect (M.bind (math.min, {}) (2, 3, 4)). to_equal (2) - - the extra arguments are taken into account: + - it takes the extra arguments into account: expect (M.bind (math.min, {1, 0}) (2, 3, 4)). to_equal (0) - - the extra arguments can be out of order: + - it supports out of order extra arguments: expect (M.bind (math.pow, {[2] = 3}) (2)). to_equal (8) diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 1c26e36..33ab3df 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -1,16 +1,11 @@ before: | + base_module = "io" this_module = "std.io" - global_table = "_G" - std_globals = { "die", "warn" } - base_module = "io" - extend_base = { "catdir", "catfile", "die", "process_files", - "readlines", "shell", "slurp", "splitdir", - "warn", "writelines", - -- camelCase compatibility: - "processFiles" } - extend_metamethods = { "readlines", "writelines" } + extend_base = { "catdir", "catfile", "die", "monkey_patch", + "process_files", "readlines", "shell", "slurp", + "splitdir", "warn", "writelines" } M = require (this_module) @@ -20,25 +15,19 @@ specify std.io: - 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) - - it replaces no apis from the core io table: - expect (show_apis {from=base_module, enhanced_in=this_module}). - to_equal {} - context via the std module: - - it adds apis to the global table: + - it does not touch the global table: expect (show_apis {added_to=global_table, by="std"}). - to_contain.all_of (std_globals) - - it adds apis to the core io table: + to_equal {} + - it does not touch the core io table: expect (show_apis {added_to=base_module, by="std"}). - to_contain.a_permutation_of (extend_base) - - it adds methods to the file metatable: - expect (show_apis {added_to="getmetatable (io.stdin)", by="std"}). - to_contain.a_permutation_of (extend_metamethods) - - it replaces no apis from the core io table: - expect (show_apis {from=base_module, enhanced_after='require "std"'}). to_equal {} @@ -102,9 +91,28 @@ specify std.io: expect (luaproc (script)).to_fail_with "program:99: By 'eck!\n" +- describe monkey_patch: + - before: + f = M.monkey_patch + mt = {} + t = { + io = { + stdin = setmetatable ({}, mt), + stdout = setmetatable ({}, mt), + stderr = setmetatable ({}, mt), + }, + } + - it installs readlines metamethod: + f (t) + expect (mt.readlines).to_be (M.readlines) + - it installs writelines metamethod: + f (t) + expect (mt.writelines).to_be (M.writelines) + - it diagnoses non-table argument: + expect (f "bad").to_error "table expected" + + - describe process_files: - - it is the same function as legacy processFiles call: - expect (M.process_files).to_be (M.processFiles) - describe readlines: diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 8c7895b..8d05107 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -1,15 +1,9 @@ before: | + base_module = "math" this_module = "std.math" - global_table = "_G" - base_module = "math" - extend_base = { "round", "_floor" } - enhance_base = { "floor" } - -- 'to_contain' will match keys as well as values :) - all_apis = {} - for _, s in ipairs (extend_base) do all_apis[s] = true end - for _, s in ipairs (enhance_base) do all_apis[s] = true end + extend_base = { "floor", "monkey_patch", "round" } M = require (this_module) @@ -19,23 +13,36 @@ specify std.math: - 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 (all_apis) - - it enhances some apis from the core math table: - expect (show_apis {from=base_module, enhanced_in=this_module}). - to_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (extend_base) - context via the std module: - - it adds apis to the core math table: + - 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_contain.a_permutation_of (extend_base) - - it replaces some apis from the core math table: - expect (show_apis {from=base_module, enhanced_after='require "std"'}). - to_contain.a_permutation_of (enhance_base) + to_equal {} - describe floor: +- describe monkey_patch: + - before: + f = M.monkey_patch + t = { + math = {}, + } + f (t) + - it installs math.floor function: + expect (t.math.floor).to_be (M.floor) + - it diagnoses non-table argument: + expect (f "bad").to_error "table expected" + + - describe round: diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index f93683e..184cd00 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -1,8 +1,8 @@ before: | + base_module = "package" this_module = "std.package" - global_table = "_G" - base_module = "package" + extend_base = { "dirsep", "execdir", "find", "igmark", "insert", "mappath", "normalize", "pathsep", "path_mark", "remove" } @@ -21,19 +21,19 @@ specify std.package: - 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) - - it replaces no apis from the core package table: - expect (show_apis {from=base_module, enhanced_in=this_module}). - to_equal {} - context via the std module: - - it adds apis to the core package table: + - 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_contain.a_permutation_of (extend_base) - - it replaces no apis from the core package table: - expect (show_apis {from=base_module, enhanced_after='require "std"'}). to_equal {} diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index e4754b4..f6477f5 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -1,12 +1,114 @@ +before: + std = require "std" + specify std: - describe lazy loading: - - before: - std = require "std" - it has no submodules on initial load: - expect (std).to_equal {version = std.version} + expect (std).to_equal { + barrel = std.barrel, + monkey_patch = std.monkey_patch, + version = std.version, + } - it loads submodules on demand: lazy = std.set expect (lazy).to_be (require "std.set") - it loads submodule functions on demand: expect (std.object.prototype (std.set {"Lazy"})). to_be "Set" + +- describe barrel: + - before: + f = std.barrel + io_mt = {} + t = { + io = { + stdin = setmetatable ({}, io_mt), + stdout = setmetatable ({}, io_mt), + stderr = setmetatable ({}, io_mt), + }, + math = {}, + table = {}, + } + f (t) + - it installs std.io monkey patches: + expect (io_mt.readlines).to_be (std.io.readlines) + expect (io_mt.writelines).to_be (std.io.writelines) + - it installs std.math monkey patches: + expect (t.math.floor).to_be (std.math.floor) + - it installs std.string monkey patches: + # FIXME: string metatable monkey-patches leak out! + mt = getmetatable "" + expect (mt.__append).to_be (std.string.__append) + expect (mt.__concat).to_be (std.string.__concat) + expect (mt.__index).to_be (std.string.__index) + expect (t.assert).to_be (std.string.assert) + expect (t.tostring).to_be (std.string.tostring) + - it installs std.table monkey patches: + expect (t.table.sort).to_be (std.table.sort) + - it scribbles into the supplied namespace: + expect (t).should_equal { + assert = std.string.assert, + bind = std.functional.bind, + collect = std.functional.collect, + compose = std.functional.compose, + curry = std.functional.curry, + die = std.io.die, + eval = std.functional.eval, + filter = std.functional.filter, + fold = std.functional.fold, + id = std.functional.id, + ileaves = std.tree.ileaves, + inodes = std.tree.inodes, + io = t.io, + leaves = std.tree.leaves, + map = std.functional.map, + math = t.math, + memoize = std.functional.memoize, + metamethod = std.table.metamethod, + nodes = std.tree.nodes, + op = std.functional.op, + pack = std.table.pack, + pickle = std.string.pickle, + prettytostring = std.string.prettytostring, + render = std.string.render, + require_version = std.string.require_version, + ripairs = std.table.ripairs, + table = t.table, + tostring = std.string.tostring, + totable = std.table.totable, + warn = std.io.warn, + } + - it diagnoses non-table argument: + expect (f "bad").to_error "table expected" + +- describe monkey_patch: + - before: + f = std.monkey_patch + io_mt = {} + t = { + io = { + stdin = setmetatable ({}, io_mt), + stdout = setmetatable ({}, io_mt), + stderr = setmetatable ({}, io_mt), + }, + math = {}, + table = {}, + } + f (t) + - it installs std.io monkey patches: + expect (io_mt.readlines).to_be (std.io.readlines) + expect (io_mt.writelines).to_be (std.io.writelines) + - it installs std.math monkey patches: + expect (t.math.floor).to_be (std.math.floor) + - it installs std.string monkey patches: + # FIXME: string metatable monkey-patches leak out! + mt = getmetatable "" + expect (mt.__append).to_be (std.string.__append) + expect (mt.__concat).to_be (std.string.__concat) + expect (mt.__index).to_be (std.string.__index) + expect (t.assert).to_be (std.string.assert) + expect (t.tostring).to_be (std.string.tostring) + - it installs std.table monkey patches: + expect (t.table.sort).to_be (std.table.sort) + - it diagnoses non-table argument: + expect (f "bad").to_error "table expected" diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 942f7e5..83345eb 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -1,36 +1,20 @@ before: | + base_module = "string" this_module = "std.string" - global_table = "_G" - std_globals = { "pickle", "prettytostring", "render", - "require_version" } - enhance_globals = { "assert", "tostring" } - base_module = "string" extend_base = { "__append", "__concat", "__index", "assert", "caps", "chomp", "escape_pattern", - "escape_shell", "finds", "ltrim", "numbertosi", - "ordinal_suffix", "pad", "pickle", - "prettytostring", "render", "require_version", - "rtrim", "split", "tfind", "tostring", "trim", - "wrap", - -- camelCase compatibility: - "escapePattern", "escapeShell", - "ordinalSuffix" } - enhance_base = { "format" } - extend_metamethods = { "__append", "__concat" } - enhance_metamethods = { "__index" } - - -- 'to_contain' will match keys as well as values :) - all_apis = {} - for _, s in ipairs (std_globals) do all_apis[s] = true end - for _, s in ipairs (enhance_globals) do all_apis[s] = true end - for _, s in ipairs (extend_base) do all_apis[s] = true end - for _, s in ipairs (enhance_base) do all_apis[s] = true end - - M = require "std.string" - getmetatable("").__index = M.__index + "escape_shell", "finds", "format", "ltrim", + "monkey_patch", "numbertosi", "ordinal_suffix", + "pad", "pickle", "prettytostring", "render", + "require_version", "rtrim", "split", "tfind", + "tostring", "trim", "wrap" } + + M = require (this_module) + getmetatable ("").__append = M.__append getmetatable ("").__concat = M.__concat + getmetatable ("").__index = M.__index specify std.string: - before: @@ -41,42 +25,34 @@ specify std.string: - 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 (all_apis) - - it enhances some apis from the core string table: - expect (show_apis {from=base_module, enhanced_in=this_module}). - to_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (extend_base) - context via the std module: - - it adds apis to the global table: + - it does not touch the global table: expect (show_apis {added_to=global_table, by="std"}). - to_contain.all_of (std_globals) - - it adds apis to the core string table: + to_equal {} + - it does not touch the core string table: expect (show_apis {added_to=base_module, by="std"}). - to_contain.a_permutation_of (extend_base) - - it adds methods to the string metatable: - expect (show_apis {added_to="getmetatable ('')", by="std"}). - to_contain.a_permutation_of (extend_metamethods) - - it replaces some entries in the string metatable: - expect (show_apis {from="getmetatable ('')", enhanced_after='require "std"'}). - to_contain.a_permutation_of (enhance_metamethods) - - it replaces some apis in the core string table: - expect (show_apis {from=base_module, enhanced_after='require "std"'}). - to_contain.a_permutation_of (enhance_base) - + 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": + - it stringifies non-string arguments: argument = { "a table" } - expect (subject .. argument).to_match (string.format ("%s%s", subject, M.tostring (argument))) + expect (subject .. argument). + to_be (string.format ("%s%s", subject, M.tostring (argument))) - it stringifies nil arguments: argument = nil - expect (subject .. argument).to_be (string.format ("%s%s", subject, M.tostring (argument))) - - the original subject is not perturbed: + expect (subject .. argument). + to_be (string.format ("%s%s", subject, argument)) + - it does not perturb the original subject: original = subject newstring = subject .. " concatenate something" expect (subject).to_be (original) @@ -95,7 +71,7 @@ specify std.string: expect (f "a stRiNg").to_be "A StRiNg" - it is available as a string metamethod: expect (("a stRiNg"):caps ()).to_be "A StRiNg" - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject) expect (subject).to_be (original) @@ -115,7 +91,7 @@ specify std.string: expect (f (subject)).to_be (subject) - it is available as a string metamethod: expect (subject:chomp ()).to_be (target) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject) expect (subject).to_be (original) @@ -147,9 +123,7 @@ specify std.string: expect (f (subject)).to_be (target) - it is available as a string metamethod: expect (subject:escape_pattern ()).to_be (target) - - legacy escapePattern call is the same function: - expect (M.escapePattern).to_be (f) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject) expect (subject).to_be (original) @@ -174,9 +148,7 @@ specify std.string: expect (f (subject)).to_be (target) - it is available as a string metamethod: expect (subject:escape_shell ()).to_be (target) - - legacy escapeShell call is the same function: - expect (M.escapeShell).to_be (f) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject) expect (subject).to_be (original) @@ -205,7 +177,7 @@ specify std.string: - 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 }) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject, "...") expect (subject).to_be (original) @@ -223,7 +195,7 @@ specify std.string: expect (f (subject)).to_be (subject) - it is available as a string metamethod: expect (subject:format ()).to_be (subject) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject) expect (subject).to_be (original) @@ -245,7 +217,7 @@ specify std.string: - it is available as a string metamethod: target = "\r\n a short string \t\r\n " expect (subject:ltrim ("[ \t\n]+")).to_equal (target) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject, "%W") expect (subject).to_be (original) @@ -254,6 +226,31 @@ specify std.string: expect (f {"a table"}).to_error ("string expected") +- describe monkey_patch: + - before: + f = M.monkey_patch + t = {} + f (t) + - it installs append metamethod: + # FIXME: string metatable monkey-patches leak out! + mt = getmetatable "" + expect (mt.__append).to_be (M.__append) + - it installs concat metamethod: + # FIXME: string metatable monkey-patches leak out! + mt = getmetatable "" + expect (mt.__concat).to_be (M.__concat) + - it installs index metamethod: + # FIXME: string metatable monkey-patches leak out! + mt = getmetatable "" + expect (mt.__index).to_be (M.__index) + - it installs the assert function: + expect (t.assert).to_be (M.assert) + - it installs the tostring function: + expect (t.tostring).to_be (M.tostring) + - it diagnoses non-table argument: + expect (f "bad").to_error "table expected" + + - describe numbertosi: - before: f = M.numbertosi @@ -289,8 +286,6 @@ specify std.string: table.insert (subject, n .. f (n)) end expect (subject).to_equal (target) - - legacy ordinalSuffix call is the same function: - expect (M.ordinalSuffix).to_be (f) - it coerces string arguments to a number: expect (f "-91").to_be "st" - "it diagnoses non-numeric arguments": @@ -331,7 +326,7 @@ specify std.string: target = "a string that's long" expect (subject:pad (width)).to_be (target) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject, width) expect (subject).to_be (original) @@ -401,7 +396,7 @@ specify std.string: - it is available as a string metamethod: target = " \t\r\n a short string \t\r" expect (subject:rtrim ("[ \t\n]+")).to_equal (target) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject, "%W") expect (subject).to_be (original) @@ -437,7 +432,7 @@ specify std.string: expect (subject:split ", ").to_equal (target) expect (("/foo/bar/baz.quux"):split "/"). to_equal {"", "foo", "bar", "baz.quux"} - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject, "e") expect (subject).to_be (original) @@ -468,7 +463,7 @@ specify std.string: - it is available as a string metamethod: target = { 8, 10, { "a", "b", "c" } } expect ({("garbage" .. subject):tfind ("(.)(.)(.)", 8)}).to_equal (target) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject, "...") expect (subject).to_be (original) @@ -493,7 +488,7 @@ specify std.string: - it is available as a string metamethod: target = "\r\n a short string \t\r" expect (subject:trim ("[ \t\n]+")).to_equal (target) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject, "%W") expect (subject).to_be (original) @@ -542,7 +537,7 @@ specify std.string: 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) - - the original subject is not perturbed: + - it does not perturb the original subject: original = subject newstring = f (subject, 55, 5) expect (subject).to_be (original) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 27e0681..02679b5 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -3,27 +3,14 @@ before: | this_module = "std.table" global_table = "_G" - std_globals = { "pack", "ripairs", "totable" } - extend_base = { "clone", "clone_select", "clone_rename", "empty", + extend_base = { "clone", "clone_rename", "clone_select", "empty", "invert", "keys", "merge", "merge_select", - "metamethod", "new", "ripairs", "size", "totable", - "values", - -- make these available after require "std" - "_sort" } - enhance_base = { "sort" } + "metamethod", "monkey_patch", "new", "ripairs", + "size", "sort", "totable", "values" } - -- 'to_contain' will match keys as well as values :) - all_apis = {} - for _, s in ipairs (std_globals) do all_apis[s] = true end - for _, s in ipairs (extend_base) do all_apis[s] = true end - for _, s in ipairs (enhance_base) do all_apis[s] = true end - + -- Lua 5.2 if table.pack then - -- Lua 5.2 - table.insert (enhance_base, "pack") - else - -- Lua 5.1 table.insert (extend_base, "pack") end @@ -35,23 +22,18 @@ specify std.table: - 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 (all_apis) - - it enhances some apis from the core table table: - expect (show_apis {from=base_module, enhanced_in=this_module}). - to_contain.a_permutation_of (enhance_base) + to_contain.a_permutation_of (extend_base) - context via the std module: - - it adds apis to the global table: - expect (show_apis {added_to=global_table, by="std"}). - to_contain.all_of (std_globals) - - it adds apis to the core table table: - expect (show_apis {added_to=base_module, by="std"}). - to_contain.a_permutation_of (extend_base) - - it replaces some apis in the core table table: - expect (show_apis {from=base_module, enhanced_after='require "std"'}). - to_contain.a_permutation_of (enhance_base) + - 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: @@ -65,7 +47,7 @@ specify std.table: expect (f (subject)).to_equal (subject) - it only makes a shallow copy of field values: expect (f (subject).k1).to_be (subject.k1) - - the original subject is not perturbed: + - 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) @@ -137,7 +119,7 @@ specify std.table: expect (f (subject, {"k1", "k2", "k3"})).to_equal (subject) - it only makes a shallow copy: expect (f (subject, {"k1"}).k1).to_be (subject.k1) - - the original subject is not perturbed: + - 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) @@ -314,6 +296,19 @@ specify std.table: expect (f ("foo", "bar")).to_error ("table expected") +- describe monkey_patch: + - before: + f = M.monkey_patch + t = { + table = {}, + } + f (t) + - it installs table.sort function: + expect (t.table.sort).to_be (M.sort) + - it diagnoses non-table argument: + expect (f "bad").to_error "table expected" + + - describe new: - before: f = M.new diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 9fbb862..0d9da21 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -1,7 +1,6 @@ before: | global_table = "_G" this_module = "std.tree" - std_globals = { "ileaves", "inodes", "leaves", "nodes" } Tree = require "std.tree" @@ -19,9 +18,9 @@ specify std.tree: to_equal {} - context via the std module: - - it adds apis to the global table: + - it does not touch the global table: expect (show_apis {added_to=global_table, by="std"}). - to_contain.all_of (std_globals) + to_equal {} - describe construction: - it constructs a new tree: From e673d5974ef03ed4dd0e566be81a546fb95b44cd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 15:10:46 +0700 Subject: [PATCH 142/703] specs: Lua 5.1 does not call tostring on format "%s" arguments. * specs/string_spec.yaml (..): Explicitly stringify nil argument. Signed-off-by: Gary V. Vaughan --- specs/string_spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 83345eb..205ff4f 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -51,7 +51,7 @@ specify std.string: - it stringifies nil arguments: argument = nil expect (subject .. argument). - to_be (string.format ("%s%s", subject, argument)) + to_be (string.format ("%s%s", subject, M.tostring (argument))) - it does not perturb the original subject: original = subject newstring = subject .. " concatenate something" From bfc00108965b038805b9da6d52a86626534313a6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 15:22:52 +0700 Subject: [PATCH 143/703] specs: std.table.pack is present for any supported Lua release. * specs/table_spec.yaml (extend_base): List "pack" unconditionally. Signed-off-by: Gary V. Vaughan --- specs/table_spec.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 02679b5..e0f72cf 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -5,14 +5,8 @@ before: | extend_base = { "clone", "clone_rename", "clone_select", "empty", "invert", "keys", "merge", "merge_select", - "metamethod", "monkey_patch", "new", "ripairs", - "size", "sort", "totable", "values" } - - - -- Lua 5.2 - if table.pack then - table.insert (extend_base, "pack") - end + "metamethod", "monkey_patch", "new", "pack", + "ripairs", "size", "sort", "totable", "values" } M = require "std.table" From 1a39efc3787456915a97a90780fc58df44b3fbcb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 26 Apr 2014 19:33:10 +0700 Subject: [PATCH 144/703] list: remove deprecated methods. * lib/std/list.lua: Remove indexKey, indexValue, mapWith, zipWith, new and slice. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 7 ++++- lib/std/list.lua | 71 ++++++++++++++++++++---------------------------- 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/NEWS b/NEWS index 4ee4cc1..0d6ebdb 100644 --- a/NEWS +++ b/NEWS @@ -44,9 +44,14 @@ Stdlib NEWS - User visible changes - The following deprecated camelCase names have been removed, you should update your code to use the snake_case equivalents: - `std.io.processFiles`, `std.string.escapePattern`, + `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), and + `std.list.slice` (use `std.list.sub` instead). + * Noteworthy changes in release 39 (2014-04-23) [stable] ** New features: diff --git a/lib/std/list.lua b/lib/std/list.lua index 1a5347e..7e44ddb 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -386,39 +386,11 @@ local function zip_with (ls, f) end ---- @export -local _functions = { - append = append, - compare = compare, - concat = concat, - cons = cons, - depair = depair, - elems = elems, - enpair = enpair, - filter = filter, - flatten = flatten, - foldl = foldl, - foldr = foldr, - index_key = index_key, - index_value = index_value, - map = map, - map_with = map_with, - project = project, - relems = relems, - rep = rep, - reverse = reverse, - shape = shape, - sub = sub, - tail = tail, - transpose = transpose, - zip_with = zip_with, -} - - List = Object { -- Derived object type. _type = "List", + ------ -- Concatenate lists. -- new = list .. table @@ -596,24 +568,39 @@ List = Object { depair = depair, index_key = function (self, f) return index_key (f, self) end, index_value = function (self, f) return index_value (f, self) end, - indexKey = function (self, f) return indexKey (f, self) end, - indexValue = function (self, f) return indexValue (f, self) end, map_with = function (self, f) return map_with (f, self) end, transpose = transpose, zip_with = function (self, f) return zip_with (f, self) end, }, - _functions = (base.merge (_functions, { - -- backwards compatibility - new = function (t) return List (t or {}) end, - slice = sub, - - -- camelCase compatibility - indexKey = index_key, - indexValue = index_value, - mapWith = map_with, - zipWith = zip_with, - })), + + --- @export + _functions = { + append = append, + compare = compare, + concat = concat, + cons = cons, + depair = depair, + elems = elems, + enpair = enpair, + filter = filter, + flatten = flatten, + foldl = foldl, + foldr = foldr, + index_key = index_key, + index_value = index_value, + map = map, + map_with = map_with, + project = project, + relems = relems, + rep = rep, + reverse = reverse, + shape = shape, + sub = sub, + tail = tail, + transpose = transpose, + zip_with = zip_with, + }, } From c10c30da8b2c434e4777dc579e289a283c9e14b2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 14:40:15 +0700 Subject: [PATCH 145/703] functional: generalize memoize with normalization parameter. * lib/std/functional.lua (memoize): Don't rely on tostring having been monkey-patched to std.string.tostring already, but also don't automatically load all of std.string into memory unless memoize is called without a normalization function. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/functional.lua | 23 +++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 0d6ebdb..c4a5544 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,9 @@ Stdlib NEWS - User visible changes ** 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`. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index fc85f91..fb8e4c3 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -92,13 +92,32 @@ local function compose (...) end +--- Signature of memoize `normalize` functions. +-- @function memoize_normalize +-- @param ... arguments +-- @treturn string normalized arguments + + --- Memoize a function, by wrapping it in a functable. +-- +-- To ensure that memoize always returns the same object for the same +-- arguments, it passes arguments to `normalize` (std.string.tostring +-- by default). You may need a more sophisticated function if memoize +-- should handle complicated argument equivalencies. -- @param fn function that returns a single result +-- @param normalize[opt] function to normalize arguments -- @return memoized function -local function memoize (fn) +local function memoize (fn, normalize) + if normalize == nil then + -- Call require here, to avoid pulling in all of 'std.string' + -- even when memoize is never called. + local stringify = require "std.string".tostring + normalize = function (...) return stringify {...} end + end + return setmetatable ({}, { __call = function (self, ...) - local k = tostring ({...}) + local k = normalize (...) local v = self[k] if v == nil then v = fn (...) From 7e070dffd0c487f44635611b42a2fb12bdf3de7f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 14:57:42 +0700 Subject: [PATCH 146/703] tree: allow objects as keys. * lib/std/tree.lua (Tree.__index): Only fold key list when key parameter is a raw list, and not when it is a std.object derived table. (Tree.__newindex): Only descend the key list creating sub-Trees when key parameter is a raw list, and not when it is a std.object derived table. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 5 +++++ lib/std/tree.lua | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index c4a5544..6d4a2f1 100644 --- a/NEWS +++ b/NEWS @@ -55,6 +55,11 @@ Stdlib NEWS - User visible changes `std.list.new` (call `std.list` directly instead), and `std.list.slice` (use `std.list.sub` instead). +** Bug fixes: + + - Allow `std.object` derived tables as `std.tree` keys again. + + * Noteworthy changes in release 39 (2014-04-23) [stable] ** New features: diff --git a/lib/std/tree.lua b/lib/std/tree.lua index a809fc7..da9aa12 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -13,7 +13,7 @@ local base = require "std.base" local Container = require "std.container" -local List = require "std.list" +local list = require "std.list" local func = require "std.functional" local prototype = (require "std.object").prototype @@ -207,8 +207,8 @@ Tree = Container { -- @todo the following doesn't treat list keys correctly -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? __index = function (self, i) - if type (i) == "table" then - return List.foldl (func.op["[]"], self, i) + if prototype (i) == "table" then + return list.foldl (func.op["[]"], self, i) else return rawget (self, i) end @@ -221,7 +221,7 @@ Tree = Container { -- @param i non-table, or list of keys `{i\_1 ... i\_n}` -- @param v value __newindex = function (self, i, v) - if type (i) == "table" then + if prototype (i) == "table" then for n = 1, #i - 1 do if prototype (self[i[n]]) ~= "Tree" then rawset (self, i[n], Tree {}) From 169417aa7f37311bc83dd0ca20c6fc6ed1d61985 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 15:53:45 +0700 Subject: [PATCH 147/703] refactor: remove deprecated methods. * lib/std/base.lua (new, metatable): Remove. (concat, M): Simplify accordingly. * lib/std/functional.lua: No need to require std.base. * lib/std/set.lua (_functions): Move into object declaration, removing `new`. * lib/std/strbuf.lua (new): Remove. * lib/std/tree.lua (new): Remove. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 7 +++++-- lib/std/base.lua | 37 +------------------------------------ lib/std/functional.lua | 2 -- lib/std/set.lua | 34 ++++++++++++++-------------------- lib/std/strbuf.lua | 5 ----- lib/std/tree.lua | 24 +++++++++--------------- 6 files changed, 29 insertions(+), 80 deletions(-) diff --git a/NEWS b/NEWS index 6d4a2f1..835b030 100644 --- a/NEWS +++ b/NEWS @@ -52,8 +52,11 @@ Stdlib NEWS - User visible changes `std.string. escapeShell`, `std.string.ordinalSuffix`. - The following deprecated function names have been removed: - `std.list.new` (call `std.list` directly instead), and - `std.list.slice` (use `std.list.sub` instead). + `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: diff --git a/lib/std/base.lua b/lib/std/base.lua index 477569d..c3dd410 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -57,8 +57,6 @@ local function merge (t, u, map, nometa) return t end -local new -- forward declaration - -- Doc-commented in list.lua... local function append (l, x) local r = {unpack (l)} @@ -101,7 +99,7 @@ end -- l1[#l1], ..., ln[1], ..., -- ln[#ln]}` local function concat (...) - local r = new () + local r = {} for l in elems ({...}) do for v in elems (l) do table.insert (r, v) @@ -123,35 +121,6 @@ local function _leaves (it, tr) return coroutine.wrap (visit), tr end --- Metamethods for lists --- It would be nice to define this in `list.lua`, but then we --- couldn't keep `new` here, and other modules that really only --- need `list.new` (as opposed to the entire `std.list` API) get --- caught in a dependency loop. -local metatable = { - -- list .. table = list.concat - __concat = concat, - - -- list == list retains its referential meaning - -- - -- list < list = list.compare returns < 0 - __lt = function (l, m) return compare (l, m) < 0 end, - - -- list <= list = list.compare returns <= 0 - __le = function (l, m) return compare (l, m) <= 0 end, - - __append = append, -} - ---- List constructor. --- Needed in order to use metamethods. --- @param t list (as a table), or nil for empty list --- @return list (with list metamethods) -function new (t) - return setmetatable (t or {}, metatable) -end - - -- Doc-commented in tree.lua... local function ileaves (tr) assert (type (tr) == "table", @@ -176,10 +145,6 @@ local M = { leaves = leaves, merge = merge, metamethod = metamethod, - new = new, - - -- list metatable - _list_mt = metatable, } return M diff --git a/lib/std/functional.lua b/lib/std/functional.lua index fb8e4c3..0f39648 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -3,8 +3,6 @@ @module std.functional ]] -local list = require "std.base" - local functional -- forward declaration diff --git a/lib/std/set.lua b/lib/std/set.lua index 94a13a1..13cb404 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -172,22 +172,6 @@ function equal (set1, set2) end ---- @export -local _functions = { - delete = delete, - difference = difference, - elems = elems, - equal = equal, - insert = insert, - intersection = intersection, - member = member, - proper_subset = proper_subset, - subset = subset, - symmetric_difference = symmetric_difference, - union = union, -} - - --- Set prototype object. -- @table std.set -- @string[opt="Set"] _type type of Set, returned by @@ -286,10 +270,20 @@ Set = Container { end, - _functions = base.merge (_functions, { - -- backwards compatibility. - new = function (t) return Set (t or {}) end, - }), + --- @export + _functions = { + delete = delete, + difference = difference, + elems = elems, + equal = equal, + insert = insert, + intersection = intersection, + member = member, + proper_subset = proper_subset, + subset = subset, + symmetric_difference = symmetric_difference, + union = union, + }, } return Set diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index a2ee225..77ed1bb 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -53,9 +53,4 @@ return Object { concat = concat, tostring = tostring, }, - - -- backwards compatibility. - _functions = { - new = function () return StrBuf {} end, - }, } diff --git a/lib/std/tree.lua b/lib/std/tree.lua index da9aa12..45879a2 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -177,17 +177,6 @@ local function merge (t, u) end ---- @export -local _functions = { - clone = clone, - ileaves = ileaves, - inodes = inodes, - leaves = leaves, - merge = merge, - nodes = nodes, -} - - --- Tree prototype object. -- @table std.tree -- @string[opt="Tree"] _type type of Tree, returned by @@ -234,10 +223,15 @@ Tree = Container { end end, - _functions = base.merge (_functions, { - -- backwards compatibility. - new = function (t) return Tree (t or {}) end, - }), + --- @export + _functions = { + clone = clone, + ileaves = ileaves, + inodes = inodes, + leaves = leaves, + merge = merge, + nodes = nodes, + }, } return Tree From 06334cb8fdb27984541df911c944626a9b2314f7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 16:13:55 +0700 Subject: [PATCH 148/703] refactor: move unshared methods out of std.base. * lib/std/base.lua (merge): Move from here... * lib/std/table.lua (merge): ...to here. * lib/std/base.lua (append, compare, concat): Move from here... * lib/std/list.lua (append, compare, concat): ...to here. * specs/object_spec.yaml (std.object): Adjust. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 58 ------------------------------------------ lib/std/list.lua | 50 ++++++++++++++++++++++++++---------- lib/std/table.lua | 21 +++++++++++++-- specs/object_spec.yaml | 4 +-- 4 files changed, 57 insertions(+), 76 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index c3dd410..8ce8c9c 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -37,50 +37,6 @@ local function metamethod (x, n) end --- Doc-commented in table.lua... -local function merge (t, u, map, nometa) - assert (type (t) == "table", - "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") - assert (type (u) == "table", - "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") - map = map or {} - if type (map) ~= "table" then - map, nometa = {}, map - end - - if not nometa then - setmetatable (t, getmetatable (u)) - end - for k, v in pairs (u) do - t[map[k] or k] = v - end - return t -end - --- Doc-commented in list.lua... -local function append (l, x) - local r = {unpack (l)} - table.insert (r, x) - return r -end - --- Doc-commented in list.lua... -local function compare (l, m) - for i = 1, math.min (#l, #m) do - if l[i] < m[i] then - return -1 - elseif l[i] > m[i] then - return 1 - end - end - if #l < #m then - return -1 - elseif #l > #m then - return 1 - end - return 0 -end - -- Doc-commented in list.lua... local function elems (l) local n = 0 @@ -98,16 +54,6 @@ end -- @return `{l1[1], ..., -- l1[#l1], ..., ln[1], ..., -- ln[#ln]}` -local function concat (...) - local r = {} - for l in elems ({...}) do - for v in elems (l) do - table.insert (r, v) - end - end - return r -end - local function _leaves (it, tr) local function visit (n) if type (n) == "table" then @@ -136,14 +82,10 @@ local function leaves (tr) end local M = { - append = append, - compare = compare, - concat = concat, deprecate = deprecate, elems = elems, ileaves = ileaves, leaves = leaves, - merge = merge, metamethod = metamethod, } diff --git a/lib/std/list.lua b/lib/std/list.lua index 7e44ddb..5c146d8 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -43,7 +43,9 @@ local List -- forward declaration -- @param x item -- @treturn List new list containing `{l[1], ..., l[#l], x}` local function append (l, x) - return List (base.append (l, x)) + local r = List {unpack (l)} + table.insert (r, x) + return r end @@ -56,7 +58,32 @@ end -- @tparam table m another list -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 -- if `l` is greater than `m` -local compare = base.compare +local function compare (l, m) + for i = 1, math.min (#l, #m) do + if l[i] < m[i] then + return -1 + elseif l[i] > m[i] then + return 1 + end + end + if #l < #m then + return -1 + elseif #l > #m then + return 1 + end + return 0 +end + + +--- An iterator over the elements of a list. +-- @static +-- @function elems +-- @tparam List l a list +-- @treturn function iterator function which returns successive elements +-- of `l` +-- @treturn List `l` +-- @return `true` +local elems = base.elems --- Concatenate arguments into a list. @@ -65,7 +92,13 @@ local compare = base.compare -- @treturn List new list containing -- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` local function concat (l, ...) - return List (base.concat (l, ...)) + local r = List {} + for e in elems ({l, ...}) do + for v in elems (e) do + table.insert (r, v) + end + end + return r end @@ -78,17 +111,6 @@ local function cons (l, x) end ---- An iterator over the elements of a list. --- @static --- @function elems --- @tparam List l a list --- @treturn function iterator function which returns successive elements --- of `l` --- @treturn List `l` --- @return `true` -local elems = base.elems - - --- Turn a list of pairs into a table. -- @todo Find a better name. -- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` diff --git a/lib/std/table.lua b/lib/std/table.lua index 217a3c6..b95d5bf 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -19,7 +19,24 @@ local elems = base.elems -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` -- @tparam boolean nometa if non-nil don't copy metatable -- @return table `t` with fields from `u` merged in -local merge = base.merge +local function merge (t, u, map, nometa) + assert (type (t) == "table", + "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") + assert (type (u) == "table", + "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") + map = map or {} + if type (map) ~= "table" then + map, nometa = {}, map + end + + if not nometa then + setmetatable (t, getmetatable (u)) + end + for k, v in pairs (u) do + t[map[k] or k] = v + end + return t +end --- Destructively merge another table's named fields into *table*. @@ -63,7 +80,7 @@ end local function clone (t, map, nometa) assert (type (t) == "table", "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") - return base.merge ({}, t, map, nometa) + return merge ({}, t, map, nometa) end diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index acf06ba..cc12538 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -248,10 +248,10 @@ specify std.object: expect (getmetatable (instance)).to_be (getmetatable (Derived)) - context with custom metamethods: - before: - base = require "std.base" + compare = require "std.list".compare bag = Object { _type = "bag", - __lt = function (a, b) return base.compare (a, b) < 0 end, + __lt = function (a, b) return compare (a, b) < 0 end, } - it has it's own metatable: expect (getmetatable (bag)).not_to_be (root_mt) From 7c073e538dc25e920bf2125bc30e22efa00bdeb5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 16:33:41 +0700 Subject: [PATCH 149/703] refactor: move leaves and ileaves from base to tree module. * lib/std/base.lua (ileaves, leaves): Move from here... * lib/std/tree.lua (ileaves, leaves): ...to here. * lib/std/base.lua (_leaves): Rename from this... (leaves): ...to this. * lib/std/list.lua (flatten): Adjust. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 45 ++++++++++++++------------------------------- lib/std/list.lua | 2 +- lib/std/tree.lua | 12 ++++++++++-- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 8ce8c9c..b96663e 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,19 +24,6 @@ local function deprecate (fn, name, warnmsg) end --- Doc-commented in table.lua... -local function metamethod (x, n) - local _, m = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if type (m) ~= "function" then - m = nil - end - return m -end - - -- Doc-commented in list.lua... local function elems (l) local n = 0 @@ -49,12 +36,9 @@ local function elems (l) l, true end ---- Concatenate lists. --- @param ... lists --- @return `{l1[1], ..., --- l1[#l1], ..., ln[1], ..., --- ln[#ln]}` -local function _leaves (it, tr) + +-- Iterator returning leaf nodes from nested tables. +local function leaves (it, tr) local function visit (n) if type (n) == "table" then for _, v in it (n) do @@ -67,24 +51,23 @@ local function _leaves (it, tr) return coroutine.wrap (visit), tr end --- Doc-commented in tree.lua... -local function ileaves (tr) - assert (type (tr) == "table", - "bad argument #1 to 'ileaves' (table expected, got " .. type (tr) .. ")") - return _leaves (ipairs, tr) -end --- Doc-commented in tree.lua... -local function leaves (tr) - assert (type (tr) == "table", - "bad argument #1 to 'leaves' (table expected, got " .. type (tr) .. ")") - return _leaves (pairs, tr) +-- Doc-commented in table.lua... +local function metamethod (x, n) + local _, m = pcall (function (x) + return getmetatable (x)[n] + end, + x) + if type (m) ~= "function" then + m = nil + end + return m end + local M = { deprecate = deprecate, elems = elems, - ileaves = ileaves, leaves = leaves, metamethod = metamethod, } diff --git a/lib/std/list.lua b/lib/std/list.lua index 5c146d8..bf384d0 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -155,7 +155,7 @@ end -- @treturn List flattened list local function flatten (l) local r = List {} - for v in base.ileaves (l) do + for v in base.leaves (ipairs, l) do table.insert (r, v) end return r diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 45879a2..ce1358d 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -27,7 +27,11 @@ local Tree -- forward declaration -- @tparam tree|table tr tree or tree-like table -- @treturn function iterator function -- @treturn tree|table the tree `tr` -local ileaves = base.ileaves +local function ileaves (tr) + assert (type (tr) == "table", + "bad argument #1 to 'ileaves' (table expected, got " .. type (tr) .. ")") + return base.leaves (ipairs, tr) +end --- Tree iterator which returns just leaves. @@ -36,7 +40,11 @@ local ileaves = base.ileaves -- @tparam tree|table tr tree or tree-like table -- @treturn function iterator function -- @treturn tree|table the tree, `tr` -local leaves = base.leaves +local function leaves (tr) + assert (type (tr) == "table", + "bad argument #1 to 'leaves' (table expected, got " .. type (tr) .. ")") + return base.leaves (pairs, tr) +end --- Make a deep copy of a tree, including any metatables. From 1edfe51568f06e28820edf85d1cb4034a2b7f1dd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 16:52:00 +0700 Subject: [PATCH 150/703] refactor: factor out merge_allfields and merge_namedfields. * lib/std/table (clone, merge): Move shared functionality from here... (merge_allfields): ...to here. (clone_select, merge_select): Move shared funtionality from here... (merge_namedfields): ...to here. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 213 +++++++++++++++++++++++++--------------------- 1 file changed, 117 insertions(+), 96 deletions(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index b95d5bf..d3be6f2 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -12,18 +12,13 @@ local M -- forward declaration local elems = base.elems ---- Destructively merge another table's fields into *table*. --- @function merge +--- Merge one table's fields into another. -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` -- @tparam boolean nometa if non-nil don't copy metatable -- @return table `t` with fields from `u` merged in -local function merge (t, u, map, nometa) - assert (type (t) == "table", - "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") - assert (type (u) == "table", - "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") +local function merge_allfields (t, u, map, nometa) map = map or {} if type (map) ~= "table" then map, nometa = {}, map @@ -39,20 +34,14 @@ local function merge (t, u, map, nometa) end ---- Destructively merge another table's named fields into *table*. --- --- Like `merge`, but does not merge any fields by default. +--- Merge one table's named fields into another. -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table keys list of keys to copy -- @tparam boolean nometa if non-nil don't copy metatable -- @return copy of fields in *selection* from *t*, also sharing *t*'s -- metatable unless *nometa* -local function merge_select (t, u, keys, nometa) - assert (type (t) == "table", - "bad argument #1 to 'merge_select' (table expected, got " .. type (t) .. ")") - assert (type (u) == "table", - "bad argument #2 to 'merge_select' (table expected, got " .. type (u) .. ")") +local function merge_namedfields (t, u, keys, nometa) keys = keys or {} if type (keys) ~= "table" then keys, nometa = {}, keys @@ -71,7 +60,6 @@ end --- Make a shallow copy of a table, including any metatable. -- -- To make deep copies, use @{std.tree.clone}. --- @function clone -- @tparam table t source table -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` -- @tparam boolean nometa if non-nil don't copy metatable @@ -80,7 +68,7 @@ end local function clone (t, map, nometa) assert (type (t) == "table", "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") - return merge ({}, t, map, nometa) + return merge_allfields ({}, t, map, nometa) end @@ -112,35 +100,7 @@ local clone_rename = base.deprecate (function (map, t) local function clone_select (t, keys, nometa) assert (type (t) == "table", "bad argument #1 to 'clone_select' (table expected, got " .. type (t) .. ")") - return merge_select ({}, t, keys, nometa) -end - - --- Preserve core table sort function. -local _sort = table.sort - ---- Make table.sort return its result. --- @tparam table t unsorted table --- @tparam function c comparator function --- @return `t` with keys sorted accordind to `c` -local function sort (t, c) - _sort (t, c) - return t -end - - ---- Overwrite core methods with `std` enhanced versions. --- --- Replaces core `table.sort` with `std.table` version. --- @tparam[opt=_G] table namespace where to install global functions --- @treturn table the module table -local function monkey_patch (namespace) - namespace = namespace or _G - assert (type (namespace) == "table", - "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") - - namespace.table.sort = sort - return M + return merge_namedfields ({}, t, keys, nometa) end @@ -152,23 +112,15 @@ local function empty (t) end ---- Turn a tuple into a list. --- @param ... tuple --- @return list -local function pack (...) - return {...} -end - - ---- Find the number of elements in a table. --- @tparam table t any table --- @return number of non-nil values in `t` -local function size (t) - local n = 0 - for _ in pairs (t) do - n = n + 1 +--- Invert a table. +-- @tparam table t a table with `{k=v, ...}` +-- @treturn table inverted table `{v=k, ...}` +local function invert (t) + local i = {} + for k, v in pairs (t) do + i[v] = k end - return n + return i end @@ -184,27 +136,65 @@ local function keys (t) end ---- Make the list of values of a table. --- @tparam table t any table --- @treturn table list of values -local function values (t) - local l = {} - for _, v in pairs (t) do - table.insert (l, v) - end - return l +--- Destructively merge another table's fields into another. +-- @tparam table t destination table +-- @tparam table u table with fields to merge +-- @tparam[opt={}] table map table of `{old_key=new_key, ...}` +-- @tparam boolean nometa if non-nil don't copy metatable +-- @return table `t` with fields from `u` merged in +local function merge (t, u, map, nometa) + assert (type (t) == "table", + "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") + assert (type (u) == "table", + "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") + return merge_allfields (t, u, map, nometa) end ---- Invert a table. --- @tparam table t a table with `{k=v, ...}` --- @treturn table inverted table `{v=k, ...}` -local function invert (t) - local i = {} - for k, v in pairs (t) do - i[v] = k - end - return i +--- Destructively merge another table's named fields into *table*. +-- +-- Like `merge`, but does not merge any fields by default. +-- @tparam table t destination table +-- @tparam table u table with fields to merge +-- @tparam[opt={}] table keys list of keys to copy +-- @tparam boolean nometa if non-nil don't copy metatable +-- @return copy of fields in *selection* from *t*, also sharing *t*'s +-- metatable unless *nometa* +local function merge_select (t, u, keys, nometa) + assert (type (t) == "table", + "bad argument #1 to 'merge_select' (table expected, got " .. type (t) .. ")") + assert (type (u) == "table", + "bad argument #2 to 'merge_select' (table expected, got " .. type (u) .. ")") + return merge_namedfields (t, u, keys, nometa) +end + + +--- Return given metamethod, if any, or nil. +-- @function metamethod +-- @param x object to get metamethod of +-- @param n name of metamethod to get +-- @return metamethod function or nil if no metamethod or not a +-- function +local metamethod = base.metamethod + + +--- Make a table with a default value for unset keys. +-- @param x default entry value (default: `nil`) +-- @tparam table t initial table (default: `{}`) +-- @treturn table table whose unset elements are x +local function new (x, t) + return setmetatable (t or {}, + {__index = function (t, i) + return x + end}) +end + + +--- Turn a tuple into a list. +-- @param ... tuple +-- @return list +local function pack (...) + return {...} end @@ -224,13 +214,44 @@ local function ripairs (t) end ---- Return given metamethod, if any, or nil. --- @function metamethod --- @param x object to get metamethod of --- @param n name of metamethod to get --- @return metamethod function or nil if no metamethod or not a --- function -local metamethod = base.metamethod +--- Find the number of elements in a table. +-- @tparam table t any table +-- @return number of non-nil values in `t` +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 + +--- Make table.sort return its result. +-- @tparam table t unsorted table +-- @tparam function c comparator function +-- @return `t` with keys sorted accordind to `c` +local function sort (t, c) + _sort (t, c) + return t +end + + +--- Overwrite core methods with `std` enhanced versions. +-- +-- Replaces core `table.sort` with `std.table` version. +-- @tparam[opt=_G] table namespace where to install global functions +-- @treturn table the module table +local function monkey_patch (namespace) + namespace = namespace or _G + assert (type (namespace) == "table", + "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") + + namespace.table.sort = sort + return M +end --- Turn an object into a table according to __totable metamethod. @@ -252,15 +273,15 @@ local function totable (x) end ---- Make a table with a default value for unset keys. --- @param x default entry value (default: `nil`) --- @tparam table t initial table (default: `{}`) --- @treturn table table whose unset elements are x -local function new (x, t) - return setmetatable (t or {}, - {__index = function (t, i) - return x - end}) +--- Make the list of values of a table. +-- @tparam table t any table +-- @treturn table list of values +local function values (t) + local l = {} + for _, v in pairs (t) do + table.insert (l, v) + end + return l end From dbd5a6bd94c416e894efa87ed9b73e6aa4e9b6cb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 17:10:36 +0700 Subject: [PATCH 151/703] refactor: use `t[#t + 1] = v` rather than `table.insert (t, v)`. Save a function call by factoring away calls to table.insert. * lib/std/container.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/optparse.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua: Substitute accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 4 ++-- lib/std/functional.lua | 2 +- lib/std/io.lua | 4 ++-- lib/std/list.lua | 14 +++++++------- lib/std/optparse.lua | 39 ++++++++++++++++++++------------------- lib/std/set.lua | 2 +- lib/std/strbuf.lua | 2 +- lib/std/string.lua | 8 ++++---- lib/std/table.lua | 4 ++-- lib/std/tree.lua | 2 +- 10 files changed, 41 insertions(+), 40 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 5e50535..c507d5e 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -230,10 +230,10 @@ local metatable = { for i, v in ipairs (array) do array[i] = tostring (v) end local keys, dict = {}, {} - for k in pairs (other) do table.insert (keys, k) end + for k in pairs (other) do keys[#keys + 1] = k end table.sort (keys, function (a, b) return tostring (a) < tostring (b) end) for _, k in ipairs (keys) do - table.insert (dict, tostring (k) .. "=" .. tostring (other[k])) + dict[#dict + 1] = tostring (k) .. "=" .. tostring (other[k]) end if #array > 0 then diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 0f39648..ce3e32b 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -141,7 +141,7 @@ end local function collect (i, ...) local t = {} for e in i (...) do - table.insert (t, e) + t[#t + 1] = e end return t end diff --git a/lib/std/io.lua b/lib/std/io.lua index d0a151f..4681126 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -45,7 +45,7 @@ local function readlines (h) h = input_handle (h) local l = {} for line in h:lines () do - table.insert (l, line) + l[#l + 1] = line end h:close () return l @@ -120,7 +120,7 @@ end local function process_files (f) -- N.B. "arg" below refers to the global array of command-line args if #arg == 0 then - table.insert (arg, "-") + arg[#arg + 1] = "-" end for i, v in ipairs (arg) do if v == "-" then diff --git a/lib/std/list.lua b/lib/std/list.lua index bf384d0..33caa98 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -44,7 +44,7 @@ local List -- forward declaration -- @treturn List new list containing `{l[1], ..., l[#l], x}` local function append (l, x) local r = List {unpack (l)} - table.insert (r, x) + r[#r + 1] = x return r end @@ -95,7 +95,7 @@ local function concat (l, ...) local r = List {} for e in elems ({l, ...}) do for v in elems (e) do - table.insert (r, v) + r[#r + 1] = v end end return r @@ -133,7 +133,7 @@ end local function enpair (t) local ls = List {} for i, v in pairs (t) do - table.insert (ls, List {i, v}) + ls[#ls + 1] = List {i, v} end return ls end @@ -156,7 +156,7 @@ end local function flatten (l) local r = List {} for v in base.leaves (ipairs, l) do - table.insert (r, v) + r[#r + 1] = v end return r end @@ -283,7 +283,7 @@ end local function reverse (l) local r = List {} for i = #l, 1, -1 do - table.insert (r, l[i]) + r[#r + 1] = l[i] end return r end @@ -336,7 +336,7 @@ local function shape (s, l) for j = 1, s[d] do local e e, i = fill (i, d + 1) - table.insert (r, e) + r[#r + 1] = e end return r, i end @@ -364,7 +364,7 @@ local function sub (l, from, to) to = to + len + 1 end for i = from, to do - table.insert (r, l[i]) + r[#r + 1] = l[i] end return r end diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 8ebb188..62610b0 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -127,8 +127,8 @@ local function normalise (self, arglist) -- Only split recognised long options. if self[optname] then - table.insert (normal, optname) - table.insert (normal, opt:sub (x + 1)) + normal[#normal + 1] = optname + normal[#normal + 1] = opt:sub (x + 1) else x = nil end @@ -136,7 +136,7 @@ local function normalise (self, arglist) if x == nil then -- No '=', or substring before '=' is not a known option name. - table.insert (normal, opt) + normal[#normal + 1] = opt end elseif opt:sub (1, 1) == "-" and string.len (opt) > 2 then @@ -144,7 +144,7 @@ local function normalise (self, arglist) repeat opt, rest = opt:sub (1, 2), opt:sub (3) - table.insert (split, opt) + split[#split + 1] = opt -- If there's no handler, the option was a typo, or not supposed -- to be an option at all. @@ -162,15 +162,15 @@ local function normalise (self, arglist) -- Split '-xshortargument' into '-x shortargument'. else - table.insert (split, rest) + split[#split + 1] = rest opt = nil end until opt == nil -- Append split options to normalised list - for _, v in ipairs (split) do table.insert (normal, v) end + for _, v in ipairs (split) do normal[#normal + 1] = v end else - table.insert (normal, opt) + normal[#normal + 1] = opt end end @@ -186,11 +186,12 @@ end -- @param value option argument value local function set (self, opt, value) local key = self[opt].key + local opts = self.opts[key] - if type (self.opts[key]) == "table" then - table.insert (self.opts[key], value) - elseif self.opts[key] ~= nil then - self.opts[key] = { self.opts[key], value } + if type (opts) == "table" then + opts[#opts + 1] = value + elseif opts ~= nil then + self.opts[key] = { opts, value } else self.opts[key] = value end @@ -299,7 +300,7 @@ end -- @treturn int index of next element of `arglist` to process local function finished (self, arglist, i) for opt = i + 1, #arglist do - table.insert (self.unrecognised, arglist[opt]) + self.unrecognised[#self.unrecognised + 1] = arglist[opt] end return 1 + #arglist end @@ -494,10 +495,10 @@ local function on (self, opts, handler, value) if opt:match ("^%-[^%-]+") ~= nil then -- '-xyz' => '-x -y -z' for i = 2, string.len (opt) do - table.insert (normal, "-" .. opt:sub (i, i)) + normal[#normal + 1] = "-" .. opt:sub (i, i) end else - table.insert (normal, opt) + normal[#normal + 1] = opt end end) end @@ -559,12 +560,12 @@ local function parse (self, arglist, defaults) local opt = arglist[i] if self[opt] == nil then - table.insert (self.unrecognised, opt) + self.unrecognised[#self.unrecognised + 1] = opt i = i + 1 -- Following non-'-' prefixed argument is an optarg. if i <= #arglist and arglist[i]:match "^[^%-]" then - table.insert (self.unrecognised, arglist[i]) + self.unrecognised[#self.unrecognised + 1] = arglist[i] i = i + 1 end @@ -638,7 +639,7 @@ function OptionParser (spec) -- by a '-'. local specs = {} parser.helptext:gsub ("\n %s*(%-[^\n]+)", - function (spec) table.insert (specs, spec) end) + function (spec) specs[#specs + 1] = spec end) -- Register option handlers according to the help text. for _, spec in ipairs (specs) do @@ -674,7 +675,7 @@ function OptionParser (spec) local _, c = spec:gsub ("^%-([-%w]),?%s+(.*)$", function (opt, rest) if opt == "-" then opt = "--" end - table.insert (options, opt) + options[#options + 1] = opt spec = rest end) @@ -684,7 +685,7 @@ function OptionParser (spec) -- Consume long option. spec:gsub ("^%-%-([%-%w]+),?%s+(.*)$", function (opt, rest) - table.insert (options, opt) + options[#options + 1] = opt spec = rest end) end diff --git a/lib/std/set.lua b/lib/std/set.lua index 13cb404..e7e6802 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -263,7 +263,7 @@ Set = Container { __totable = function (self) local t = {} for e in elems (self) do - table.insert (t, e) + t[#t + 1] = e end table.sort (t) return t diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 77ed1bb..114a24c 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -11,7 +11,7 @@ local Object = require "std.object" -- @tparam string s string to add -- @treturn std.strbuf modified buffer local function concat (self, s) - table.insert (self, s) + self[#self + 1] = s return self end diff --git a/lib/std/string.lua b/lib/std/string.lua index 73882ae..7855ea4 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -133,7 +133,7 @@ local function finds (s, p, init, plain) repeat from, to, r = tfind (s, p, init, plain) if from ~= nil then - table.insert (l, {from, to, capt = r}) + l[#l + 1] = {from, to, capt = r} init = to + 1 end until not from @@ -151,10 +151,10 @@ local function split (s, sep) assert (type (s) == "string", "bad argument #1 to 'split' (string expected, got " .. type (s) .. ")") local b, len, t, patt = 0, #s, {}, "(.-)" .. sep - if sep == "" then patt = "(.)"; table.insert (t, "") end + if sep == "" then patt = "(.)"; t[#t + 1] = "" end while b <= len do local e, n, m = string.find (s, patt, b + 1) - table.insert (t, m or s:sub (b + 1, len)) + t[#t + 1] = m or s:sub (b + 1, len) b = n or len + 1 end return t @@ -238,7 +238,7 @@ local function render (x, open, close, elem, pair, sep, roots) -- create a sorted list of keys local ord = {} - for k, _ in pairs (x) do table.insert (ord, k) end + for k, _ in pairs (x) do ord[#ord + 1] = k end table.sort (ord, function (a, b) return tostring (a) < tostring (b) end) -- render x elements in order diff --git a/lib/std/table.lua b/lib/std/table.lua index d3be6f2..b2ddd4b 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -130,7 +130,7 @@ end local function keys (t) local l = {} for k, _ in pairs (t) do - table.insert (l, k) + l[#l + 1] = k end return l end @@ -279,7 +279,7 @@ end local function values (t) local l = {} for _, v in pairs (t) do - table.insert (l, v) + l[#l + 1] = v end return l end diff --git a/lib/std/tree.lua b/lib/std/tree.lua index ce1358d..2776a78 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -95,7 +95,7 @@ local function _nodes (it, tr) if type (n) == "table" then coroutine.yield ("branch", p, n) for i, v in it (n) do - table.insert (p, i) + p[#p + 1] = i visit (v) table.remove (p) end From 942d3923cd419de24aeefd25c8a94550ba748329 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 17:44:36 +0700 Subject: [PATCH 152/703] refactor: remove unused `std.modules`. * lib/std/modules.lua: Remove. * local.mk (dist_luastd_DATA): Adjust. Signed-off-by: Gary V. Vaughan --- lib/std/modules.lua | 19 ------------------- local.mk | 1 - 2 files changed, 20 deletions(-) delete mode 100644 lib/std/modules.lua diff --git a/lib/std/modules.lua b/lib/std/modules.lua deleted file mode 100644 index d023b1b..0000000 --- a/lib/std/modules.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Set of imported modules. - -return { - -- true => module symbols injected into equivalent core namespace - -- with `require 'std'`: - debug = true, - debug_init = false, - functional = false, - io = true, - list = false, - math = true, - optparse = false, - package = true, - set = false, - strbuf = false, - string = true, - table = true, - tree = false, -} diff --git a/local.mk b/local.mk index 8713c99..d0f7fdc 100644 --- a/local.mk +++ b/local.mk @@ -69,7 +69,6 @@ dist_luastd_DATA = \ lib/std/io.lua \ lib/std/list.lua \ lib/std/math.lua \ - lib/std/modules.lua \ lib/std/object.lua \ lib/std/optparse.lua \ lib/std/package.lua \ From 05f10d586c5edc96bf9852c175659d5ba94682ec Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 17:45:55 +0700 Subject: [PATCH 153/703] Release version 40 * NEWS: Record release date. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 835b030..fbfcf80 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ Stdlib NEWS - User visible changes -* Noteworthy changes in release ?.? (????-??-??) [?] +* Noteworthy changes in release 40 (2014-05-01) [stable] ** New features: From 7f0fa38329967787ddfff96890829eba0e38368e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 1 May 2014 17:46:18 +0700 Subject: [PATCH 154/703] maint: post-release administrivia. * configure.ac (AC_INIT): Bump version to 41. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS | 3 +++ configure.ac | 2 +- local.mk | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.prev-version b/.prev-version index a272009..425151f 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -39 +40 diff --git a/NEWS b/NEWS index fbfcf80..f2dad91 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Stdlib NEWS - User visible changes +* Noteworthy changes in release ?.? (????-??-??) [?] + + * Noteworthy changes in release 40 (2014-05-01) [stable] ** New features: diff --git a/configure.ac b/configure.ac index e767700..41d16c3 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [40], [http://github.com/lua-stdlib/lua-stdlib/issues]) +AC_INIT([stdlib], [41], [http://github.com/lua-stdlib/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/local.mk b/local.mk index d0f7fdc..73fd872 100644 --- a/local.mk +++ b/local.mk @@ -29,7 +29,7 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" ## Bootstrap. ## ## ---------- ## -old_NEWS_hash = 606609f9586288cfe6d9df676719570a +old_NEWS_hash = b53d7090ac89fb479e5f12aa357d6881 update_copyright_env = \ UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ From c05400ac97341314e73be20d959cd9e0c11e35cf Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 19 May 2014 15:09:02 +0700 Subject: [PATCH 155/703] debug: add argcheck APIs. * specs/debug_spec.yaml (argcheck, argerror, argscheck): Specify behaviour of argument checking functions based on luaL_argerror and luaL_argcheck, for standardised argument error diagnostics. * lib/std/debug.lua: Tidy up LDocs. (argcheck, argerror, argscheck): New functions to satisfy the specifications. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 16 +++ lib/std/debug.lua | 179 ++++++++++++++++++++++++++++-- specs/debug_spec.yaml | 251 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 438 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index f2dad91..b84ce47 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,22 @@ Stdlib NEWS - User visible changes * Noteworthy changes in release ?.? (????-??-??) [?] +** New features: + + - 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 paramater + types with a single function call in the common case. + + - New `_DEBUG.argcheck` field that disables `debug.argcheck` (and + `debug.argscheck`) for production code. Similarly `_DEBUG = false` + disables those functions too. + +** Bug fixes: + + - Removed LDocs for unused `_DEBUG.std` field. + * Noteworthy changes in release 40 (2014-05-01) [stable] diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 555d09a..fffc61b 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -1,20 +1,64 @@ --[[-- - Additions to the debug module + Additions to the debug module. + + The behaviour of the functions in this module are controlled by the value + of the global `_DEBUG`. Not setting `_DEBUG` prior to requiring any of + stdlib's modules is equivalent to having `_DEBUG = true`. + + The first line of Lua code in production quality projects that use stdlib + should be either: + + _DEBUG = false + + or alternatively, if you need to be careful not to damage the global + environment: + + local init = require "std.debug_init" + init._DEBUG = false + + This mitigates almost all of the overhead of argument typechecking in + stdlib API functions. + @module std.debug ]] local init = require "std.debug_init" local io = require "std.io" local list = require "std.list" +local Object = require "std.object" local string = require "std.string" ---- To activate debugging set _DEBUG either to any true value +local prototype = Object.prototype +local typeof = type + + +--- Control std.debug function behaviour. +-- To activate debugging set _DEBUG either to any true value -- (equivalent to {level = 1}), or as documented below. -- @class table -- @name _DEBUG --- @field level debugging level +-- @field argcheck honor argcheck and argscheck calls -- @field call do call trace debugging --- @field std do standard library debugging (run examples & test code) +-- @field level debugging level + + +--- Concatenate a table of strings using ", " and " or " delimiters. +-- @tparam table alternatives a table of strings +-- @treturn string string of elements from alternatives delimited by ", " +-- and " or " +local function concat (alternatives) + local t, i = {}, 1 + while i < #alternatives do + t[i] = alternatives[i] + i = i + 1 + end + if #alternatives > 1 then + t[#t] = t[#t] .. " or " .. alternatives[#alternatives] + else + t = alternatives + end + return table.concat (t, ", ") +end --- Print a debugging message. @@ -75,17 +119,138 @@ if type (init._DEBUG) == "table" and init._DEBUG.call then debug.sethook (trace, "cr") end + +--- Raise a bad argument error. +-- Equivalent to luaL_argerror in the Lua C API. This function does not +-- return. The `level` argument behaves just like the core `error` +-- function. +-- @string name function to callout in error message +-- @int i argument number +-- @string[opt] extramsg additional text to append to message inside parentheses +-- @int[opt=1] level call stack level to blame for the error +local function argerror (name, i, extramsg, level) + level = level or 1 + local s = string.format ("bad argument #%d to '%s'", i, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + return error (s, level + 1) +end + + +--- Check the type of an argument against expected types. +-- Equivalent to luaL_argcheck in the Lua C API. +-- Argument `actual` must match one of the types from in `expected`, each +-- of which can be the name of a primitive Lua type, a stdlib object type, +-- or one of the special options below: +-- +-- #table accept any non-empty table +-- list accept a table with a non-empty array part +-- object accept any std.Object derived type +-- any accept any argument type +-- +-- Call `argerror` if there is a type mismatch. +-- +-- Normally, you should not need to use the `level` parameter, as the +-- default is to blame the caller of the function using `argcheck` in +-- error messages; which is almost certainly what you want. +-- @string name function to blame in error message +-- @int i argument number to blame in error message +-- @tparam table|string expected a list of acceptable argument types +-- @param actual argument passed +-- @int[opt=2] level call stack level to blame for the error +local function argcheck (name, i, expected, actual, level) + level = level or 2 + if prototype (expected) ~= "table" then expected = {expected} end + + -- Check actual has one of the types from expected + local ok, actualtype = false, prototype (actual) + for _, check in ipairs (expected) do + if check == "any" then + ok = true + + elseif check == "#table" then + if actualtype == "table" and next (actual) then + ok = true + end + + elseif check == "list" then + if typeof (actual) == "table" and #actual > 0 then + ok = true + end + + elseif check == "object" then + if actualtype ~= "table" and typeof (actual) == "table" then + ok = true + end + + elseif check == actualtype then + ok = true + end + + if ok then break end + end + + if not ok then + if actualtype == "nil" then + actualtype = "no value" + elseif actualtype == "table" and next (actual) == nil then + actualtype = "empty table" + elseif actualtype == "List" and #actual == 0 then + actualtype = "empty List" + end + expected = concat (expected):gsub ("#table", "non-empty table") + return argerror (name, i, expected .. " expected, got " .. actualtype, level) + end +end + + +--- Check that all arguments match specified types. +-- @string name function to blame in error message +-- @tparam table|string expected a list of lists of acceptable argument types +-- @tparam table|any actual argument value, or table of argument values +local function argscheck (name, expected, actual) + if typeof (expected) ~= "table" then expected = {expected} end + if typeof (actual) ~= "table" then actual = {actual} end + + for i, v in ipairs (expected) do + if v ~= "any" then + argcheck (name, i, expected[i], actual[i], 3) + end + end +end + + --- @export local M = { - say = say, - trace = trace, + argcheck = argcheck, + argerror = argerror, + argscheck = argscheck, + say = say, + trace = trace, } + +-- Turn off argument checking if _DEBUG is false, or a table containing +-- a false valued `argcheck` field. + +local _ARGCHECK = init._DEBUG +if type (init._DEBUG) == "table" then + _ARGCHECK = init._DEBUG.argcheck + if _ARGCHECK == nil then _ARGCHECK= true end +end + +if not _ARGCHECK then + M.argcheck = function () end + M.argscheck = function () end +end + + for k, v in pairs (debug) do M[k] = M[k] or v end ---- The global function `debug` is an abbreviation for `debug.say (1, ...)` +--- Equivalent to calling `debug.say (1, ...)` -- @function debug -- @see say local metatable = { diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 2520d5c..330f652 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -3,7 +3,7 @@ before: | this_module = "std.debug" global_table = "_G" - extend_base = { "say", "trace" } + extend_base = { "argcheck", "argerror", "argscheck", "say", "trace" } M = require "std.debug" @@ -32,6 +32,255 @@ specify std.debug: - describe _DEBUG: +- describe argerror: + - before: | + fn = M.argerror + function mkstack (level) + return string.format ([[ + _DEBUG = true -- line 1 + local debug = require "std.debug" -- line 2 + function ohnoes () -- line 3 + debug.argerror ("ohnoes", 1, nil, %s) -- line 4 + end -- line 5 + function caller () -- line 6 + local r = ohnoes () -- line 7 + return "not a tail call" -- line 8 + end -- line 9 + caller () -- line 10 + ]], tostring (level)) + end + - it raises a bad argument error: | + expect (fn ('expect', 1)). + to_error "bad argument #1 to 'expect'" + - it blames the call site by default: | + expect (luaproc (mkstack ())).to_contain_error ":4: bad argument" + - it honors optional call stack level reporting: | + expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" + expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" + - it reports the calling function name: + expect (fn ('expect', 1)).to_error "'expect'" + - it reports the argument number: | + expect (fn ('expect', 12345)).to_error "#12345" + - it reports extra message in parentheses: + expect (fn ('expect', 1, "extramsg")).to_error " (extramsg)" + + +- describe argcheck: + - before: | + Object = require 'std.object' + List = require "std.list" + Foo = Object { _type = "Foo" } + + function fn (...) return M.argcheck ('expect', 1, ...) end + + function mkstack (level, debugp) + return string.format ([[ + _DEBUG = %s -- line 1 + local debug = require "std.debug" -- line 2 + function ohnoes (t) -- line 3 + debug.argcheck ("ohnoes", 1, "table", t, %s) -- line 4 + end -- line 5 + function caller () -- line 6 + local r = ohnoes "not a table" -- line 7 + return "not a tail call" -- line 8 + end -- line 9 + caller () -- line 10 + ]], tostring (debugp), tostring (level)) + end + - it blames the calling function by default: | + expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" + - it honors optional call stack level reporting: | + expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" + expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" + expect (luaproc (mkstack (3))).to_contain_error ":10: bad argument" + - it can be disabled by setting _DEBUG to false: + expect (luaproc (mkstack (nil, false))). + not_to_contain_error "bad argument" + - it can be disabled by setting _DEBUG.argcheck to false: + expect (luaproc (mkstack (nil, "{ argcheck = false }"))). + not_to_contain_error "bad argument" + - it is not disabled by setting _DEBUG.argcheck to true: + expect (luaproc (mkstack (nil, "{ argcheck = true }"))). + to_contain_error "bad argument" + - it is not disabled by leaving _DEBUG.argcheck unset: + expect (luaproc (mkstack (nil, "{}"))). + to_contain_error "bad argument" + - it diagnoses missing primitive types: + expect (fn ("boolean", nil)).to_error "boolean expected, got no value" + expect (fn ("number", nil)).to_error "number expected, got no value" + expect (fn ("string", nil)).to_error "string expected, got no value" + expect (fn ("function", nil)).to_error "function expected, got no value" + expect (fn ("table", nil)).to_error "table expected, got no value" + - it diagnoses mismatched primitive types: + expect (fn ("boolean", {0})).to_error "boolean expected, got table" + expect (fn ("number", {0})).to_error "number expected, got table" + expect (fn ("string", {0})).to_error "string expected, got table" + expect (fn ("function", {0})).to_error "function expected, got table" + expect (fn ("table", false)).to_error "table expected, got boolean" + expect (fn ("table", require "std.object")). + to_error "table expected, got Object" + - it matches primitive Lua types: + expect (fn ("boolean", true)).not_to_error () + expect (fn ("number", 1)).not_to_error () + expect (fn ("string", "s")).not_to_error () + expect (fn ("function", function () end)).not_to_error () + expect (fn ("table", {})).not_to_error () + - it diagnoses missing non-empty table types: + expect (fn ("#table", nil)). + to_error "non-empty table expected, got no value" + - it diagnoses mismatched non-empty table types: + expect (fn ("#table", false)). + to_error "non-empty table expected, got boolean" + expect (fn ("#table", {})). + to_error "non-empty table expected, got empty table" + - it matches non-empty table types: + expect (fn ("#table", {0})).not_to_error () + - it diagnonses missing list types: + expect (fn ("list", nil)). + to_error "list expected, got no value" + - it diagnoses mismatched list types: + expect (fn ("list", false)). + to_error "list expected, got boolean" + expect (fn ("list", {})). + to_error "list expected, got empty table" + expect (fn ("list", {foo=1})). + to_error "list expected, got table" + expect (fn ("list", Object)). + to_error "list expected, got Object" + expect (fn ("list", List {})). + to_error "list expected, got empty List" + - it matches list types: + expect (fn ("list", {1})).not_to_error () + expect (fn ("list", List {1})).not_to_error () + - it diagnoses missing object types: + expect (fn ("object", nil)).to_error "object expected, got no value" + expect (fn ("Object", nil)).to_error "Object expected, got no value" + expect (fn ("Foo", nil)).to_error "Foo expected, got no value" + - it diagnoses mismatched object types: + expect (fn ("object", {0})).to_error "object expected, got table" + expect (fn ("Object", {0})).to_error "Object expected, got table" + expect (fn ("object", {_type="Object"})).to_error "object expected, got table" + expect (fn ("Object", {_type="Object"})).to_error "Object expected, got table" + expect (fn ("Object", Foo)).to_error "Object expected, got Foo" + expect (fn ("Foo", {0})).to_error "Foo expected, got table" + expect (fn ("Foo", Object)).to_error "Foo expected, got Object" + - it matches object types: + expect (fn ("object", Object)).not_to_error () + expect (fn ("object", Object {})).not_to_error () + expect (fn ("object", Foo)).not_to_error () + expect (fn ("object", Foo {})).not_to_error () + - it matches anything: + expect (fn ("any", nil)).not_to_error () + expect (fn ("any", true)).not_to_error () + expect (fn ("any", {})).not_to_error () + expect (fn ("any", Object)).not_to_error () + expect (fn ("any", Foo {})).not_to_error () + - context with a list of valid types: + - it diagnoses missing elements: + expect (fn ({"string", "table"}, nil)). + to_error "string or table expected, got no value" + expect (fn ({"string", "list", "#table"}, nil)). + to_error "string, list or non-empty table expected, got no value" + expect (fn ({"string", "number", "list", "object"}, nil)). + to_error "string, number, list or object expected, got no value" + - it diagnoses mismatched elements: + expect (fn ({"string", "table"}, false)). + to_error "string or table expected, got boolean" + expect (fn ({"string", "list", "#table"}, {})). + to_error "string, list or non-empty table expected, got empty table" + expect (fn ({"string", "number", "list", "object"}, {})). + to_error "string, number, list or object expected, got empty table" + - it matches any type from a list: + expect (fn ({"string", "table"}, "foo")).not_to_error () + expect (fn ({"string", "table"}, {})).not_to_error () + expect (fn ({"string", "table"}, {0})).not_to_error () + expect (fn ({"table", "#table"}, {})).not_to_error () + expect (fn ({"#table", "table"}, {})).not_to_error () + + +- describe argscheck: + - before: | + function fn (...) return M.argscheck ('expect', ...) end + + function mkstack (debugp) + return string.format ([[ + _DEBUG = %s -- line 1 + local debug = require "std.debug" -- line 2 + function ohnoes (t, n) -- line 3 + debug.argscheck ("ohnoes", {"table", "number"}, {t, n}) -- line 4 + end -- line 5 + function caller () -- line 6 + local r = ohnoes ({}, "not a number") -- line 7 + return "not a tail call" -- line 8 + end -- line 9 + caller () -- line 10 + ]], tostring (debugp)) + end + - it blames the calling function: | + expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" + - it can be disabled by setting _DEBUG to false: + expect (luaproc (mkstack (false))). + not_to_contain_error "bad argument" + - it can be disabled by setting _DEBUG.argcheck to false: + expect (luaproc (mkstack ("{ argcheck = false }"))). + not_to_contain_error "bad argument" + - it is not disabled by setting _DEBUG.argcheck to true: + expect (luaproc (mkstack ("{ argcheck = true }"))). + to_contain_error "bad argument" + - it is not disabled by leaving _DEBUG.argcheck unset: + expect (luaproc (mkstack ("{}"))). + to_contain_error "bad argument" + - context with single non-table specs: + - it diagnoses missing argument: + expect (fn ("boolean", nil)). + to_error "boolean expected, got no value" + - it reports the correct missing argument number: + expect (fn ("boolean", nil)).to_error "#1 " + - it diagnoses mismatched argument: + expect (fn ("boolean", "false")). + to_error "boolean expected, got string" + - it reports the correct mismatched argument number: + expect (fn ("boolean", "false")).to_error "#1 " + - it matches argument type: + expect (fn ("boolean", false)).not_to_error () + - context with single argument table: + - it diagnoses missing argument: + expect (fn ({"boolean"}, {nil})). + to_error "boolean expected, got no value" + - it reports the correct missing argument number: + expect (fn ({"boolean"}, {nil})).to_error "#1 " + - it diagnoses mismatched argument: + expect (fn ({"boolean"}, {"false"})). + to_error "boolean expected, got string" + - it reports the correct mismatched argument number: + expect (fn ({"boolean"}, {"false"})).to_error "#1 " + - it matches argument type: + expect (fn ({"boolean"}, {false})).not_to_error () + - context with multi-argument table: + - it diagnoses missing argument: + expect (fn ({"boolean", "table"}, {false, nil})). + to_error "table expected, got no value" + expect (fn ({"boolean", "table", "string"}, {false, nil, "nil"})). + to_error "table expected, got no value" + - it reports the correct missing argument number: + expect (fn ({"boolean", "table"}, {false, nil})).to_error "#2 " + expect (fn ({"boolean", "table", "string"}, {false, nil, "nil"})). + to_error "#2 " + - it diagnoses mismatched argument: + expect (fn ({"boolean", "table"}, {false, "false"})). + to_error "table expected, got string" + expect (fn ({"boolean", "table", "string"}, {false, "nil", "nil"})). + to_error "table expected, got string" + - it reports the correct mismatched argument number: + expect (fn ({"boolean", "table"}, {false, "false"})).to_error "#2 " + expect (fn ({"boolean", "table", "string"}, {false, "nil", "nil"})). + to_error "#2 " + - it matches argument type: + expect (fn ({"boolean", "table"}, {false, {}})).not_to_error () + expect (fn ({"boolean", "table", "string"}, {false, {}, "{}"})). + not_to_error () + + - describe debug: From aab7d093fba552247f1dba3ddcc0f824545393ce Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 19 May 2014 20:11:25 +0700 Subject: [PATCH 156/703] debug: make sure to use debug.getinfo (). * lib/std/debug.lua (trace): Use debug.getinfo instead of bare getinfo. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 2 ++ lib/std/debug.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index b84ce47..c113591 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,8 @@ Stdlib NEWS - User visible changes - Removed LDocs for unused `_DEBUG.std` field. + - `debug.trace` works with Lua 5.2.x again. + * Noteworthy changes in release 40 (2014-05-01) [stable] diff --git a/lib/std/debug.lua b/lib/std/debug.lua index fffc61b..bfdac11 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -93,7 +93,7 @@ local function trace (event) if t ~= nil and t.currentline >= 0 then s = s .. t.short_src .. ":" .. t.currentline .. " " end - t = getinfo (2) + t = debug.getinfo (2) if event == "call" then level = level + 1 else From 9ccae40206251cb24c4ed334ab9b1c6002a2a658 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 19 May 2014 20:54:01 +0700 Subject: [PATCH 157/703] alien: implement a subset of alien to simplify wrappers. * specs/alien_spec.yaml: Specify behaviour for a useful subset of the alien APIs. * specs/specs.mk (specl_SPECS): Add specs/alien_spec.yaml. * local.mk (dist_modules_DATA): Add doc/modules/std.alien.html. * lib/std/alien.lua: New file, provide pure Lua implementation of alien.array, alien.memmove and alien.memset. * build-aux/config.ld.in (files): Add lib/std/alien.lua. * local.mk (dist_luastd_DATA): Add lib/std/alien.lua. Signed-off-by: Gary V. Vaughan --- build-aux/config.ld.in | 1 + lib/std/alien.lua | 217 ++++++++++++++++++++++++++++++++++++++ local.mk | 2 + specs/alien_spec.yaml | 229 +++++++++++++++++++++++++++++++++++++++++ specs/specs.mk | 1 + 5 files changed, 450 insertions(+) create mode 100644 lib/std/alien.lua create mode 100644 specs/alien_spec.yaml diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index fb4576f..8e0e5e5 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -7,6 +7,7 @@ dir = "." file = { -- Modules "../lib/std.lua", + "../lib/std/alien.lua", "../lib/std/debug.lua", "../lib/std/functional.lua", "../lib/std/io.lua", diff --git a/lib/std/alien.lua b/lib/std/alien.lua new file mode 100644 index 0000000..7fd62c4 --- /dev/null +++ b/lib/std/alien.lua @@ -0,0 +1,217 @@ +--[[-- + A pure Lua implementation of bits of the alien API stdlib relies on. + +# Internal API + + This is very far from a full implementation, and serves barely sufficient + functionality to enable use of `std.array` objects that won't work with + the real alien module - either because it is not available, or because the + contiguous memory model of alien arrays won't work for complex types that + are supported by std.array. + + The documentation is here for completeness and to aid understanding, but + you almost certainly won't find a good use for this module. + + @module std.alien +]] + +local debug = require "std.debug" +local argscheck = debug.argscheck + +local typeof = type + + +------ +-- A pointer into a std.alien.buffer object. +-- This helps std.object.prototype recognise the fake pointers generated +-- by std.alien.array.buffer:topointer (). +-- @table std.alien.pointer +-- @tfield std.alien.buffer buffer std.alien.array objects contain one +-- @int index index into `buffer` +-- @see std.alien.buffer:topointer + +local pointer_mt = { + _type = "std.alien.pointer", +} + + +local buffer_methods = { + --- Return a table containing the index and a buffer reference. + -- @function std.alien.buffer:topointer + -- @int index reference to an element from buffer + -- @see std.alien.pointer + topointer = function (self, index) + return setmetatable ({ buffer = self, index = index }, pointer_mt) + end, +} + + +local buffer_mt = { + _type = "std.alien.array.buffer", + __index = buffer_methods, +} + + +local array_methods = { + --- Change the number of elements available in an array. + -- @function std.alien.array:realloc + -- @int count the minimum number of elements to make available + -- @see std.alien.array + realloc = function (self, count) + self.length = count + end, +} + +local array_mt = { + _type = "std.alien.array", + + --- Fetch the `index`th element, or fallback to a method name. + -- If `index` is a number, and it is not between 1 and the length + -- of this array, throw an "array access out of bounds" error. + -- @function array.__index + -- @tparam std.alien.array self an object from std.alien.array () + -- @int index element index + -- @return the `index`th element of `self` + -- @local + __index = function (self, index) + if typeof (index) == "number" then + if index < 1 or index > self.length then + error "array access out of bounds" + end + return rawget (self.buffer, index) + end + return array_methods[index] + end, + + --- Fetch the number of elements available in this array. + -- Note that this metamethod is ignored in Lua<5.2. For compatibility + -- with earlier releases, use array.length instead of #array. + -- @function array.__len + -- @tparam std.alien.array self an object fro std.alien.array () + -- @treturn int the number of avaliable element slots + -- @local + __len = function (self) + return self.length + end, + + --- Set the `index`th element to `value`. + -- If `index` is a number, and it is not between 1 and the length + -- of this array, throw an "array access out of bounds" error. + -- @tparam std.alien.array self an object from std.alien.array () + -- @int index element index + -- @int value set the `index`th element to this + -- @return the `index`th element of `self` + -- @local + __newindex = function (self, index, value) + if typeof (index) == "number" then + if index < 1 or index > self.length then + error "array access out of bounds" + end + rawset (self.buffer, index, value) + else + rawset (self, index, value) + end + return self + end, +} + + +--- Return a new std.alien.array object. +-- @string type for API compatibility with alien proper +-- @tparam int|table init number of elements to allocate, or a table of values +-- @treturn std.alien.array a new std.alien.array object +-- @see std.alien.array +local function array (type, init) + argscheck ('array', {"string", {"number", "table"}}, {type, init}) + + local array = { + type = type, + buffer = {}, + size = 1, + length = init, + } + + if typeof (init) == "table" then + array.buffer = init + array.length = #init + end + array.buffer = setmetatable (array.buffer, buffer_mt) + + return setmetatable (array, array_mt) +end + + +--- Move a block of contiguous elements to a new position. +-- Works with overlapping blocks, or between entirely different arrays. +-- @tparam std.alien.pointer to destination for elements to copy +-- @tparam std.alien.pointer from source of elements to be copied +-- @int bytes number of elements to copy (this only works because we ensure +-- std.alien.array.size is always 1) +-- @see std.alien.buffer:topointer +local function memmove (to, from, bytes) + argscheck ("memmove", {"std.alien.pointer", "std.alien.pointer", "number"}, + {to, from, bytes}) + + local tobuf, frombuf = to.buffer, from.buffer + local to, from = to.index, from.index + if tobuf == frombuf and to > from then + for i = bytes - 1, 0, -1 do + tobuf[to + i] = frombuf[from + i] + end + else + for i = 0, bytes - 1 do + tobuf[to + i] = frombuf[from + i] + end + end +end + + +--- Set a new value for a contiguos block of elements. +-- There's no concept of bytes or object size in `std.alien.array`s, so +-- in practice, the use of this function is limited to zeroing out new +-- elements. +-- @tparam std.alien.pointer pointer an object returned by `topointer` +-- @param value the new value to set elements to +-- @int bytes numebr of elements to set (this only works because we ensure +-- std.alien.array.size is always 1) +-- @see std.alien.buffer:topointer +local function memset (pointer, value, bytes) + argscheck ("memset", {"std.alien.pointer", "any", "number"}, + {pointer, value, bytes}) + + local buffer, from, to = pointer.buffer, pointer.index, pointer.index + bytes -1 + for i = from, to do + buffer[i] = value + end +end + + +--- @export +return { + array = array, + memmove = memmove, + memset = memset, +} + +-- If we put these near the code they document, LDoc crashes! :-/ + +------ +-- An array of homogenous elements. +-- Instantiate one of these by calling `array`. +-- @table std.alien.array +-- @string type nominal name of types of elements stored in this array +-- @tparam std.alien.buffer buffer the elements of this array +-- @int size the size of a single element, always 1 in this implementation +-- @int length the number of elements in this array +-- @see std.alien.buffer +-- @see array +-- @see std.alien.array:realloc + + +------ +-- The element buffer of a `std.alien.array`. +-- In this implementation, a buffer is just the array part of a Lua table. +-- @table std.alien.buffer +-- @see std.alien.array +-- @see memmove +-- @see memset diff --git a/local.mk b/local.mk index 73fd872..69dcf6c 100644 --- a/local.mk +++ b/local.mk @@ -62,6 +62,7 @@ dist_lua_DATA += \ luastddir = $(luadir)/std dist_luastd_DATA = \ + lib/std/alien.lua \ lib/std/base.lua \ lib/std/container.lua \ lib/std/debug.lua \ @@ -137,6 +138,7 @@ dist_classes_DATA += \ dist_modules_DATA += \ $(srcdir)/doc/modules/std.html \ + $(srcdir)/doc/modules/std.alien.html \ $(srcdir)/doc/modules/std.debug.html \ $(srcdir)/doc/modules/std.functional.html \ $(srcdir)/doc/modules/std.io.html \ diff --git a/specs/alien_spec.yaml b/specs/alien_spec.yaml new file mode 100644 index 0000000..95edaf2 --- /dev/null +++ b/specs/alien_spec.yaml @@ -0,0 +1,229 @@ +before: | + Object = require "std.object" + prototype = Object.prototype + +## ================================================================== ## +## FIXME: When anchors and aliases work, the alien and std.alien ## +## specifications should use an alias to prevent repetition. ## +## In the mean time, please be careful to make changes twice ## +## (once in each section) to ensure the std.alien API behaves ## +## identically to the real alien module! ## +## ================================================================== ## + +specify alien: +- before: | + -- Don't crash for a lack of an installed alien module + have_alien, alien = pcall (require, "alien") + if not have_alien then + alien = require "std.alien" + end +- describe array: + - before: + fn = alien.array + - it diagnoses missing arguments: + expect (fn ()).to_error () + expect (fn "int").to_error () + - when initialised with a length: + - before: | + a = fn ("char", 4) + for i = 1, a.length do + a[i] = i * i + end + function set (i, v) a[i] = v end + - it instantiates a new array object: + expect (prototype (a)). + to_be.any_of {"table", "std.alien.array"} + - it knows its own length: + expect (a.length).to_be (4) + - it diagnoses out of bounds access: + expect (a[-1]).to_error "array access out of bounds" + expect (a[0]).to_error "array access out of bounds" + expect (a[a.length + 1]).to_error "array access out of bounds" + expect (a[a.length * 2]).to_error "array access out of bounds" + - it reports value of elements by index: + expect (a[1]).to_be (1) + expect (a[2]).to_be (4) + expect (a[3]).to_be (9) + expect (a[4]).to_be (16) + - it diagnoses out of bounds assignment: + expect (set (-1, 42)).to_error "array access out of bounds" + expect (set (0, 42)).to_error "array access out of bounds" + expect (set (a.length + 1, 42)).to_error "array access out of bounds" + expect (set (a.length * 2, 42)).to_error "array access out of bounds" + - it allows setting of element values by index: + expect (set (1, 42)).not_to_error () + expect (a[1]).to_be (42) + - it knows the size of its elements: + expect (a.size).to_be (1) + - it knows the type of its elements: + expect (a.type).to_be "char" + - when initialised with elements: + - before: | + a = fn ("char", {1, 4, 9, 16}) + function set (i, v) a[i] = v end + - it instatiates a new array object: + expect (prototype (a)). + to_be.any_of {"table", "std.alien.array"} + - it knows its own length: + expect (a.length).to_be (4) + - it diagnoses out of bounds access: + expect (a[-1]).to_error "array access out of bounds" + expect (a[0]).to_error "array access out of bounds" + expect (a[a.length + 1]).to_error "array access out of bounds" + expect (a[a.length * 2]).to_error "array access out of bounds" + - it reports value of elements by index: + expect (a[1]).to_be (1) + expect (a[2]).to_be (4) + expect (a[3]).to_be (9) + expect (a[4]).to_be (16) + - it diagnoses out of bounds assignment: + expect (set (-1, 42)).to_error "array access out of bounds" + expect (set (0, 42)).to_error "array access out of bounds" + expect (set (a.length + 1, 42)).to_error "array access out of bounds" + expect (set (a.length * 2, 42)).to_error "array access out of bounds" + - it allows setting of element values by index: + expect (set (1, 42)).not_to_error () + expect (a[1]).to_be (42) + - it knows the size of its elements: + expect (a.size).to_be (1) + - it knows the type of its elements: + expect (a.type).to_be "char" + +- describe memmove: + - before: + fn = alien.memmove + a = alien.array ("int", 8) + b = alien.array ("int", {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}) + - it copies elements from one array to another: + fn (a.buffer:topointer (1), b.buffer:topointer (2 * b.size + 1), 8 * a.size) + for i = 1, a.length do + expect (a[i]).to_be (b[i + 2]) + end + - it shifts elements of overlapping blocks to the left: + fn (b.buffer:topointer (1), b.buffer:topointer (2 * b.size + 1), 8 * a.size) + for i, v in ipairs {2, 3, 5, 8, 13, 21, 34, 55, 34, 55} do + expect (b[i]).to_be (v) + end + - it shifts elements of overlapping blocks to the right: + b[1] = 0 + fn (b.buffer:topointer (2 * b.size + 1), b.buffer:topointer (1), 8 * b.size) + for i, v in ipairs {0, 1, 0, 1, 2, 3, 5, 8, 13, 21 } do + expect (b[i]).to_be (v) + end + +- describe memset: + - before: + fn = alien.memset + - it sets a block of elements to zero: + a = alien.array ("int", {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}) + fn (a.buffer:topointer (2 * a.size + 1), 0, 6 * a.size) + for i, v in ipairs {1, 1, 0, 0, 0, 0, 0, 0, 34, 55} do + expect (a[i]).to_be (v) + end + +specify std.alien: +- before: + alien = require "std.alien" +- describe array: + - before: + fn = alien.array + - it diagnoses missing arguments: + expect (fn ()).to_error () + expect (fn "int").to_error () + - when initialised with a length: + - before: | + a = fn ("char", 4) + for i = 1, a.length do + a[i] = i * i + end + function set (i, v) a[i] = v end + - it instantiates a new array object: + expect (prototype (a)). + to_be.any_of {"table", "std.alien.array"} + - it knows its own length: + expect (a.length).to_be (4) + - it diagnoses out of bounds access: + expect (a[-1]).to_error "array access out of bounds" + expect (a[0]).to_error "array access out of bounds" + expect (a[a.length + 1]).to_error "array access out of bounds" + expect (a[a.length * 2]).to_error "array access out of bounds" + - it reports value of elements by index: + expect (a[1]).to_be (1) + expect (a[2]).to_be (4) + expect (a[3]).to_be (9) + expect (a[4]).to_be (16) + - it diagnoses out of bounds assignment: + expect (set (-1, 42)).to_error "array access out of bounds" + expect (set (0, 42)).to_error "array access out of bounds" + expect (set (a.length + 1, 42)).to_error "array access out of bounds" + expect (set (a.length * 2, 42)).to_error "array access out of bounds" + - it allows setting of element values by index: + expect (set (1, 42)).not_to_error () + expect (a[1]).to_be (42) + - it knows the size of its elements: + expect (a.size).to_be (1) + - it knows the type of its elements: + expect (a.type).to_be "char" + - when initialised with elements: + - before: | + a = fn ("char", {1, 4, 9, 16}) + function set (i, v) a[i] = v end + - it instatiates a new array object: + expect (prototype (a)). + to_be.any_of {"table", "std.alien.array"} + - it knows its own length: + expect (a.length).to_be (4) + - it diagnoses out of bounds access: + expect (a[-1]).to_error "array access out of bounds" + expect (a[0]).to_error "array access out of bounds" + expect (a[a.length + 1]).to_error "array access out of bounds" + expect (a[a.length * 2]).to_error "array access out of bounds" + - it reports value of elements by index: + expect (a[1]).to_be (1) + expect (a[2]).to_be (4) + expect (a[3]).to_be (9) + expect (a[4]).to_be (16) + - it diagnoses out of bounds assignment: + expect (set (-1, 42)).to_error "array access out of bounds" + expect (set (0, 42)).to_error "array access out of bounds" + expect (set (a.length + 1, 42)).to_error "array access out of bounds" + expect (set (a.length * 2, 42)).to_error "array access out of bounds" + - it allows setting of element values by index: + expect (set (1, 42)).not_to_error () + expect (a[1]).to_be (42) + - it knows the size of its elements: + expect (a.size).to_be (1) + - it knows the type of its elements: + expect (a.type).to_be "char" + +- describe memmove: + - before: + fn = alien.memmove + a = alien.array ("int", 8) + b = alien.array ("int", {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}) + - it copies elements from one array to another: + fn (a.buffer:topointer (1), b.buffer:topointer (2 * b.size + 1), 8 * a.size) + for i = 1, a.length do + expect (a[i]).to_be (b[i + 2]) + end + - it shifts elements of overlapping blocks to the left: + fn (b.buffer:topointer (1), b.buffer:topointer (2 * b.size + 1), 8 * a.size) + for i, v in ipairs {2, 3, 5, 8, 13, 21, 34, 55, 34, 55} do + expect (b[i]).to_be (v) + end + - it shifts elements of overlapping blocks to the right: + b[1] = 0 + fn (b.buffer:topointer (2 * b.size + 1), b.buffer:topointer (1), 8 * b.size) + for i, v in ipairs {0, 1, 0, 1, 2, 3, 5, 8, 13, 21 } do + expect (b[i]).to_be (v) + end + +- describe memset: + - before: + fn = alien.memset + - it sets a block of elements to zero: + a = alien.array ("int", {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}) + fn (a.buffer:topointer (2 * a.size + 1), 0, 6 * a.size) + for i, v in ipairs {1, 1, 0, 0, 0, 0, 0, 0, 34, 55} do + expect (a[i]).to_be (v) + end diff --git a/specs/specs.mk b/specs/specs.mk index 0f5aa9b..0d46fef 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -24,6 +24,7 @@ SPECL_OPTS = --unicode ## affected. specl_SPECS = \ + $(srcdir)/specs/alien_spec.yaml \ $(srcdir)/specs/base_spec.yaml \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ From e1a62b0f4e4e7a02bf26f9033cf6dd10130f5c90 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 20 May 2014 11:32:10 +0700 Subject: [PATCH 158/703] debug: fix argcheck diagnostics on Lua 5.1. Lua 5.2 seems to treat `return argerror` (where argerror never returns anyway) as subject to some kind of optimisation that loses a call stack level by the time control flow reaches the `error` call inside. Removing the `return` results in the same call stack frame reference in 5.2 and 5.1, but requires incrementing the level for the correct result. * lib/std/debug.lua (argcheck): Drop the extraneous `return` statement, and increment `level` before calling `argerror`. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index bfdac11..e6adefc 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -200,7 +200,7 @@ local function argcheck (name, i, expected, actual, level) actualtype = "empty List" end expected = concat (expected):gsub ("#table", "non-empty table") - return argerror (name, i, expected .. " expected, got " .. actualtype, level) + argerror (name, i, expected .. " expected, got " .. actualtype, level + 1) end end From 80a8c502bb649a4b11bdc6cfbed5fc8adce80a84 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 20 May 2014 11:32:10 +0700 Subject: [PATCH 159/703] debug: fix argerror diagnostics on luajit. Luajit seems to treat `return error` (where error never returns anyway) as subject to some kind of optimisation that loses a call stack level by the time `error` looks up the line number for the targetted level. Removing the `return` results in the same call stack frame reference in luajit, 5.2 and 5.1. * lib/std/debug.lua (argerror): Drop the extraneous `return` statement. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index e6adefc..5deec6a 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -134,7 +134,7 @@ local function argerror (name, i, extramsg, level) if extramsg ~= nil then s = s .. " (" .. extramsg .. ")" end - return error (s, level + 1) + error (s, level + 1) end From 585a89231733602de8905f6c816e5197287f0c6e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 20 May 2014 10:53:08 +0700 Subject: [PATCH 160/703] array: a new Object based array for queue and stack-like ops. * specs/array_spec.yaml: New file. Specify base behaviours for a new array object. * specs/specs.mk (specl_SPECS): Add specs/array_spec.yaml. * lib/std/array.lua: New file. Implement specified behaviours. * build-aux/config.ld.in (files): Add lib/std/array.lua. * local.mk (dist_classes_DATA): Add doc/std.array.html. (dist_luastd_DATA): Add lib/std/array.lua. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 + build-aux/config.ld.in | 1 + lib/std/array.lua | 355 ++++++++++++++++++++++++ local.mk | 2 + specs/array_spec.yaml | 592 +++++++++++++++++++++++++++++++++++++++++ specs/specs.mk | 1 + 6 files changed, 954 insertions(+) create mode 100644 lib/std/array.lua create mode 100644 specs/array_spec.yaml diff --git a/NEWS b/NEWS index c113591..2df7a15 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,9 @@ Stdlib NEWS - User visible changes `debug.argscheck`) for production code. Similarly `_DEBUG = false` disables those functions too. + - New `std.array` object, for clean and fast queue-like or stack-like + container management. + ** Bug fixes: - Removed LDocs for unused `_DEBUG.std` field. diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 8e0e5e5..f0a9dc4 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -8,6 +8,7 @@ file = { -- Modules "../lib/std.lua", "../lib/std/alien.lua", + "../lib/std/array.lua", "../lib/std/debug.lua", "../lib/std/functional.lua", "../lib/std/io.lua", diff --git a/lib/std/array.lua b/lib/std/array.lua new file mode 100644 index 0000000..34f3d49 --- /dev/null +++ b/lib/std/array.lua @@ -0,0 +1,355 @@ +--[[-- + Efficient array of homogenous objects. + + An Array is a block of contiguous memory, divided into equal sized + elements that can be indexed quickly. + + Create a new Array with: + + > Array = require "std.array" + > array = Array ("int", 0xdead, 0xbeef, 0xfeed) + > =array[1], array[2], array[3], array[-1] + 57005 48879 65261 65261 + + All the indices passed to methods use 1-based counting. + + If the Lua alien module is installed, and the `type` argument passed + when cloning an Array object is suitable (i.e. the name of a numeric + C type that alien.array understands), then the array contents are + managed in an alien.array. + + If alien is not installed, or does not understand the `type` argument + it is given, then a much slower (but API compatible) Lua table is used + to manage elements. + + In either case, std.array provides a means for managing collections + of homogenous Lua objects with a vector-like, stack-like or queue-like + API. + + @classmod std.array +]] + + +local debug = require "std.debug" +local argcheck, argscheck = debug.argcheck, debug.argscheck + +local Object = require "std.object" +local prototype = Object.prototype + +local have_alien, alien = pcall (require, "alien") +if not have_alien then + alien = require "std.alien" +end +local calloc, memmove, memset = alien.array, alien.memmove, alien.memset + +local typeof = type + + +local element_chunk_size = 16 + + +--- Convert an array element index into a pointer. +-- @tparam alien.array array an array +-- @int i an index into array +-- @treturn alien.buffer.pointer suitable for memmove or memset +local function topointer (array, i) + return array.buffer:topointer ((i - 1) * array.size + 1) +end + + +--- Fast zeroing of a contiguous block of array elements. +-- @tparam alien.array array an array +-- @int from index of first element to zero out +-- @int n number of elements to zero out +-- @treturn alien.array array +local function setzero (array, from, n) + if n > 0 then + memset (topointer (array, from), 0, n * array.size) + end + return array +end + + +--- Fast move of a block of elements within an array. +-- @tparam alien.array array an array +-- @int to index of first destination element +-- @int from index of first source element +-- @int n number of elements to move +local function move (array, to, from, n) + memmove (topointer (array, to), topointer (array, from), n * array.size) +end + + +local _functions = { + --- Remove the right-most element. + -- @function pop + -- @return the right-most element + pop = function (self) + argscheck ("pop", {"Array"}, {self}) + + local used = self.length + if used > 0 then + local a = self.array + local elem = a[used] + self:realloc (used - 1) + return elem + end + return nil + end, + + + --- Add elem as the new right-most element. + -- @function push + -- @param elem new element to be pushed + -- @return elem + push = function (self, elem) + argscheck ("push", {"Array", "number"}, {self, elem}) + + local a, used = self.array, self.length + 1 + self:realloc (used) + a[used] = elem + return elem + end, + + + --- Change the number of elements allocated to be at least `n`. + -- @function realloc + -- @int n the number of elements required + -- @treturn Array the array + realloc = function (self, n) + argscheck ("realloc", {"Array", "number"}, {self, n}) + + local a, used = self.array, self.length + if n > a.length or n < a.length / 2 then + a:realloc (n + element_chunk_size) + end + + -- Zero padding for uninitialised elements. + setzero (a, used + 1, n - used) + + self.length = n + return self + end, + + + --- Set `n` elements starting at `from` to `v`. + -- @function set + -- @int from index of first element to set + -- @int v value to store + -- @int n number of elements to set + -- @treturn Array the array + set = function (self, from, v, n) + argscheck ("set", {"Array", "number", "number", "number"}, + {self, from, v, n}) + + local used = self.length + if from < 0 then from = from + used + 1 end + assert (from > 0 and from <= used) + + local a, i = self.array, from + n - 1 + while i >= from do + a[i] = v + i = i - 1 + end + return self + end, + + + --- Shift the whole array to the left by removing the left-most element. + -- This makes the array 1 element shorter than it was before the shift. + -- @function shift + -- @return the removed element. + shift = function (self) + argscheck ("shift", {"Array"}, {self}) + + local n = self.length - 1 + if n >= 0 then + local a = self.array + local elem = a[1] + move (a, 1, 2, n) + self:realloc (n) + return elem + end + return nil + end, + + + --- Shift the whole array to the right by inserting a new left-most element. + -- @function unshift + -- @param elem new element to be pushed + -- @treturn elem + unshift = function (self, elem) + argscheck ("unshift", {"Array", "number"}, {self, elem}) + + local a, n = self.array, self.length + self:realloc (n + 1) + move (a, 2, 1, n) + a[1] = elem + return elem + end, +} + + +--- Copy an alien.array. +-- @tparam alien.array array array to be copied +-- @treturn alien.array a new array identical to `array` +local function copy (array) + local a = calloc (array.type, array.length) + local n = array.size * array.length + memmove (a.buffer:topointer (1), array.buffer:topointer (1), n) + return a +end + + +------ +-- An efficient array of homogenous objects. +-- @table Array +-- @int length number of elements currently allocated +-- @tfield alien.array array a block of indexable memory +local Array = Object { + _type = "Array", + + + -- Prototype initial values. + length = 0, + array = calloc ("int", {0}), + + + -- Module functions. + _functions = _functions, + + + --- Instantiate a newly cloned Array. + -- @function __call + -- @string[opt] type the C type of each element + -- @tparam[opt] int|table init initial size or list of initial elements + -- @treturn Array a new Array object + _init = function (self, type, init) + if init ~= nil then + -- When called with 2 arguments: + argcheck ("Array", 1, {"string"}, type) + argcheck ("Array", 2, {"number", "table"}, init) + elseif type ~= nil then + -- When called with 1 argument: + argcheck ("Array", 1, {"number", "string", "table"}, type) + end + + local parray, pused = self.array, self.length + local plength, ptype = parray.length, parray.type + + -- Non-string argument 1 is really an init argument. + if typeof (type) ~= "string" then type, init = nil, type end + + -- New array type is copied from prototype if not specified. + if type == nil then type = ptype end + + local a + if init == nil then + -- 1a. A fast clone of prototype's array memory. + if type == ptype then + a = copy (parray) + + -- 1b. Size of elements changed, so we have to manually copy them + -- over where they are type coerced by underlying C code. + else + a = calloc (type, plength) + for i = 1, plength do + a[i] = parray[i] + end + end + + elseif typeof (init) == "number" then + -- 2a. Clone a number of elements from the prototype, padding with + -- zeros if we have more elements than the prototype. + if type == ptype then + a = copy (parray) + a:realloc (init) -- alien.array.realloc, not std.array.realloc!! + + -- 2b. Same, but element-wise copying with a different sized type. + else + a = calloc (type, init) + for i = 1, math.min (init, plength) do + a[i] = parray[i] + end + end + + setzero (a, pused + 1, init - pused) + self.length = init + + elseif typeof (init) == "table" then + -- 3. With an initialisation table, ignore prototype elements. + a = calloc (type, init) + self.length = #init + end + + self.array = a + return self + end, + + + --- Return the number of elements in this Array. + -- @function __len + -- @treturn int number of elements + __len = function (self) + argscheck ("__len", {"Array"}, {self}) + return self.length + end, + + + --- Return the `n`th character in this Array. + -- @function __index + -- @int n 1-based index, or negative to index starting from the right + -- @treturn string the element at index `n` + __index = function (self, n) + argscheck ("__index", {"Array", {"number", "string"}}, {self, n}) + + if typeof (n) == "number" then + if n < 0 then n = n + self.length + 1 end + if n > 0 and n <= self.length then + return self.array[n] + end + else + return _functions[n] + end + end, + + + --- Set the `n`th element of this Array to `elem`. + -- @function __newindex + -- @int n 1-based index + -- @param elem value to store at index n + -- @treturn Array the array + __newindex = function (self, n, elem) + argscheck ("__newindex", {"Array", "number", "number"}, {self, n, elem}) + + if typeof (n) == "number" then + local a, used = self.array, self.length + if n == 0 or math.abs (n) > used then + return a[0] -- guaranteed to be out of bounds + end + if n < 0 then n = n + used + 1 end + a[n] = elem + else + rawset (self, n, elem) + end + return self + end, + + + --- Return a string representation of the contents of this Array. + -- @function __tostring + -- @treturn string string representation + __tostring = function (self) + argscheck ("__tostring", {"Array"}, {self}) + + local a = self.array + local t = {} + for i = 1, self.length do + t[#t + 1] = tostring (a[i]) + end + t = { '"' .. a.type .. '"', "{" .. table.concat (t, ", ") .. "}" } + return prototype (self) .. " (" .. table.concat (t, ", ") .. ")" + end, +} + +return Array diff --git a/local.mk b/local.mk index 69dcf6c..7b153aa 100644 --- a/local.mk +++ b/local.mk @@ -63,6 +63,7 @@ luastddir = $(luadir)/std dist_luastd_DATA = \ lib/std/alien.lua \ + lib/std/array.lua \ lib/std/base.lua \ lib/std/container.lua \ lib/std/debug.lua \ @@ -127,6 +128,7 @@ dist_doc_DATA += \ $(srcdir)/doc/ldoc.css dist_classes_DATA += \ + $(srcdir)/doc/classes/std.array.html \ $(srcdir)/doc/classes/std.container.html \ $(srcdir)/doc/classes/std.list.html \ $(srcdir)/doc/classes/std.object.html \ diff --git a/specs/array_spec.yaml b/specs/array_spec.yaml new file mode 100644 index 0000000..cd61d56 --- /dev/null +++ b/specs/array_spec.yaml @@ -0,0 +1,592 @@ +before: + Object = require "std.object" + Array = require "std.array" + + prototype = Object.prototype + +specify Array: +- before: | + array = Array ("long", {1, 1}) + -- append fibonacci numbers until long word overflows + repeat + array:push (array[-1] + array[-2]) + until array.length > 50 or array[-1] < array[-2] + -- discard overflowed element + array:pop () + +- describe __call: + - it diagnoses wrong argument types: | + expect (Array (1, 2)). + to_error "bad argument #1 to 'Array' (string expected, got number)" + expect (Array (function () end)). + to_error "bad argument #1 to 'Array' (number, string or table expected, got function)" + expect (Array ("int", function () end)). + to_error "bad argument #2 to 'Array' (number or table expected, got function)" + - context with inherited element type: + - it constructs an empty array: + array = Array () + expect (array.length).to_be (0) + expect (array.array.type).to_be (Array.array.type) + - it constructs a sized array: + array = Array (100) + expect (array.length).to_be (100) + expect (array.array.type).to_be (Array.array.type) + - it sets uninitialised elements to zero: + array = Array (100) + for i = 1, 100 do + expect (array[i]).to_be (0) + end + expect (array.array.type).to_be (Array.array.type) + - it initialises values from a table: + array = Array {1, 4, 9, 16, 25, 36, 49, 64, 81} + expect (array.length).to_be (9) + for i = 1, array.length do + expect (array[i]).to_be (i * i) + end + expect (array.array.type).to_be (Array.array.type) + - it contains values from prototype array: + a = array () + for i = 3, array.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + expect (a.array.type).to_be (array.array.type) + - it truncates copied prototype values: + c = math.floor (array.length / 2) + a = array (c) + expect (a.length).to_be (c) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + expect (a.array.type).to_be (array.array.type) + - it zero pads copied prototype values: + a = array (array.length * 2) + expect (a.length).to_be (array.length * 2) + for i = 3, array.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + for i = array.length + 1, a.length do + expect (a[i]).to_be (0) + end + expect (a.array.type).to_be (array.array.type) + - context with specified element type: + - it constructs an empty array: + array = Array "double" + expect (array.length).to_be (0) + expect (array.array.type).to_be "double" + - it constructs a sized array: + array = Array ("double", 100) + expect (array.length).to_be (100) + expect (array.array.type).to_be "double" + - it sets uninitialised elements to zero: + array = Array ("double", 100) + for i = 1, 100 do + expect (array[i]).to_be (0) + end + expect (array.array.type).to_be "double" + - it initialises values from a table: + array = Array ("double", {1, 4, 9, 16, 25, 36, 49, 64, 81}) + expect (array.length).to_be (9) + for i = 1, array.length do + expect (array[i]).to_be (i * i) + end + expect (array.array.type).to_be "double" + - it contains values from prototype array: + a = array "double" + for i = 3, array.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + expect (a.array.type).to_be "double" + - it truncates copied prototype values: + c = math.floor (array.length / 2) + a = array ("double", c) + expect (a.length).to_be (c) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + expect (a.array.type).to_be "double" + - it zero pads copied prototype values: + a = array ("double", array.length * 2) + expect (a.length).to_be (array.length * 2) + for i = 3, array.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + for i = array.length + 1, a.length do + expect (a[i]).to_be (0) + end + expect (a.array.type).to_be "double" + +- describe __len: + - it returns the number of elements stored: + array = Array "char" + expect (array.length).to_be (0) + array = Array ("short", {1, 2, 3}) + expect (array.length).to_be (3) + +- describe __index: + - it returns nil for an empty array: + array = Array "int" + expect (array[1]).to_be (nil) + expect (array[-1]).to_be (nil) + - it retrieves a value stored at that index: + expect (array[1]).to_be (1) + expect (array[2]).to_be (1) + expect (array[3]).to_be (2) + expect (array[4]).to_be (3) + expect (array[5]).to_be (5) + - it retrieves negative indices counting from the right: + expect (array[-1]).to_be (array[array.length]) + expect (array[-2]).to_be (array[array.length - 1]) + expect (array[-(array.length - 1)]).to_be (array[2]) + expect (array[-(array.length)]).to_be (array[1]) + - it returns nil for out of bounds indices: + expect (array[-(array.length * 2)]).to_be (nil) + expect (array[-(array.length + 1)]).to_be (nil) + expect (array[0]).to_be (nil) + expect (array[array.length + 1]).to_be (nil) + expect (array[array.length * 2]).to_be (nil) + - it retrieves method names: + expect (type (array.push)).to_be "function" + expect (type (array.pop)).to_be "function" + - it diagnoses undefined methods: + expect (array.notamethod ()).to_error "attempt to call field 'notamethod'" + +- describe __newindex: + - it sets a new value at that index: + array[2] = 2 + expect (array[2]).to_be (2) + - it sets negative indexed elements counting from the right: + for i = 1, array.length do array[-i] = array.length - i + 1 end + for i = 1, array.length do + expect (array[i]).to_be (i) + end + - it diagnoses out of bounds indices: + for _, i in ipairs {array.length * -2, -1 - array.length, 0, + array.length + 1, array.length * 2} do + expect ((function () array[i] = i end) ()). + to_error "array access out of bounds" + end + +- describe __tostring: + - it renders all elements of the array: + array = Array ("char", {1, 4, 9, 16, 25}) + expect (tostring (array)).to_be 'Array ("char", {1, 4, 9, 16, 25})' + expect (tostring (Array "char")).to_be 'Array ("char", {})' + +- describe pop: + - context when called as a module function: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short array for this example. + array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (Array.pop ()). + to_error "bad argument #1 to 'pop' (Array expected, got no value)" + - it returns nil for an empty array: + array = Array "char" + expect (Array.pop (array)).to_be (nil) + - it removes an element from the array: + count = array.length + repeat + expect (array.length).to_be (count) + count = count - 1 + until Array.pop (array) == nil + expect (array.length).to_be (0) + - it returns the removed element: + while array.length > 2 do + expect (Array.pop (array)).to_be (array[-1] + array[-2]) + end + - it does not perturb existing elements: + Array.pop (array) + for i = 3, array.length do + expect (array[i]).to_be (array[i -1] + array[i - 2]) + end + - context when called as an object method: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short array for this example. + array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it returns nil for an empty array: + array = Array "char" + expect (array:pop ()).to_be (nil) + - it removes an element from the array: + count = array.length + repeat + expect (array.length).to_be (count) + count = count - 1 + until array:pop () == nil + expect (array.length).to_be (0) + - it returns the removed element: + while array.length > 2 do + expect (array:pop ()).to_be (array[-1] + array[-2]) + end + - it does not perturb existing elements: + array:pop () + for i = 3, array.length do + expect (array[i]).to_be (array[i -1] + array[i - 2]) + end + +- describe push: + - context when called as a module function: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short array for this example. + array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (Array.push ()). + to_error "bad argument #1 to 'push' (Array expected, got no value)" + expect (Array.push (array)). + to_error "bad argument #2 to 'push' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (Array.push (1234)). + to_error "bad argument #1 to 'push' (Array expected, got number)" + expect (Array.push (array, "short")). + to_error "bad argument #2 to 'push' (number expected, got string)" + - it adds a single element to an empty array: + array = Array "int" + Array.push (array, 42) + expect (array[1]).to_be (array[-1]) + - it adds an element to an array: + count = array.length + Array.push (array, 42) + expect (array[-1]).to_be (42) + expect (array.length).to_be (count + 1) + Array.push (array, -273) + expect (array[-1]).to_be (-273) + expect (array.length).to_be (count + 2) + - it does not perturb existing elements: + Array.push (array, 42) + for i = 3, array.length - 1 do + expect (array[i]).to_be (array[i - 1] + array[i - 2]) + end + - context when called as an object method: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short array for this example. + array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (array:push ()). + to_error "bad argument #2 to 'push' (number expected, got no value)" + - it diagnoses wrong argument type: | + expect (array:push ("short")). + to_error "bad argument #2 to 'push' (number expected, got string)" + - it adds a single element to an empty array: + array = Array "int" + array:push (42) + expect (array[1]).to_be (array[-1]) + - it adds an element to an array: + count = array.length + array:push (42) + expect (array[-1]).to_be (42) + expect (array.length).to_be (count + 1) + array:push (-273) + expect (array[-1]).to_be (-273) + expect (array.length).to_be (count + 2) + - it returns pushed value: + expect (array:push (42)).to_be (42) + expect (array:push (-273)).to_be (-273) + - it does not perturb existing elements: + array:push (42) + for i = 3, array.length - 1 do + expect (array[i]).to_be (array[i - 1] + array[i -2]) + end + +- describe realloc: + - context when called as a module function: + - it diagnoses missing arguments: | + expect (Array.realloc ()). + to_error "bad argument #1 to 'realloc' (Array expected, got no value)" + expect (Array.realloc (array)). + to_error "bad argument #2 to 'realloc' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (Array.realloc (1234)). + to_error "bad argument #1 to 'realloc' (Array expected, got number)" + expect (Array.realloc (array, "string")). + to_error "bad argument #2 to 'realloc' (number expected, got string)" + - it reduces the number of usable elements: + array = Array (100) + Array.realloc (array, 50) + expect (array.length).to_be (50) + expect (array.array.length >= 50).to_be (true) + - it truncates existing elements when reducing size: + a = array (100) + Array.realloc (a, 50) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it increases the number of usable elements: + array = Array (50) + Array.realloc (array, 100) + expect (array.length).to_be (100) + expect (array.array.length >= 100).to_be (true) + - it does not perturb existing element values: + a = array (50) + Array.realloc (a, 100) + for i = 3, 50 do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it sets new elements to zero: + a = array (50) + Array.realloc (a, 100) + for i = 51, a.length do + expect (a[i]).to_be (0) + end + - context when called as an object method: + - it diagnoses missing arguments: | + expect (array:realloc ()). + to_error "bad argument #2 to 'realloc' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (array:realloc "string"). + to_error "bad argument #2 to 'realloc' (number expected, got string)" + - it reduces the number of usable elements: + array = Array (100):realloc (50) + expect (array.length).to_be (50) + expect (array.array.length >= 50).to_be (true) + - it truncates existing elements when reducing size: + a = array (100):realloc (50) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it increases the number of usable elements: + array = Array (50):realloc (100) + expect (array.length).to_be (100) + expect (array.array.length >= 100).to_be (true) + - it does not perturb existing element values: + a = array (50):realloc (100) + for i = 3, 50 do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it sets new elements to zero: + a = array (50):realloc (100) + for i = 51, a.length do + expect (a[i]).to_be (0) + end + +- describe set: + - context when called as a module function: + - it diagnoses missing arguments: | + expect (Array.set ()). + to_error "bad argument #1 to 'set' (Array expected, got no value)" + expect (Array.set (array)). + to_error "bad argument #2 to 'set' (number expected, got no value)" + expect (Array.set (array, 1)). + to_error "bad argument #3 to 'set' (number expected, got no value)" + expect (Array.set (array, 1, 0)). + to_error "bad argument #4 to 'set' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (Array.set (100)). + to_error "bad argument #1 to 'set' (Array expected, got number)" + expect (Array.set (array, "bogus")). + to_error "bad argument #2 to 'set' (number expected, got string)" + expect (Array.set (array, 1, {0})). + to_error "bad argument #3 to 'set' (number expected, got table)" + expect (Array.set (array, 1, 0, function () end)). + to_error "bad argument #4 to 'set' (number expected, got function)" + - it changes the value of a subsequence of elements: + array = Array (100) + Array.set (array, 25, 1, 50) + for i = 1, array.length do + if i >= 25 and i < 75 then + expect (array[i]).to_be (1) + else + expect (array[i]).to_be (0) + end + end + - it understands negative from index: + array = Array (100) + Array.set (array, -50, 1, 50) + for i = 1, array.length do + if i <= 50 then + expect (array[i]).to_be (0) + else + expect (array[i]).to_be (1) + end + end + - it does not affect the prototype array elements: + a = array (100) + Array.set (a, 25, 1, 50) + for i = 3, array.length do + expect (array[i]).to_be (array[i - 1] + array[i - 2]) + end + - it does not affect elements outside range being set: + a = array (100) + Array.set (a, 25, 1, 50) + for i = 1, a.length do + if i >= 25 and i < 75 then + expect (a[i]).to_be (1) + elseif i <= array.length then + expect (a[i]).to_be (array[i]) + else + expect (a[i]).to_be (0) + end + end + - context when called as an object method: + - it diagnoses missing arguments: | + expect (array:set ()). + to_error "bad argument #2 to 'set' (number expected, got no value)" + expect (array:set (1)). + to_error "bad argument #3 to 'set' (number expected, got no value)" + expect (array:set (1, 0)). + to_error "bad argument #4 to 'set' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (array:set "bogus"). + to_error "bad argument #2 to 'set' (number expected, got string)" + expect (array:set (1, {0})). + to_error "bad argument #3 to 'set' (number expected, got table)" + expect (array:set (1, 0, function () end)). + to_error "bad argument #4 to 'set' (number expected, got function)" + - it changes the value of a subsequence of elements: + array = Array (100):set (25, 1, 50) + for i = 1, array.length do + if i >= 25 and i < 75 then + expect (array[i]).to_be (1) + else + expect (array[i]).to_be (0) + end + end + - it understands negative from index: + array = Array (100):set (-50, 1, 50) + for i = 1, array.length do + if i <= 50 then + expect (array[i]).to_be (0) + else + expect (array[i]).to_be (1) + end + end + - it does not affect the prototype array elements: + a = array (100):set (25, 1, 50) + for i = 3, array.length do + expect (array[i]).to_be (array[i - 1] + array[i - 2]) + end + - it does not affect elements outside range being set: + a = array (100):set (25, 1, 50) + for i = 1, a.length do + if i >= 25 and i < 75 then + expect (a[i]).to_be (1) + elseif i <= array.length then + expect (a[i]).to_be (array[i]) + else + expect (a[i]).to_be (0) + end + end + +- describe shift: + - context when called as a module function: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short array for this example. + array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (Array.shift ()). + to_error "bad argument #1 to 'shift' (Array expected, got no value)" + - it returns nil for an empty array: + array = Array "char" + expect (Array.shift (array)).to_be (nil) + - it removes an element from the array: + count = array.length + repeat + expect (array.length).to_be (count) + count = count - 1 + until Array.shift (array) == nil + expect (array.length).to_be (0) + - it returns the removed element: + while array.length > 2 do + expect (Array.shift (array)).to_be (array[2] - array[1]) + end + - it shifts existing elements one position left: + shiftme = array () + Array.shift (shiftme) + for i = 1, shiftme.length do + expect (shiftme[i]).to_be (array[i + 1]) + end + - context when called as an object method: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short array for this example. + array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it returns nil for an empty array: + array = Array "char" + expect (array:shift ()).to_be (nil) + - it removes an element from the array: + count = array.length + repeat + expect (array.length).to_be (count) + count = count - 1 + until array:shift () == nil + expect (array.length).to_be (0) + - it returns the removed element: + while array.length > 2 do + expect (array:shift ()).to_be (array[2] - array[1]) + end + - it shifts existing elements one position left: + shiftme = array () + shiftme:shift () + for i = 1, shiftme.length do + expect (shiftme[i]).to_be (array[i + 1]) + end + +- describe unshift: + - context when called as a module function: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short array for this example. + array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (Array.unshift ()). + to_error "bad argument #1 to 'unshift' (Array expected, got no value)" + expect (Array.unshift (array)). + to_error "bad argument #2 to 'unshift' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (Array.unshift (1234)). + to_error "bad argument #1 to 'unshift' (Array expected, got number)" + expect (Array.unshift (array, "short")). + to_error "bad argument #2 to 'unshift' (number expected, got string)" + - it adds a single element to an empty array: + array = Array "int" + Array.unshift (array, 42) + expect (array[1]).to_be (array[-1]) + - it inserts an element into an array: + count = array.length + Array.unshift (array, 42) + expect (array[1]).to_be (42) + expect (array.length).to_be (count + 1) + Array.unshift (array, -273) + expect (array[1]).to_be (-273) + expect (array.length).to_be (count + 2) + - it shifts existing elements one position right: + unshiftme = array () + Array.unshift (unshiftme, 42) + for i = 1, array.length do + expect (unshiftme[i + 1]).to_be (array[i]) + end + - context when called as an object method: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short array for this example. + array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (array:unshift ()). + to_error "bad argument #2 to 'unshift' (number expected, got no value)" + - it diagnoses wrong argument type: | + expect (array:unshift ("short")). + to_error "bad argument #2 to 'unshift' (number expected, got string)" + - it adds a single element to an empty array: + array = Array "int" + array:unshift (42) + expect (array[1]).to_be (array[-1]) + - it adds an element to an array: + count = array.length + array:unshift (42) + expect (array[1]).to_be (42) + expect (array.length).to_be (count + 1) + array:unshift (-273) + expect (array[1]).to_be (-273) + expect (array.length).to_be (count + 2) + - it returns unshifted value: + expect (array:unshift (42)).to_be (42) + expect (array:unshift (-273)).to_be (-273) + - it shifts existing elements one position right: + unshiftme = array () + unshiftme:unshift (42) + for i = 1, array.length do + expect (unshiftme[i + 1]).to_be (array[i]) + end diff --git a/specs/specs.mk b/specs/specs.mk index 0d46fef..3bf523e 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -25,6 +25,7 @@ SPECL_OPTS = --unicode specl_SPECS = \ $(srcdir)/specs/alien_spec.yaml \ + $(srcdir)/specs/array_spec.yaml \ $(srcdir)/specs/base_spec.yaml \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ From d13c1bd72e06e9f5e85807c3e4d436d4c290e1eb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 20 May 2014 17:07:20 +0700 Subject: [PATCH 161/703] array: support simultaneous alien and Lua array objects. * lib/std/array.lua (alien_type): A set of valid types for arrays to be implemented with alien.array. (topointer): Default offset to 1. (setzero): Use memset for alien arrays, direct element buffer copying for Lua arrays. (shift, unshift): Use memmove for manipulating alien.arrays, or else table.insert and table.remove for Lua tables. (copy): Remove. (clone): Use memmove for copying elements between same-size element alien arrays, direct element buffer copying otherwise. (_init): Simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 138 ++++++++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 61 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index 34f3d49..aa59aa8 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -36,12 +36,29 @@ local argcheck, argscheck = debug.argcheck, debug.argscheck local Object = require "std.object" local prototype = Object.prototype +local alien_type = {} + local have_alien, alien = pcall (require, "alien") -if not have_alien then +if have_alien then + + -- Element types to be managed by alien: + for e in require "std.base".elems { + "byte", "char", "short", "ushort", "int", "uint", "long", "ulong", + "ptrdiff_t", "size_t", "float", "double", "pointer", + "ref char", "ref int", "ref uint", "ref double", + "longlong", "ulonglong"} + do + alien_type[e] = true + end + +else + + -- Fallback to Lua implementation. alien = require "std.alien" -end -local calloc, memmove, memset = alien.array, alien.memmove, alien.memset +end +local calloc, memmove, memset, sizeof = + alien.array, alien.memmove, alien.memset, alien.sizeof local typeof = type @@ -50,9 +67,10 @@ local element_chunk_size = 16 --- Convert an array element index into a pointer. -- @tparam alien.array array an array --- @int i an index into array +-- @int i[opt=1] an index into array -- @treturn alien.buffer.pointer suitable for memmove or memset local function topointer (array, i) + i = i or 1 return array.buffer:topointer ((i - 1) * array.size + 1) end @@ -64,19 +82,35 @@ end -- @treturn alien.array array local function setzero (array, from, n) if n > 0 then - memset (topointer (array, from), 0, n * array.size) + if alien_type[array.type] then + memset (topointer (array, from), 0, n * array.size) + else + for i = from, from + n - 1 do + array.buffer[i] = 0 + end + end end return array end ---- Fast move of a block of elements within an array. --- @tparam alien.array array an array --- @int to index of first destination element --- @int from index of first source element --- @int n number of elements to move -local function move (array, to, from, n) - memmove (topointer (array, to), topointer (array, from), n * array.size) +--- Clone the elements of an array. +-- @param self object with in progress clone +-- @string type element type +-- @int required number of elements required in clone +-- @treturn alien.array a clone of `self.array` +local function clone (self, type, required) + local parray, pused = self.array, self.length + local a = calloc (type, required) + + if alien_type[type] and sizeof (type) == sizeof (parray.type) then + local bytes = math.min (required, pused) * parray.size + memmove (topointer (a), topointer (parray), bytes) + else + local a, b = a.buffer, parray.buffer + for i = 1, math.min (required, pused) do a[i] = b[i] end + end + return setzero (a, pused + 1, required - pused) end @@ -165,9 +199,16 @@ local _functions = { local n = self.length - 1 if n >= 0 then local a = self.array - local elem = a[1] - move (a, 1, 2, n) - self:realloc (n) + local elem + if alien_type[a.type] then + elem = a[1] + memmove (topointer (a, 1), topointer (a, 2), n * a.size) + self:realloc (n) + else + elem = table.remove (a.buffer, 1) + self.length = n + a.length = n + end return elem end return nil @@ -182,25 +223,20 @@ local _functions = { argscheck ("unshift", {"Array", "number"}, {self, elem}) local a, n = self.array, self.length - self:realloc (n + 1) - move (a, 2, 1, n) - a[1] = elem + if alien_type[a.type] then + self:realloc (n + 1) + memmove (topointer (a, 2), topointer (a, 1), n * a.size) + a[1] = elem + else + table.insert (a.buffer, 1, elem) + self.length = n + 1 + a.length = n + 1 + end return elem end, } ---- Copy an alien.array. --- @tparam alien.array array array to be copied --- @treturn alien.array a new array identical to `array` -local function copy (array) - local a = calloc (array.type, array.length) - local n = array.size * array.length - memmove (a.buffer:topointer (1), array.buffer:topointer (1), n) - return a -end - - ------ -- An efficient array of homogenous objects. -- @table Array @@ -220,8 +256,12 @@ local Array = Object { --- Instantiate a newly cloned Array. + -- If not specified, `type` will be the same as the prototype array being + -- cloned; otherwise, it can be any string. Only valid alien accepted by + -- `alien.array` will use the fast `alien.array` managed memory buffer for + -- Array contents; otherwise, a much slower Lua emulation is used. -- @function __call - -- @string[opt] type the C type of each element + -- @tparam[opt] int|table init initial size or list of initial elements -- @treturn Array a new Array object _init = function (self, type, init) @@ -234,46 +274,22 @@ local Array = Object { argcheck ("Array", 1, {"number", "string", "table"}, type) end - local parray, pused = self.array, self.length - local plength, ptype = parray.length, parray.type - -- Non-string argument 1 is really an init argument. if typeof (type) ~= "string" then type, init = nil, type end -- New array type is copied from prototype if not specified. - if type == nil then type = ptype end + local parray = self.array + if type == nil then type = parray.type end local a if init == nil then - -- 1a. A fast clone of prototype's array memory. - if type == ptype then - a = copy (parray) - - -- 1b. Size of elements changed, so we have to manually copy them - -- over where they are type coerced by underlying C code. - else - a = calloc (type, plength) - for i = 1, plength do - a[i] = parray[i] - end - end + -- 1. A clone of prototype array. + a = clone (self, type, parray.length) elseif typeof (init) == "number" then - -- 2a. Clone a number of elements from the prototype, padding with - -- zeros if we have more elements than the prototype. - if type == ptype then - a = copy (parray) - a:realloc (init) -- alien.array.realloc, not std.array.realloc!! - - -- 2b. Same, but element-wise copying with a different sized type. - else - a = calloc (type, init) - for i = 1, math.min (init, plength) do - a[i] = parray[i] - end - end - - setzero (a, pused + 1, init - pused) + -- 2. Clone a number of elements from the prototype, padding with + -- zeros if we have more elements than the prototype. + a = clone (self, type, init) self.length = init elseif typeof (init) == "table" then From d8157eccd71837601257a6ba019f530679cfc465 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 21 May 2014 11:31:11 +0700 Subject: [PATCH 162/703] slingshot: sync with upstream. * slingshot: Sync with upstream for grep GNUism fix. Signed-off-by: Gary V. Vaughan --- slingshot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slingshot b/slingshot index 4a4ad2f..39016bb 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 4a4ad2f1a7ac754983d3df6a01f146620f260ecf +Subproject commit 39016bbdb0c9a8730b522f5acb85aa4248a69814 From 77a4beee9e447f387fbe79eae04645ffb8e62411 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 21 May 2014 11:45:16 +0700 Subject: [PATCH 163/703] configury: don't require specific git version. * bootstrap.conf (buildreq): Don't set a git version number, or else `GIT=true ./bootstrap` doesn't work. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap.conf b/bootstrap.conf index 19ba632..5635e88 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -35,7 +35,7 @@ # Build prerequisites buildreq=' - git 1.5.5 http://git-scm.com + git - http://git-scm.com ldoc 1.4.0 http://luarocks.org/repositories/rocks/ldoc-1.4.2-1.rockspec specl 11 http://luarocks.org/repositories/rocks/specl-11-1.rockspec ' From cd3770856412b7091bf68ce3114259af65fba732 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 21 May 2014 13:46:52 +0700 Subject: [PATCH 164/703] optparse: fix a global symbol leak. * lib/std/optparse.lua (on): Declare `normal` as a local variable. * NEWS (Bug fixes): Updated. Signed-off-by: Gary V. Vaughan --- NEWS | 2 ++ lib/std/optparse.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 2df7a15..8836190 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,8 @@ Stdlib NEWS - User visible changes - `debug.trace` works with Lua 5.2.x again. + - `optparse.on` now works with `std.strict` enabled. + * Noteworthy changes in release 40 (2014-05-01) [stable] diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 62610b0..5837ec6 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -479,7 +479,7 @@ local function on (self, opts, handler, value) if type (opts) == "string" then opts = { opts } end handler = handler or flag -- unspecified options behave as flags - normal = {} + local normal = {} for _, optspec in ipairs (opts) do optspec:gsub ("(%S+)", function (opt) From 1c9126ec033b54c695e98179baef415917ebf86a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 21 May 2014 13:51:59 +0700 Subject: [PATCH 165/703] optparse: fix another global symbol leak. * lib/std/optparse.lua (on): Declare `key` as a local variable. Signed-off-by: Gary V. Vaughan --- lib/std/optparse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 5837ec6..8e750da 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -504,7 +504,7 @@ local function on (self, opts, handler, value) end -- strip leading '-', and convert non-alphanums to '_' - key = normal[#normal]:match ("^%-*(.*)$"):gsub ("%W", "_") + local key = normal[#normal]:match ("^%-*(.*)$"):gsub ("%W", "_") for _, opt in ipairs (normal) do self[opt] = { key = key, handler = handler, value = value } From ac4278279b5c9a2376154a6bf31d894e15b5480b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 24 May 2014 15:34:52 +0700 Subject: [PATCH 166/703] debug: argcheck "any" does not match `nil`. * specs/debug_spec.yaml (argcheck): Passing a nil argument does not satisfy the "any" spec. * lib/debug.lua (argcheck): Require non-nil argument before setting passing status for an "any" spec. (argscheck): Remove "any" shortcut. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 13 +++++++------ specs/debug_spec.yaml | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 5deec6a..bbc0656 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -147,7 +147,7 @@ end -- #table accept any non-empty table -- list accept a table with a non-empty array part -- object accept any std.Object derived type --- any accept any argument type +-- any accept any non-nil argument type -- -- Call `argerror` if there is a type mismatch. -- @@ -165,9 +165,12 @@ local function argcheck (name, i, expected, actual, level) -- Check actual has one of the types from expected local ok, actualtype = false, prototype (actual) - for _, check in ipairs (expected) do + for i, check in ipairs (expected) do if check == "any" then - ok = true + expected[i] = "any value" + if actual ~= nil then + ok = true + end elseif check == "#table" then if actualtype == "table" and next (actual) then @@ -214,9 +217,7 @@ local function argscheck (name, expected, actual) if typeof (actual) ~= "table" then actual = {actual} end for i, v in ipairs (expected) do - if v ~= "any" then - argcheck (name, i, expected[i], actual[i], 3) - end + argcheck (name, i, expected[i], actual[i], 3) end end diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 330f652..86a39c8 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -156,6 +156,7 @@ specify std.debug: expect (fn ("object", nil)).to_error "object expected, got no value" expect (fn ("Object", nil)).to_error "Object expected, got no value" expect (fn ("Foo", nil)).to_error "Foo expected, got no value" + expect (fn ("any", nil)).to_error "any value expected, got no value" - it diagnoses mismatched object types: expect (fn ("object", {0})).to_error "object expected, got table" expect (fn ("Object", {0})).to_error "Object expected, got table" @@ -170,7 +171,6 @@ specify std.debug: expect (fn ("object", Foo)).not_to_error () expect (fn ("object", Foo {})).not_to_error () - it matches anything: - expect (fn ("any", nil)).not_to_error () expect (fn ("any", true)).not_to_error () expect (fn ("any", {})).not_to_error () expect (fn ("any", Object)).not_to_error () From b9f348237bb51a1a7d3c8ac7fa269122c6c18509 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 23 May 2014 17:48:05 +0700 Subject: [PATCH 167/703] base_array: non-alien capable base class for std.array. * specs/base_array_spec.yaml: New file. Specify behaviour for base_array. * specs/specs.mk (specl_SPECS): Add specs/base_array_spec.yaml. * lib/std/base_array.lua: New file. Implement base_array class to satisfy specification. * local.mk (dist_luastd_DATA): Add lib/std/base_array.lua. Signed-off-by: Gary V. Vaughan --- lib/std/base_array.lua | 226 +++++++++++++++ local.mk | 1 + specs/base_array_spec.yaml | 546 +++++++++++++++++++++++++++++++++++++ specs/specs.mk | 1 + 4 files changed, 774 insertions(+) create mode 100644 lib/std/base_array.lua create mode 100644 specs/base_array_spec.yaml diff --git a/lib/std/base_array.lua b/lib/std/base_array.lua new file mode 100644 index 0000000..f6cc411 --- /dev/null +++ b/lib/std/base_array.lua @@ -0,0 +1,226 @@ +------ +-- @module std.base_array + + +local debug = require "std.debug" +local argcheck, argscheck = debug.argcheck, debug.argscheck + +local Object = require "std.object" +local prototype = Object.prototype + +local typeof = type + + +local _functions = { + --- Remove the right-most element. + -- @function pop + -- @return the right-most element + pop = function (self) + argcheck ("pop", 1, "Array", self) + + self.length = math.max (self.length - 1, 0) + return table.remove (self.buffer) + end, + + + --- Add elem as the new right-most element. + -- @function push + -- @param elem new element to be pushed + -- @return elem + push = function (self, elem) + argscheck ("push", {"Array", "any"}, {self, elem}) + + local length = self.length + 1 + self.buffer[length] = elem + self.length = length + return elem + end, + + + --- Change the number of elements allocated to be at least `n`. + -- @function realloc + -- @int n the number of elements required + -- @treturn Array the array + realloc = function (self, n) + argscheck ("realloc", {"Array", "number"}, {self, n}) + + -- Zero padding for uninitialised elements. + for i = self.length + 1, n do + self.buffer[i] = 0 + end + self.length = n + + return self + end, + + + --- Set `n` elements starting at `from` to `v`. + -- @function set + -- @int from index of first element to set + -- @param v value to store + -- @int n number of elements to set + -- @treturn Array the array + set = function (self, from, v, n) + argscheck ("set", {"Array", "number", "any", "number"}, + {self, from, v, n}) + + local length = self.length + if from < 0 then from = from + length + 1 end + assert (from > 0 and from <= length) + + for i = from, from + n - 1 do + self[i] = v + end + + return self + end, + + + --- Shift the whole array to the left by removing the left-most element. + -- This makes the array 1 element shorter than it was before the shift. + -- @function shift + -- @return the removed element. + shift = function (self) + argcheck ("shift", 1, "Array", self) + + self.length = math.max (self.length - 1, 0) + return table.remove (self.buffer, 1) + end, + + + --- Shift the whole array to the right by inserting a new left-most element. + -- @function unshift + -- @param elem new element to be pushed + -- @treturn elem + unshift = function (self, elem) + argscheck ("unshift", {"Array", "any"}, {self, elem}) + + self.length = self.length + 1 + table.insert (self.buffer, 1, elem) + return elem + end, +} + + +------ +-- A container for contiguous objects. +-- @table Array +-- @tfield table buffer contained objects +-- @int length number of elements +local Array = Object { + _type = "Array", + + + -- Prototype initial values. + buffer = {}, + length = 0, + + + -- Module functions. + _functions = _functions, + + + --- Instantiate a newly cloned Array. + -- @function __call + -- @string[opt] type element type name + -- @tparam[opt] int|table init initial size or list of initial elements + -- @treturn Array a new Array object + _init = function (self, type, init) + if init ~= nil then + -- When called with 2 arguments: + argcheck ("Array", 1, "string", type) + argcheck ("Array", 2, {"number", "table"}, init) + elseif type ~= nil then + argcheck ("Array", 1, {"number", "string", "table"}, type) + end + + -- Non-string argument 1 is reall an init argument. + if typeof (type) ~= "string" then type, init = nil, type end + + local b = {} + if typeof (init) == "table" then + for i = 1, #init do + b[i] = init[i] + end + else + local plength = self.length + local length = init or plength + + for i = 1, math.min (plength, length) do + b[i] = self.buffer[i] + end + for i = plength + 1, length do + b[i] = 0 + end + end + self.buffer = b + self.length = #b + return self + end, + + + --- Return the number of elements in this Array. + -- @function __len + -- @treturn int number of elements + __len = function (self) + argcheck ("__len", 1, "Array", self) + return self.length + end, + + + --- Return the `n`th character in this Array. + -- @function __index + -- @int n 1-based index, or negative to index starting from the right + -- @treturn string the element at index `n` + __index = function (self, n) + argscheck ("__index", {"Array", {"number", "string"}}, {self, n}) + + if typeof (n) == "number" then + if n < 0 then n = n + self.length + 1 end + if n > 0 and n <= self.length then + return self.buffer[n] + end + else + return _functions[n] + end + end, + + + --- Set the `n`th element of this Array to `elem`. + -- @function __newindex + -- @int n 1-based index + -- @param elem value to store at index n + -- @treturn Array the array + __newindex = function (self, n, elem) + argscheck ("__newindex", {"Array", "number", "any"}, {self, n, elem}) + + if typeof (n) == "number" then + local used = self.length + if n == 0 or math.abs (n) > used then + error ("array access " .. n .. " out of bounds: 0 < abs (n) <= " .. + tostring (self.length), 2) + end + if n < 0 then n = n + used + 1 end + self.buffer[n] = elem + else + rawset (self, n, elem) + end + return self + end, + + + --- Return a string representation of the contents of this Array. + -- @function __tostring + -- @treturn string string representation + __tostring = function (self) + argcheck ("__tostring", 1, "Array", self) + + local t = {} + for i = 1, self.length do + t[#t + 1] = tostring (self[i]) + end + return prototype (self) .. " {" .. table.concat (t, ", ") .. "}" + end, +} + +return Array diff --git a/local.mk b/local.mk index 7b153aa..d86d465 100644 --- a/local.mk +++ b/local.mk @@ -65,6 +65,7 @@ dist_luastd_DATA = \ lib/std/alien.lua \ lib/std/array.lua \ lib/std/base.lua \ + lib/std/base_array.lua \ lib/std/container.lua \ lib/std/debug.lua \ lib/std/functional.lua \ diff --git a/specs/base_array_spec.yaml b/specs/base_array_spec.yaml new file mode 100644 index 0000000..9d7d353 --- /dev/null +++ b/specs/base_array_spec.yaml @@ -0,0 +1,546 @@ +before: + Object = require "std.object" + Array = require "std.base_array" + + prototype = Object.prototype + +specify Array: +- before: | + array = Array {1, 1} + -- append fibonacci numbers until long word overflows + repeat + array:push (array[-1] + array[-2]) + until array.length > 50 or array[-1] < array[-2] + -- discard overflowed element + array:pop () + +- describe __call: + - it diagnoses wrong argument types: | + expect (Array (1, 2)). + to_error "bad argument #1 to 'Array' (string expected, got number)" + expect (Array (function () end)). + to_error "bad argument #1 to 'Array' (number, string or table expected, got function)" + expect (Array ("int", function () end)). + to_error "bad argument #2 to 'Array' (number or table expected, got function)" + - context with inherited element type: + - it constructs an empty array: + array = Array () + expect (array.length).to_be (0) + - it constructs a sized array: + array = Array (100) + expect (array.length).to_be (100) + - it sets uninitialised elements to zero: + array = Array (100) + for i = 1, 100 do + expect (array[i]).to_be (0) + end + - it initialises values from a table: + array = Array {1, 4, 9, 16, 25, 36, 49, 64, 81} + expect (array.length).to_be (9) + for i = 1, array.length do + expect (array[i]).to_be (i * i) + end + - it contains values from prototype array: + a = array () + for i = 3, array.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it truncates copied prototype values: + c = math.floor (array.length / 2) + a = array (c) + expect (a.length).to_be (c) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it zero pads copied prototype values: + a = array (array.length * 2) + expect (a.length).to_be (array.length * 2) + for i = 3, array.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + for i = array.length + 1, a.length do + expect (a[i]).to_be (0) + end + - context with specified element type: + - it constructs an empty array: + array = Array () + expect (array.length).to_be (0) + array = Array {} + expect (array.length).to_be (0) + - it constructs a sized array: + array = Array (100) + expect (array.length).to_be (100) + - it sets uninitialised elements to zero: + array = Array (100) + for i = 1, 100 do + expect (array[i]).to_be (0) + end + - it initialises values from a table: + array = Array {1, 4, 9, 16, 25, 36, 49, 64, 81} + expect (array.length).to_be (9) + for i = 1, array.length do + expect (array[i]).to_be (i * i) + end + - it contains values from prototype array: + a = array () + for i = 3, array.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it truncates copied prototype values: + c = math.floor (array.length / 2) + a = array (c) + expect (a.length).to_be (c) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it zero pads copied prototype values: + a = array (array.length * 2) + expect (a.length).to_be (array.length * 2) + for i = 3, array.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + for i = array.length + 1, a.length do + expect (a[i]).to_be (0) + end + +- describe __len: + - it returns the number of elements stored: + array = Array () + expect (array.length).to_be (0) + array = Array {1, 2, 3} + expect (array.length).to_be (3) + +- describe __index: + - it returns nil for an empty array: + array = Array () + expect (array[1]).to_be (nil) + expect (array[-1]).to_be (nil) + - it retrieves a value stored at that index: + expect (array[1]).to_be (1) + expect (array[2]).to_be (1) + expect (array[3]).to_be (2) + expect (array[4]).to_be (3) + expect (array[5]).to_be (5) + - it retrieves negative indices counting from the right: + expect (array[-1]).to_be (array[array.length]) + expect (array[-2]).to_be (array[array.length - 1]) + expect (array[-(array.length - 1)]).to_be (array[2]) + expect (array[-(array.length)]).to_be (array[1]) + - it returns nil for out of bounds indices: + expect (array[-(array.length * 2)]).to_be (nil) + expect (array[-(array.length + 1)]).to_be (nil) + expect (array[0]).to_be (nil) + expect (array[array.length + 1]).to_be (nil) + expect (array[array.length * 2]).to_be (nil) + - it retrieves method names: + expect (type (array.push)).to_be "function" + expect (type (array.pop)).to_be "function" + - it diagnoses undefined methods: + expect (array.notamethod ()).to_error "attempt to call field 'notamethod'" + +- describe __newindex: + - it sets a new value at that index: + array[2] = 2 + expect (array[2]).to_be (2) + - it sets negative indexed elements counting from the right: + for i = 1, array.length do array[-i] = array.length - i + 1 end + for i = 1, array.length do + expect (array[i]).to_be (i) + end + - it diagnoses out of bounds indices: + for _, i in ipairs {array.length * -2, -1 - array.length, 0, + array.length + 1, array.length * 2} do + expect ((function () array[i] = i end) ()). + to_error "out of bounds" + end + +- describe __tostring: + - it renders all elements of the array: + array = Array {1, 4, 9, 16, 25} + expect (tostring (array)).to_be 'Array {1, 4, 9, 16, 25}' + expect (tostring (Array ())).to_be 'Array {}' + +- describe pop: + - before: + array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} + - context when called as a module function: + - it diagnoses missing arguments: | + expect (Array.pop ()). + to_error "bad argument #1 to 'pop' (Array expected, got no value)" + - it returns nil for an empty array: + array = Array () + expect (Array.pop (array)).to_be (nil) + - it removes an element from the array: + count = array.length + repeat + expect (array.length).to_be (count) + count = count - 1 + until Array.pop (array) == nil + expect (array.length).to_be (0) + - it returns the removed element: + while array.length > 2 do + expect (Array.pop (array)).to_be (array[-1] + array[-2]) + end + - it does not perturb existing elements: + Array.pop (array) + for i = 3, array.length do + expect (array[i]).to_be (array[i -1] + array[i - 2]) + end + - context when called as an object method: + - before: + array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} + - it returns nil for an empty array: + array = Array () + expect (array:pop ()).to_be (nil) + - it removes an element from the array: + count = array.length + repeat + expect (array.length).to_be (count) + count = count - 1 + until array:pop () == nil + expect (array.length).to_be (0) + - it returns the removed element: + while array.length > 2 do + expect (array:pop ()).to_be (array[-1] + array[-2]) + end + - it does not perturb existing elements: + array:pop () + for i = 3, array.length do + expect (array[i]).to_be (array[i -1] + array[i - 2]) + end + +- describe push: + - context when called as a module function: + - before: + array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} + - it diagnoses missing arguments: | + expect (Array.push ()). + to_error "bad argument #1 to 'push' (Array expected, got no value)" + expect (Array.push (Array)). + to_error "bad argument #2 to 'push' (any value expected, got no value)" + - it diagnoses wrong argument types: | + expect (Array.push (1234)). + to_error "bad argument #1 to 'push' (Array expected, got number)" + - it adds a single element to an empty array: + array = Array () + Array.push (array, 42) + expect (array[1]).to_be (array[-1]) + - it adds an element to an array: + count = array.length + Array.push (array, 42) + expect (array[-1]).to_be (42) + expect (array.length).to_be (count + 1) + Array.push (array, -273) + expect (array[-1]).to_be (-273) + expect (array.length).to_be (count + 2) + - it does not perturb existing elements: + Array.push (array, 42) + for i = 3, array.length - 1 do + expect (array[i]).to_be (array[i - 1] + array[i - 2]) + end + - context when called as an object method: + - before: + array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} + - it diagnoses missing arguments: | + expect (array:push ()). + to_error "bad argument #2 to 'push' (any value expected, got no value)" + - it adds a single element to an empty array: + array = Array () + array:push (42) + expect (array[1]).to_be (array[-1]) + - it adds an element to an array: + count = array.length + array:push (42) + expect (array[-1]).to_be (42) + expect (array.length).to_be (count + 1) + array:push (-273) + expect (array[-1]).to_be (-273) + expect (array.length).to_be (count + 2) + - it returns pushed value: + expect (array:push (42)).to_be (42) + expect (array:push (-273)).to_be (-273) + - it does not perturb existing elements: + array:push (42) + for i = 3, array.length - 1 do + expect (array[i]).to_be (array[i - 1] + array[i -2]) + end + +- describe realloc: + - context when called as a module function: + - it diagnoses missing arguments: | + expect (Array.realloc ()). + to_error "bad argument #1 to 'realloc' (Array expected, got no value)" + expect (Array.realloc (array)). + to_error "bad argument #2 to 'realloc' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (Array.realloc (1234)). + to_error "bad argument #1 to 'realloc' (Array expected, got number)" + expect (Array.realloc (array, "string")). + to_error "bad argument #2 to 'realloc' (number expected, got string)" + - it reduces the number of usable elements: + array = Array (100) + Array.realloc (array, 50) + expect (array.length).to_be (50) + - it truncates existing elements when reducing size: + a = array (100) + Array.realloc (a, 50) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it increases the number of usable elements: + array = Array (50) + Array.realloc (array, 100) + expect (array.length).to_be (100) + - it does not perturb existing element values: + a = array (50) + Array.realloc (a, 100) + for i = 3, 50 do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it sets new elements to zero: + a = array (50) + Array.realloc (a, 100) + for i = 51, a.length do + expect (a[i]).to_be (0) + end + - context when called as an object method: + - it diagnoses missing arguments: | + expect (array:realloc ()). + to_error "bad argument #2 to 'realloc' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (array:realloc "string"). + to_error "bad argument #2 to 'realloc' (number expected, got string)" + - it reduces the number of usable elements: + array = Array (100):realloc (50) + expect (array.length).to_be (50) + - it truncates existing elements when reducing size: + a = array (100):realloc (50) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it increases the number of usable elements: + array = Array (50):realloc (100) + expect (array.length).to_be (100) + - it does not perturb existing element values: + a = array (50):realloc (100) + for i = 3, 50 do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it sets new elements to zero: + a = array (50):realloc (100) + for i = 51, a.length do + expect (a[i]).to_be (0) + end + +- describe set: + - context when called as a module function: + - it diagnoses missing arguments: | + expect (Array.set ()). + to_error "bad argument #1 to 'set' (Array expected, got no value)" + expect (Array.set (array)). + to_error "bad argument #2 to 'set' (number expected, got no value)" + expect (Array.set (array, 1)). + to_error "bad argument #3 to 'set' (any value expected, got no value)" + expect (Array.set (array, 1, 0)). + to_error "bad argument #4 to 'set' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (Array.set (100)). + to_error "bad argument #1 to 'set' (Array expected, got number)" + expect (Array.set (array, "bogus")). + to_error "bad argument #2 to 'set' (number expected, got string)" + expect (Array.set (array, 1, 0, function () end)). + to_error "bad argument #4 to 'set' (number expected, got function)" + - it changes the value of a subsequence of elements: + array = Array (100) + Array.set (array, 25, 1, 50) + for i = 1, array.length do + if i >= 25 and i < 75 then + expect (array[i]).to_be (1) + else + expect (array[i]).to_be (0) + end + end + - it understands negative from index: + array = Array (100) + Array.set (array, -50, 1, 50) + for i = 1, array.length do + if i <= 50 then + expect (array[i]).to_be (0) + else + expect (array[i]).to_be (1) + end + end + - it does not affect the prototype array elements: + a = array (100) + Array.set (a, 25, 1, 50) + for i = 3, array.length do + expect (array[i]).to_be (array[i - 1] + array[i - 2]) + end + - it does not affect elements outside range being set: + a = array (100) + Array.set (a, 25, 1, 50) + for i = 1, a.length do + if i >= 25 and i < 75 then + expect (a[i]).to_be (1) + elseif i <= array.length then + expect (a[i]).to_be (array[i]) + else + expect (a[i]).to_be (0) + end + end + - context when called as an object method: + - it diagnoses missing arguments: | + expect (array:set ()). + to_error "bad argument #2 to 'set' (number expected, got no value)" + expect (array:set (1)). + to_error "bad argument #3 to 'set' (any value expected, got no value)" + expect (array:set (1, 0)). + to_error "bad argument #4 to 'set' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (array:set "bogus"). + to_error "bad argument #2 to 'set' (number expected, got string)" + expect (array:set (1, 0, function () end)). + to_error "bad argument #4 to 'set' (number expected, got function)" + - it changes the value of a subsequence of elements: + array = Array (100):set (25, 1, 50) + for i = 1, array.length do + if i >= 25 and i < 75 then + expect (array[i]).to_be (1) + else + expect (array[i]).to_be (0) + end + end + - it understands negative from index: + array = Array (100):set (-50, 1, 50) + for i = 1, array.length do + if i <= 50 then + expect (array[i]).to_be (0) + else + expect (array[i]).to_be (1) + end + end + - it does not affect the prototype array elements: + a = array (100):set (25, 1, 50) + for i = 3, array.length do + expect (array[i]).to_be (array[i - 1] + array[i - 2]) + end + - it does not affect elements outside range being set: + a = array (100):set (25, 1, 50) + for i = 1, a.length do + if i >= 25 and i < 75 then + expect (a[i]).to_be (1) + elseif i <= array.length then + expect (a[i]).to_be (array[i]) + else + expect (a[i]).to_be (0) + end + end + +- describe shift: + - context when called as a module function: + - before: + array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} + - it diagnoses missing arguments: | + expect (Array.shift ()). + to_error "bad argument #1 to 'shift' (Array expected, got no value)" + - it returns nil for an empty array: + array = Array () + expect (Array.shift (array)).to_be (nil) + - it removes an element from the array: + count = array.length + repeat + expect (array.length).to_be (count) + count = count - 1 + until Array.shift (array) == nil + expect (array.length).to_be (0) + - it returns the removed element: + while array.length > 2 do + expect (Array.shift (array)).to_be (array[2] - array[1]) + end + - it shifts existing elements one position left: + shiftme = array () + Array.shift (shiftme) + for i = 1, shiftme.length do + expect (shiftme[i]).to_be (array[i + 1]) + end + - context when called as an object method: + - before: + array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} + - it returns nil for an empty array: + array = Array () + expect (array:shift ()).to_be (nil) + - it removes an element from the array: + count = array.length + repeat + expect (array.length).to_be (count) + count = count - 1 + until array:shift () == nil + expect (array.length).to_be (0) + - it returns the removed element: + while array.length > 2 do + expect (array:shift ()).to_be (array[2] - array[1]) + end + - it shifts existing elements one position left: + shiftme = array () + shiftme:shift () + for i = 1, shiftme.length do + expect (shiftme[i]).to_be (array[i + 1]) + end + +- describe unshift: + - context when called as a module function: + - before: + array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} + - it diagnoses missing arguments: | + expect (Array.unshift ()). + to_error "bad argument #1 to 'unshift' (Array expected, got no value)" + expect (Array.unshift (array)). + to_error "bad argument #2 to 'unshift' (any value expected, got no value)" + - it diagnoses wrong argument types: | + expect (Array.unshift (1234)). + to_error "bad argument #1 to 'unshift' (Array expected, got number)" + - it adds a single element to an empty array: + array = Array () + Array.unshift (array, 42) + expect (array[1]).to_be (array[-1]) + - it inserts an element into an array: + count = array.length + Array.unshift (array, 42) + expect (array[1]).to_be (42) + expect (array.length).to_be (count + 1) + Array.unshift (array, -273) + expect (array[1]).to_be (-273) + expect (array.length).to_be (count + 2) + - it shifts existing elements one position right: + unshiftme = array () + Array.unshift (unshiftme, 42) + for i = 1, array.length do + expect (unshiftme[i + 1]).to_be (array[i]) + end + - context when called as an object method: + - before: + array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} + - it diagnoses missing arguments: | + expect (array:unshift ()). + to_error "bad argument #2 to 'unshift' (any value expected, got no value)" + - it adds a single element to an empty array: + array = Array () + array:unshift (42) + expect (array[1]).to_be (array[-1]) + - it adds an element to an array: + count = array.length + array:unshift (42) + expect (array[1]).to_be (42) + expect (array.length).to_be (count + 1) + array:unshift (-273) + expect (array[1]).to_be (-273) + expect (array.length).to_be (count + 2) + - it returns unshifted value: + expect (array:unshift (42)).to_be (42) + expect (array:unshift (-273)).to_be (-273) + - it shifts existing elements one position right: + unshiftme = array () + unshiftme:unshift (42) + for i = 1, array.length do + expect (unshiftme[i + 1]).to_be (array[i]) + end diff --git a/specs/specs.mk b/specs/specs.mk index 3bf523e..eab229c 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -27,6 +27,7 @@ specl_SPECS = \ $(srcdir)/specs/alien_spec.yaml \ $(srcdir)/specs/array_spec.yaml \ $(srcdir)/specs/base_spec.yaml \ + $(srcdir)/specs/base_array_spec.yaml \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ $(srcdir)/specs/functional_spec.yaml \ From e063c09b5f15f72fc4ff78ff9424f6706ebb86fa Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 22 May 2014 14:28:49 +0700 Subject: [PATCH 168/703] alien: remove surplus std.alien module. * lib/std/array.lua: Factor away dependencies on std.alien. (__call): Instead of an _init call built for container.__call, we now use a custom __call metamethod that returns a base_array object that is optimized for Lua table buffers when the element type is not recognized (either because there is no alien module installed, or it does not support buffers of the given type), or else builds an alien.buffer optimised object and returns that. * lib/std/alien.lua, specs/alien_spec.yaml: Remove. * build-aux/config.ld.in (file): Remove lib/std/alien.lua. * local.mk (dist_luastd_DATA): Likewise. (dist_modules_DATA): Remove doc/modules/std.alien.html. * specs/specs.mk (specl_SPECS): Remove specs/alien_spec.yaml. Signed-off-by: Gary V. Vaughan --- build-aux/config.ld.in | 1 - lib/std/alien.lua | 217 -------------------------------------- lib/std/array.lua | 220 +++++++++++++++++---------------------- local.mk | 2 - specs/alien_spec.yaml | 229 ----------------------------------------- specs/array_spec.yaml | 84 +++++++++++---- specs/specs.mk | 1 - 7 files changed, 162 insertions(+), 592 deletions(-) delete mode 100644 lib/std/alien.lua delete mode 100644 specs/alien_spec.yaml diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index f0a9dc4..ce0b186 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -7,7 +7,6 @@ dir = "." file = { -- Modules "../lib/std.lua", - "../lib/std/alien.lua", "../lib/std/array.lua", "../lib/std/debug.lua", "../lib/std/functional.lua", diff --git a/lib/std/alien.lua b/lib/std/alien.lua deleted file mode 100644 index 7fd62c4..0000000 --- a/lib/std/alien.lua +++ /dev/null @@ -1,217 +0,0 @@ ---[[-- - A pure Lua implementation of bits of the alien API stdlib relies on. - -# Internal API - - This is very far from a full implementation, and serves barely sufficient - functionality to enable use of `std.array` objects that won't work with - the real alien module - either because it is not available, or because the - contiguous memory model of alien arrays won't work for complex types that - are supported by std.array. - - The documentation is here for completeness and to aid understanding, but - you almost certainly won't find a good use for this module. - - @module std.alien -]] - -local debug = require "std.debug" -local argscheck = debug.argscheck - -local typeof = type - - ------- --- A pointer into a std.alien.buffer object. --- This helps std.object.prototype recognise the fake pointers generated --- by std.alien.array.buffer:topointer (). --- @table std.alien.pointer --- @tfield std.alien.buffer buffer std.alien.array objects contain one --- @int index index into `buffer` --- @see std.alien.buffer:topointer - -local pointer_mt = { - _type = "std.alien.pointer", -} - - -local buffer_methods = { - --- Return a table containing the index and a buffer reference. - -- @function std.alien.buffer:topointer - -- @int index reference to an element from buffer - -- @see std.alien.pointer - topointer = function (self, index) - return setmetatable ({ buffer = self, index = index }, pointer_mt) - end, -} - - -local buffer_mt = { - _type = "std.alien.array.buffer", - __index = buffer_methods, -} - - -local array_methods = { - --- Change the number of elements available in an array. - -- @function std.alien.array:realloc - -- @int count the minimum number of elements to make available - -- @see std.alien.array - realloc = function (self, count) - self.length = count - end, -} - -local array_mt = { - _type = "std.alien.array", - - --- Fetch the `index`th element, or fallback to a method name. - -- If `index` is a number, and it is not between 1 and the length - -- of this array, throw an "array access out of bounds" error. - -- @function array.__index - -- @tparam std.alien.array self an object from std.alien.array () - -- @int index element index - -- @return the `index`th element of `self` - -- @local - __index = function (self, index) - if typeof (index) == "number" then - if index < 1 or index > self.length then - error "array access out of bounds" - end - return rawget (self.buffer, index) - end - return array_methods[index] - end, - - --- Fetch the number of elements available in this array. - -- Note that this metamethod is ignored in Lua<5.2. For compatibility - -- with earlier releases, use array.length instead of #array. - -- @function array.__len - -- @tparam std.alien.array self an object fro std.alien.array () - -- @treturn int the number of avaliable element slots - -- @local - __len = function (self) - return self.length - end, - - --- Set the `index`th element to `value`. - -- If `index` is a number, and it is not between 1 and the length - -- of this array, throw an "array access out of bounds" error. - -- @tparam std.alien.array self an object from std.alien.array () - -- @int index element index - -- @int value set the `index`th element to this - -- @return the `index`th element of `self` - -- @local - __newindex = function (self, index, value) - if typeof (index) == "number" then - if index < 1 or index > self.length then - error "array access out of bounds" - end - rawset (self.buffer, index, value) - else - rawset (self, index, value) - end - return self - end, -} - - ---- Return a new std.alien.array object. --- @string type for API compatibility with alien proper --- @tparam int|table init number of elements to allocate, or a table of values --- @treturn std.alien.array a new std.alien.array object --- @see std.alien.array -local function array (type, init) - argscheck ('array', {"string", {"number", "table"}}, {type, init}) - - local array = { - type = type, - buffer = {}, - size = 1, - length = init, - } - - if typeof (init) == "table" then - array.buffer = init - array.length = #init - end - array.buffer = setmetatable (array.buffer, buffer_mt) - - return setmetatable (array, array_mt) -end - - ---- Move a block of contiguous elements to a new position. --- Works with overlapping blocks, or between entirely different arrays. --- @tparam std.alien.pointer to destination for elements to copy --- @tparam std.alien.pointer from source of elements to be copied --- @int bytes number of elements to copy (this only works because we ensure --- std.alien.array.size is always 1) --- @see std.alien.buffer:topointer -local function memmove (to, from, bytes) - argscheck ("memmove", {"std.alien.pointer", "std.alien.pointer", "number"}, - {to, from, bytes}) - - local tobuf, frombuf = to.buffer, from.buffer - local to, from = to.index, from.index - if tobuf == frombuf and to > from then - for i = bytes - 1, 0, -1 do - tobuf[to + i] = frombuf[from + i] - end - else - for i = 0, bytes - 1 do - tobuf[to + i] = frombuf[from + i] - end - end -end - - ---- Set a new value for a contiguos block of elements. --- There's no concept of bytes or object size in `std.alien.array`s, so --- in practice, the use of this function is limited to zeroing out new --- elements. --- @tparam std.alien.pointer pointer an object returned by `topointer` --- @param value the new value to set elements to --- @int bytes numebr of elements to set (this only works because we ensure --- std.alien.array.size is always 1) --- @see std.alien.buffer:topointer -local function memset (pointer, value, bytes) - argscheck ("memset", {"std.alien.pointer", "any", "number"}, - {pointer, value, bytes}) - - local buffer, from, to = pointer.buffer, pointer.index, pointer.index + bytes -1 - for i = from, to do - buffer[i] = value - end -end - - ---- @export -return { - array = array, - memmove = memmove, - memset = memset, -} - --- If we put these near the code they document, LDoc crashes! :-/ - ------- --- An array of homogenous elements. --- Instantiate one of these by calling `array`. --- @table std.alien.array --- @string type nominal name of types of elements stored in this array --- @tparam std.alien.buffer buffer the elements of this array --- @int size the size of a single element, always 1 in this implementation --- @int length the number of elements in this array --- @see std.alien.buffer --- @see array --- @see std.alien.array:realloc - - ------- --- The element buffer of a `std.alien.array`. --- In this implementation, a buffer is just the array part of a Lua table. --- @table std.alien.buffer --- @see std.alien.array --- @see memmove --- @see memset diff --git a/lib/std/array.lua b/lib/std/array.lua index aa59aa8..46d5536 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -7,9 +7,9 @@ Create a new Array with: > Array = require "std.array" - > array = Array ("int", 0xdead, 0xbeef, 0xfeed) - > =array[1], array[2], array[3], array[-1] - 57005 48879 65261 65261 + > array = Array ("int", {0xdead, 0xbeef, 0xfeed}) + > =array[1], array[2], array[3], array[-3], array[-4] + 57005 48879 65261 57005 nil All the indices passed to methods use 1-based counting. @@ -36,29 +36,15 @@ local argcheck, argscheck = debug.argcheck, debug.argscheck local Object = require "std.object" local prototype = Object.prototype -local alien_type = {} +local BaseArray = require "std.base_array" local have_alien, alien = pcall (require, "alien") +local buffer, memmove, memset if have_alien then - - -- Element types to be managed by alien: - for e in require "std.base".elems { - "byte", "char", "short", "ushort", "int", "uint", "long", "ulong", - "ptrdiff_t", "size_t", "float", "double", "pointer", - "ref char", "ref int", "ref uint", "ref double", - "longlong", "ulonglong"} - do - alien_type[e] = true - end - + buffer, memmove, memset = alien.buffer, alien.memmove, alien.memset else - - -- Fallback to Lua implementation. - alien = require "std.alien" - + buffer = function () return {} end end -local calloc, memmove, memset, sizeof = - alien.array, alien.memmove, alien.memset, alien.sizeof local typeof = type @@ -66,51 +52,21 @@ local element_chunk_size = 16 --- Convert an array element index into a pointer. --- @tparam alien.array array an array +-- @tparam std.array self an array -- @int i[opt=1] an index into array -- @treturn alien.buffer.pointer suitable for memmove or memset -local function topointer (array, i) +local function topointer (self, i) i = i or 1 - return array.buffer:topointer ((i - 1) * array.size + 1) + return self.buffer:topointer ((i - 1) * self.size + 1) end ---- Fast zeroing of a contiguous block of array elements. --- @tparam alien.array array an array +--- Fast zeroing of a contiguous block of array elements for `alien.buffer`s. +-- @tparam std.array self an array -- @int from index of first element to zero out -- @int n number of elements to zero out --- @treturn alien.array array -local function setzero (array, from, n) - if n > 0 then - if alien_type[array.type] then - memset (topointer (array, from), 0, n * array.size) - else - for i = from, from + n - 1 do - array.buffer[i] = 0 - end - end - end - return array -end - - ---- Clone the elements of an array. --- @param self object with in progress clone --- @string type element type --- @int required number of elements required in clone --- @treturn alien.array a clone of `self.array` -local function clone (self, type, required) - local parray, pused = self.array, self.length - local a = calloc (type, required) - - if alien_type[type] and sizeof (type) == sizeof (parray.type) then - local bytes = math.min (required, pused) * parray.size - memmove (topointer (a), topointer (parray), bytes) - else - local a, b = a.buffer, parray.buffer - for i = 1, math.min (required, pused) do a[i] = b[i] end - end - return setzero (a, pused + 1, required - pused) +local function setzero (self, from, n) + if n > 0 then memset (topointer (self, from), 0, n * self.size) end end @@ -123,8 +79,7 @@ local _functions = { local used = self.length if used > 0 then - local a = self.array - local elem = a[used] + local elem = self[used] self:realloc (used - 1) return elem end @@ -139,9 +94,9 @@ local _functions = { push = function (self, elem) argscheck ("push", {"Array", "number"}, {self, elem}) - local a, used = self.array, self.length + 1 + local used = self.length + 1 self:realloc (used) - a[used] = elem + self[used] = elem return elem end, @@ -153,15 +108,16 @@ local _functions = { realloc = function (self, n) argscheck ("realloc", {"Array", "number"}, {self, n}) - local a, used = self.array, self.length - if n > a.length or n < a.length / 2 then - a:realloc (n + element_chunk_size) + if n > self.allocated or n < self.allocated / 2 then + self.allocated = n + element_chunk_size + self.buffer:realloc (self.allocated * self.size) end -- Zero padding for uninitialised elements. - setzero (a, used + 1, n - used) - + local used = self.length self.length = n + setzero (self, used + 1, n - used) + return self end, @@ -180,9 +136,9 @@ local _functions = { if from < 0 then from = from + used + 1 end assert (from > 0 and from <= used) - local a, i = self.array, from + n - 1 + local i = from + n - 1 while i >= from do - a[i] = v + self[i] = v i = i - 1 end return self @@ -198,17 +154,9 @@ local _functions = { local n = self.length - 1 if n >= 0 then - local a = self.array - local elem - if alien_type[a.type] then - elem = a[1] - memmove (topointer (a, 1), topointer (a, 2), n * a.size) - self:realloc (n) - else - elem = table.remove (a.buffer, 1) - self.length = n - a.length = n - end + local elem = self[1] + memmove (topointer (self), topointer (self, 2), n * self.size) + self:realloc (n) return elem end return nil @@ -222,21 +170,24 @@ local _functions = { unshift = function (self, elem) argscheck ("unshift", {"Array", "number"}, {self, elem}) - local a, n = self.array, self.length - if alien_type[a.type] then - self:realloc (n + 1) - memmove (topointer (a, 2), topointer (a, 1), n * a.size) - a[1] = elem - else - table.insert (a.buffer, 1, elem) - self.length = n + 1 - a.length = n + 1 - end + local n = self.length + self:realloc (n + 1) + memmove (topointer (self, 2), topointer (self), n * self.size) + self[1] = elem return elem end, } +--- Number of bytes needed in an alien.buffer for each `type` element. +-- @string type name of an element type +-- @treturn int bytes per `type`, or 0 if alien.buffer cannot store `type`s +local function sizeof (type) + local ok, size = pcall ((alien or {}).sizeof, type) + return ok and size or 0 +end + + ------ -- An efficient array of homogenous objects. -- @table Array @@ -247,8 +198,11 @@ local Array = Object { -- Prototype initial values. - length = 0, - array = calloc ("int", {0}), + allocated = 1, + buffer = buffer (sizeof "int"), + length = 0, + size = sizeof "int", + type = "int", -- Module functions. @@ -261,13 +215,13 @@ local Array = Object { -- `alien.array` will use the fast `alien.array` managed memory buffer for -- Array contents; otherwise, a much slower Lua emulation is used. -- @function __call - + -- @string type element type name -- @tparam[opt] int|table init initial size or list of initial elements -- @treturn Array a new Array object - _init = function (self, type, init) + __call = function (self, type, init) if init ~= nil then -- When called with 2 arguments: - argcheck ("Array", 1, {"string"}, type) + argcheck ("Array", 1, "string", type) argcheck ("Array", 2, {"number", "table"}, init) elseif type ~= nil then -- When called with 1 argument: @@ -277,29 +231,50 @@ local Array = Object { -- Non-string argument 1 is really an init argument. if typeof (type) ~= "string" then type, init = nil, type end - -- New array type is copied from prototype if not specified. - local parray = self.array - if type == nil then type = parray.type end - - local a - if init == nil then - -- 1. A clone of prototype array. - a = clone (self, type, parray.length) - - elseif typeof (init) == "number" then - -- 2. Clone a number of elements from the prototype, padding with - -- zeros if we have more elements than the prototype. - a = clone (self, type, init) - self.length = init - - elseif typeof (init) == "table" then - -- 3. With an initialisation table, ignore prototype elements. - a = calloc (type, init) - self.length = #init + type = type or self.type + init = init or self.length + + -- If type cannot be managed by an alien.buffer, revert to a table + -- based BaseArray object instead. + local size = sizeof (type) + if size == 0 then + return BaseArray (init) end - self.array = a - return self + -- This will become the cloned Array object. + local obj = {} + + for k, v in pairs (self) do + if typeof (v) ~= "table" or v._type ~= "modulefunction" then + obj[k] = v + end + end + obj.size = size + obj.type = type + + if typeof (init) == "table" then + obj.length = #init + obj.allocated = #init + obj.buffer = buffer (size * #init) + for i = 1, #init do + obj.buffer:set ((i - 1) * size + 1, init[i], type) + end + else + obj.length = init + obj.allocated = math.max (init or 0, 1) + obj.buffer = buffer (size * obj.allocated) + + if size == self.size then + local bytes = math.min (init, self.length) * size + memmove (obj.buffer:topointer (), self.buffer:topointer (), bytes) + else + local a, b = obj.buffer, self.buffer + for i = 1, math.min (init, self.length) do a[i] = b[i] end + end + setzero (obj, self.length + 1, init - self.length) + end + + return setmetatable (obj, getmetatable (self)) end, @@ -322,7 +297,7 @@ local Array = Object { if typeof (n) == "number" then if n < 0 then n = n + self.length + 1 end if n > 0 and n <= self.length then - return self.array[n] + return self.buffer:get ((n - 1) * self.size + 1, self.type) end else return _functions[n] @@ -339,12 +314,12 @@ local Array = Object { argscheck ("__newindex", {"Array", "number", "number"}, {self, n, elem}) if typeof (n) == "number" then - local a, used = self.array, self.length + local used = self.length if n == 0 or math.abs (n) > used then - return a[0] -- guaranteed to be out of bounds + error ("array access " .. n .. " out of bounds: 0 < n <= " .. tostring (self.length), 2) end if n < 0 then n = n + used + 1 end - a[n] = elem + self.buffer:set ((n - 1) * self.size + 1, elem, self.type) else rawset (self, n, elem) end @@ -358,12 +333,11 @@ local Array = Object { __tostring = function (self) argscheck ("__tostring", {"Array"}, {self}) - local a = self.array local t = {} for i = 1, self.length do - t[#t + 1] = tostring (a[i]) + t[#t + 1] = tostring (self[i]) end - t = { '"' .. a.type .. '"', "{" .. table.concat (t, ", ") .. "}" } + t = { '"' .. self.type .. '"', "{" .. table.concat (t, ", ") .. "}" } return prototype (self) .. " (" .. table.concat (t, ", ") .. ")" end, } diff --git a/local.mk b/local.mk index d86d465..6ce9418 100644 --- a/local.mk +++ b/local.mk @@ -62,7 +62,6 @@ dist_lua_DATA += \ luastddir = $(luadir)/std dist_luastd_DATA = \ - lib/std/alien.lua \ lib/std/array.lua \ lib/std/base.lua \ lib/std/base_array.lua \ @@ -141,7 +140,6 @@ dist_classes_DATA += \ dist_modules_DATA += \ $(srcdir)/doc/modules/std.html \ - $(srcdir)/doc/modules/std.alien.html \ $(srcdir)/doc/modules/std.debug.html \ $(srcdir)/doc/modules/std.functional.html \ $(srcdir)/doc/modules/std.io.html \ diff --git a/specs/alien_spec.yaml b/specs/alien_spec.yaml deleted file mode 100644 index 95edaf2..0000000 --- a/specs/alien_spec.yaml +++ /dev/null @@ -1,229 +0,0 @@ -before: | - Object = require "std.object" - prototype = Object.prototype - -## ================================================================== ## -## FIXME: When anchors and aliases work, the alien and std.alien ## -## specifications should use an alias to prevent repetition. ## -## In the mean time, please be careful to make changes twice ## -## (once in each section) to ensure the std.alien API behaves ## -## identically to the real alien module! ## -## ================================================================== ## - -specify alien: -- before: | - -- Don't crash for a lack of an installed alien module - have_alien, alien = pcall (require, "alien") - if not have_alien then - alien = require "std.alien" - end -- describe array: - - before: - fn = alien.array - - it diagnoses missing arguments: - expect (fn ()).to_error () - expect (fn "int").to_error () - - when initialised with a length: - - before: | - a = fn ("char", 4) - for i = 1, a.length do - a[i] = i * i - end - function set (i, v) a[i] = v end - - it instantiates a new array object: - expect (prototype (a)). - to_be.any_of {"table", "std.alien.array"} - - it knows its own length: - expect (a.length).to_be (4) - - it diagnoses out of bounds access: - expect (a[-1]).to_error "array access out of bounds" - expect (a[0]).to_error "array access out of bounds" - expect (a[a.length + 1]).to_error "array access out of bounds" - expect (a[a.length * 2]).to_error "array access out of bounds" - - it reports value of elements by index: - expect (a[1]).to_be (1) - expect (a[2]).to_be (4) - expect (a[3]).to_be (9) - expect (a[4]).to_be (16) - - it diagnoses out of bounds assignment: - expect (set (-1, 42)).to_error "array access out of bounds" - expect (set (0, 42)).to_error "array access out of bounds" - expect (set (a.length + 1, 42)).to_error "array access out of bounds" - expect (set (a.length * 2, 42)).to_error "array access out of bounds" - - it allows setting of element values by index: - expect (set (1, 42)).not_to_error () - expect (a[1]).to_be (42) - - it knows the size of its elements: - expect (a.size).to_be (1) - - it knows the type of its elements: - expect (a.type).to_be "char" - - when initialised with elements: - - before: | - a = fn ("char", {1, 4, 9, 16}) - function set (i, v) a[i] = v end - - it instatiates a new array object: - expect (prototype (a)). - to_be.any_of {"table", "std.alien.array"} - - it knows its own length: - expect (a.length).to_be (4) - - it diagnoses out of bounds access: - expect (a[-1]).to_error "array access out of bounds" - expect (a[0]).to_error "array access out of bounds" - expect (a[a.length + 1]).to_error "array access out of bounds" - expect (a[a.length * 2]).to_error "array access out of bounds" - - it reports value of elements by index: - expect (a[1]).to_be (1) - expect (a[2]).to_be (4) - expect (a[3]).to_be (9) - expect (a[4]).to_be (16) - - it diagnoses out of bounds assignment: - expect (set (-1, 42)).to_error "array access out of bounds" - expect (set (0, 42)).to_error "array access out of bounds" - expect (set (a.length + 1, 42)).to_error "array access out of bounds" - expect (set (a.length * 2, 42)).to_error "array access out of bounds" - - it allows setting of element values by index: - expect (set (1, 42)).not_to_error () - expect (a[1]).to_be (42) - - it knows the size of its elements: - expect (a.size).to_be (1) - - it knows the type of its elements: - expect (a.type).to_be "char" - -- describe memmove: - - before: - fn = alien.memmove - a = alien.array ("int", 8) - b = alien.array ("int", {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}) - - it copies elements from one array to another: - fn (a.buffer:topointer (1), b.buffer:topointer (2 * b.size + 1), 8 * a.size) - for i = 1, a.length do - expect (a[i]).to_be (b[i + 2]) - end - - it shifts elements of overlapping blocks to the left: - fn (b.buffer:topointer (1), b.buffer:topointer (2 * b.size + 1), 8 * a.size) - for i, v in ipairs {2, 3, 5, 8, 13, 21, 34, 55, 34, 55} do - expect (b[i]).to_be (v) - end - - it shifts elements of overlapping blocks to the right: - b[1] = 0 - fn (b.buffer:topointer (2 * b.size + 1), b.buffer:topointer (1), 8 * b.size) - for i, v in ipairs {0, 1, 0, 1, 2, 3, 5, 8, 13, 21 } do - expect (b[i]).to_be (v) - end - -- describe memset: - - before: - fn = alien.memset - - it sets a block of elements to zero: - a = alien.array ("int", {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}) - fn (a.buffer:topointer (2 * a.size + 1), 0, 6 * a.size) - for i, v in ipairs {1, 1, 0, 0, 0, 0, 0, 0, 34, 55} do - expect (a[i]).to_be (v) - end - -specify std.alien: -- before: - alien = require "std.alien" -- describe array: - - before: - fn = alien.array - - it diagnoses missing arguments: - expect (fn ()).to_error () - expect (fn "int").to_error () - - when initialised with a length: - - before: | - a = fn ("char", 4) - for i = 1, a.length do - a[i] = i * i - end - function set (i, v) a[i] = v end - - it instantiates a new array object: - expect (prototype (a)). - to_be.any_of {"table", "std.alien.array"} - - it knows its own length: - expect (a.length).to_be (4) - - it diagnoses out of bounds access: - expect (a[-1]).to_error "array access out of bounds" - expect (a[0]).to_error "array access out of bounds" - expect (a[a.length + 1]).to_error "array access out of bounds" - expect (a[a.length * 2]).to_error "array access out of bounds" - - it reports value of elements by index: - expect (a[1]).to_be (1) - expect (a[2]).to_be (4) - expect (a[3]).to_be (9) - expect (a[4]).to_be (16) - - it diagnoses out of bounds assignment: - expect (set (-1, 42)).to_error "array access out of bounds" - expect (set (0, 42)).to_error "array access out of bounds" - expect (set (a.length + 1, 42)).to_error "array access out of bounds" - expect (set (a.length * 2, 42)).to_error "array access out of bounds" - - it allows setting of element values by index: - expect (set (1, 42)).not_to_error () - expect (a[1]).to_be (42) - - it knows the size of its elements: - expect (a.size).to_be (1) - - it knows the type of its elements: - expect (a.type).to_be "char" - - when initialised with elements: - - before: | - a = fn ("char", {1, 4, 9, 16}) - function set (i, v) a[i] = v end - - it instatiates a new array object: - expect (prototype (a)). - to_be.any_of {"table", "std.alien.array"} - - it knows its own length: - expect (a.length).to_be (4) - - it diagnoses out of bounds access: - expect (a[-1]).to_error "array access out of bounds" - expect (a[0]).to_error "array access out of bounds" - expect (a[a.length + 1]).to_error "array access out of bounds" - expect (a[a.length * 2]).to_error "array access out of bounds" - - it reports value of elements by index: - expect (a[1]).to_be (1) - expect (a[2]).to_be (4) - expect (a[3]).to_be (9) - expect (a[4]).to_be (16) - - it diagnoses out of bounds assignment: - expect (set (-1, 42)).to_error "array access out of bounds" - expect (set (0, 42)).to_error "array access out of bounds" - expect (set (a.length + 1, 42)).to_error "array access out of bounds" - expect (set (a.length * 2, 42)).to_error "array access out of bounds" - - it allows setting of element values by index: - expect (set (1, 42)).not_to_error () - expect (a[1]).to_be (42) - - it knows the size of its elements: - expect (a.size).to_be (1) - - it knows the type of its elements: - expect (a.type).to_be "char" - -- describe memmove: - - before: - fn = alien.memmove - a = alien.array ("int", 8) - b = alien.array ("int", {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}) - - it copies elements from one array to another: - fn (a.buffer:topointer (1), b.buffer:topointer (2 * b.size + 1), 8 * a.size) - for i = 1, a.length do - expect (a[i]).to_be (b[i + 2]) - end - - it shifts elements of overlapping blocks to the left: - fn (b.buffer:topointer (1), b.buffer:topointer (2 * b.size + 1), 8 * a.size) - for i, v in ipairs {2, 3, 5, 8, 13, 21, 34, 55, 34, 55} do - expect (b[i]).to_be (v) - end - - it shifts elements of overlapping blocks to the right: - b[1] = 0 - fn (b.buffer:topointer (2 * b.size + 1), b.buffer:topointer (1), 8 * b.size) - for i, v in ipairs {0, 1, 0, 1, 2, 3, 5, 8, 13, 21 } do - expect (b[i]).to_be (v) - end - -- describe memset: - - before: - fn = alien.memset - - it sets a block of elements to zero: - a = alien.array ("int", {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}) - fn (a.buffer:topointer (2 * a.size + 1), 0, 6 * a.size) - for i, v in ipairs {1, 1, 0, 0, 0, 0, 0, 0, 34, 55} do - expect (a[i]).to_be (v) - end diff --git a/specs/array_spec.yaml b/specs/array_spec.yaml index cd61d56..99e703b 100644 --- a/specs/array_spec.yaml +++ b/specs/array_spec.yaml @@ -26,30 +26,30 @@ specify Array: - it constructs an empty array: array = Array () expect (array.length).to_be (0) - expect (array.array.type).to_be (Array.array.type) + expect (array.type).to_be (Array.type) - it constructs a sized array: array = Array (100) expect (array.length).to_be (100) - expect (array.array.type).to_be (Array.array.type) + expect (array.type).to_be (Array.type) - it sets uninitialised elements to zero: array = Array (100) for i = 1, 100 do expect (array[i]).to_be (0) end - expect (array.array.type).to_be (Array.array.type) + expect (array.type).to_be (Array.type) - it initialises values from a table: array = Array {1, 4, 9, 16, 25, 36, 49, 64, 81} expect (array.length).to_be (9) for i = 1, array.length do expect (array[i]).to_be (i * i) end - expect (array.array.type).to_be (Array.array.type) + expect (array.type).to_be (Array.type) - it contains values from prototype array: a = array () for i = 3, array.length do expect (a[i]).to_be (a[i - 1] + a[i - 2]) end - expect (a.array.type).to_be (array.array.type) + expect (a.type).to_be (array.type) - it truncates copied prototype values: c = math.floor (array.length / 2) a = array (c) @@ -57,7 +57,7 @@ specify Array: for i = 3, a.length do expect (a[i]).to_be (a[i - 1] + a[i - 2]) end - expect (a.array.type).to_be (array.array.type) + expect (a.type).to_be (array.type) - it zero pads copied prototype values: a = array (array.length * 2) expect (a.length).to_be (array.length * 2) @@ -67,35 +67,35 @@ specify Array: for i = array.length + 1, a.length do expect (a[i]).to_be (0) end - expect (a.array.type).to_be (array.array.type) + expect (a.type).to_be (array.type) - context with specified element type: - it constructs an empty array: array = Array "double" expect (array.length).to_be (0) - expect (array.array.type).to_be "double" + expect (array.type).to_be "double" - it constructs a sized array: array = Array ("double", 100) expect (array.length).to_be (100) - expect (array.array.type).to_be "double" + expect (array.type).to_be "double" - it sets uninitialised elements to zero: array = Array ("double", 100) for i = 1, 100 do expect (array[i]).to_be (0) end - expect (array.array.type).to_be "double" + expect (array.type).to_be "double" - it initialises values from a table: array = Array ("double", {1, 4, 9, 16, 25, 36, 49, 64, 81}) expect (array.length).to_be (9) for i = 1, array.length do expect (array[i]).to_be (i * i) end - expect (array.array.type).to_be "double" + expect (array.type).to_be "double" - it contains values from prototype array: a = array "double" for i = 3, array.length do expect (a[i]).to_be (a[i - 1] + a[i - 2]) end - expect (a.array.type).to_be "double" + expect (a.type).to_be "double" - it truncates copied prototype values: c = math.floor (array.length / 2) a = array ("double", c) @@ -103,7 +103,7 @@ specify Array: for i = 3, a.length do expect (a[i]).to_be (a[i - 1] + a[i - 2]) end - expect (a.array.type).to_be "double" + expect (a.type).to_be "double" - it zero pads copied prototype values: a = array ("double", array.length * 2) expect (a.length).to_be (array.length * 2) @@ -113,7 +113,57 @@ specify Array: for i = array.length + 1, a.length do expect (a[i]).to_be (0) end - expect (a.array.type).to_be "double" + expect (a.type).to_be "double" + - context with non-alien element type: + - before: + array = Array ("table", { + {v=1}, {v=1}, {v=2}, {v=3}, {v=5}, {v=8}, {v=13}, {v=21}, {v=34} + }) + - it constructs an empty array: + array = Array "table" + expect (array.length).to_be (0) + expect (array.type).to_be (nil) + - it constructs a sized array: + array = Array ("table", 100) + expect (array.length).to_be (100) + expect (array.type).to_be (nil) + - it sets uninitialised elements to zero: + array = Array ("table", 100) + for i = 1, 100 do + expect (array[i]).to_be (0) + end + expect (array.type).to_be (nil) + - it initialises values from a table: + array = Array ("table", {{v=1}, {v=4}, {v=9}, {v=16}, {v=25}}) + expect (array.length).to_be (5) + for i = 1, array.length do + expect (array[i].v).to_be (i * i) + end + expect (array.type).to_be (nil) + - it contains values from prototype array: + a = array () + for i = 3, array.length do + expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) + end + expect (a.type).to_be (nil) + - it truncates copied prototype values: + c = math.floor (array.length / 2) + a = array ("table", c) + expect (a.length).to_be (c) + for i = 3, a.length do + expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) + end + expect (a.type).to_be (nil) + - it zero pads copied prototype values: + a = array ("table", array.length * 2) + expect (a.length).to_be (array.length * 2) + for i = 3, array.length do + expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) + end + for i = array.length + 1, a.length do + expect (a[i]).to_be (0) + end + expect (a.type).to_be (nil) - describe __len: - it returns the number of elements stored: @@ -163,7 +213,7 @@ specify Array: for _, i in ipairs {array.length * -2, -1 - array.length, 0, array.length + 1, array.length * 2} do expect ((function () array[i] = i end) ()). - to_error "array access out of bounds" + to_error "out of bounds" end - describe __tostring: @@ -306,7 +356,6 @@ specify Array: array = Array (100) Array.realloc (array, 50) expect (array.length).to_be (50) - expect (array.array.length >= 50).to_be (true) - it truncates existing elements when reducing size: a = array (100) Array.realloc (a, 50) @@ -317,7 +366,6 @@ specify Array: array = Array (50) Array.realloc (array, 100) expect (array.length).to_be (100) - expect (array.array.length >= 100).to_be (true) - it does not perturb existing element values: a = array (50) Array.realloc (a, 100) @@ -340,7 +388,6 @@ specify Array: - it reduces the number of usable elements: array = Array (100):realloc (50) expect (array.length).to_be (50) - expect (array.array.length >= 50).to_be (true) - it truncates existing elements when reducing size: a = array (100):realloc (50) for i = 3, a.length do @@ -349,7 +396,6 @@ specify Array: - it increases the number of usable elements: array = Array (50):realloc (100) expect (array.length).to_be (100) - expect (array.array.length >= 100).to_be (true) - it does not perturb existing element values: a = array (50):realloc (100) for i = 3, 50 do diff --git a/specs/specs.mk b/specs/specs.mk index eab229c..66a5b72 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -24,7 +24,6 @@ SPECL_OPTS = --unicode ## affected. specl_SPECS = \ - $(srcdir)/specs/alien_spec.yaml \ $(srcdir)/specs/array_spec.yaml \ $(srcdir)/specs/base_spec.yaml \ $(srcdir)/specs/base_array_spec.yaml \ From 4746fe65335578ef1ba93fa8b92cbcf17cae9882 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Tue, 27 May 2014 13:05:52 +0100 Subject: [PATCH 169/703] functional.lua: add missing parameter name to docstring --- lib/std/functional.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index ce3e32b..5118c51 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -16,7 +16,7 @@ end --- Partially apply a function. -- @param f function to apply partially --- @tparam table {p1=a1, ..., pn=an} table of parameters to bind to given arguments +-- @tparam t table {p1=a1, ..., pn=an} table of parameters to bind to given arguments -- @return function with pi already bound local function bind (f, ...) local fix = {...} -- backwards compatibility with old API; DEPRECATED: remove in first release after 2015-04-21 From 80d726f92d4974c2a9b88a169b10afaf3c5dbb23 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 May 2014 18:16:28 +0700 Subject: [PATCH 170/703] refactor: move argscheck et.al. into std.base module. Unfortunately, debug.say is a very high level function that pulls in a lot of other modules, modules that we'd like to be able to add debug.argscheck to... since argscheck has almost no pre- requisites, move it into the base module where it can then be available everywhere else. * lib/std/container.lua (prototype): Move from here... * lib/std/base.lua (prototype): ...to here. * lib/std/debug.lua (concat, argerror, argcheck, argscheck): Move from here... * lib/std/base.lua (concat, argerror, argcheck, argscheck): ...to here. Be sure to disable argument checking if _DEBUG or _DEBUG.argcheck are false. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 136 ++++++++++++++++++++++++++++++++++++++++-- lib/std/container.lua | 7 ++- lib/std/debug.lua | 107 +++------------------------------ 3 files changed, 143 insertions(+), 107 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index b96663e..06544d1 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -1,6 +1,129 @@ ------ -- @module std.base +local typeof = type + +-- Doc-commented in container.lua +local function prototype (o) + return (getmetatable (o) or {})._type or type (o) +end + + +local init = require "std.debug_init" + +local _ARGCHECK = init._DEBUG +if type (init._DEBUG) == "table" then + _ARGCHECK = init._DEBUG.argcheck + if _ARGCHECK == nil then _ARGCHECK= true end +end + +local argcheck, argerror, argscheck + +if not _ARGCHECK then + + local function nop () end + + -- Turn off argument checking if _DEBUG is false, or a table containing + -- a false valued `argcheck` field. + + argcheck = nop + argscheck = nop + +else + + --- Concatenate a table of strings using ", " and " or " delimiters. + -- @tparam table alternatives a table of strings + -- @treturn string string of elements from alternatives delimited by ", " + -- and " or " + local function concat (alternatives) + local t, i = {}, 1 + while i < #alternatives do + t[i] = alternatives[i] + i = i + 1 + end + if #alternatives > 1 then + t[#t] = t[#t] .. " or " .. alternatives[#alternatives] + else + t = alternatives + end + return table.concat (t, ", ") + end + + + -- Doc-commented in debug.lua + function argcheck (name, i, expected, actual, level) + level = level or 2 + if prototype (expected) ~= "table" then expected = {expected} end + + -- Check actual has one of the types from expected + local ok, actualtype = false, prototype (actual) + for i, check in ipairs (expected) do + if check == "any" then + expected[i] = "any value" + if actual ~= nil then + ok = true + end + + elseif check == "#table" then + if actualtype == "table" and next (actual) then + ok = true + end + + elseif check == "list" then + if typeof (actual) == "table" and #actual > 0 then + ok = true + end + + elseif check == "object" then + if actualtype ~= "table" and typeof (actual) == "table" then + ok = true + end + + elseif check == actualtype then + ok = true + end + + if ok then break end + end + + if not ok then + if actualtype == "nil" then + actualtype = "no value" + elseif actualtype == "table" and next (actual) == nil then + actualtype = "empty table" + elseif actualtype == "List" and #actual == 0 then + actualtype = "empty List" + end + expected = concat (expected):gsub ("#table", "non-empty table") + argerror (name, i, expected .. " expected, got " .. actualtype, level + 1) + end + end + + + -- Doc-commented in debug.lua + function argscheck (name, expected, actual) + if typeof (expected) ~= "table" then expected = {expected} end + if typeof (actual) ~= "table" then actual = {actual} end + + for i, v in ipairs (expected) do + argcheck (name, i, expected[i], actual[i], 3) + end + end + +end + + +-- Doc-commented in debug.lua... +-- This function is not disabled by setting _DEBUG. +function argerror (name, i, extramsg, level) + level = level or 1 + local s = string.format ("bad argument #%d to '%s'", i, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) +end + --- Write a deprecation warning to stderr on first call. -- @func fn deprecated function @@ -66,10 +189,15 @@ end local M = { - deprecate = deprecate, - elems = elems, - leaves = leaves, - metamethod = metamethod, + argcheck = argcheck, + argerror = argerror, + argscheck = argscheck, + deprecate = deprecate, + elems = elems, + leaves = leaves, + metamethod = metamethod, + prototype = prototype, } + return M diff --git a/lib/std/container.lua b/lib/std/container.lua index c507d5e..4172720 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -58,6 +58,9 @@ ]] +local base = require "std.base" + + -- Instantiate a new object based on *proto*. -- -- This is equivalent to: @@ -154,9 +157,7 @@ end -- @tparam std.container o an container -- @treturn string type of the container -- @see std.object.prototype -local function prototype (o) - return (getmetatable (o) or {})._type or type (o) -end +local prototype = base.prototype --- Container prototype. diff --git a/lib/std/debug.lua b/lib/std/debug.lua index bbc0656..b953c50 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -22,15 +22,13 @@ @module std.debug ]] +local base = require "std.base" local init = require "std.debug_init" local io = require "std.io" local list = require "std.list" local Object = require "std.object" local string = require "std.string" -local prototype = Object.prototype -local typeof = type - --- Control std.debug function behaviour. -- To activate debugging set _DEBUG either to any true value @@ -42,25 +40,6 @@ local typeof = type -- @field level debugging level ---- Concatenate a table of strings using ", " and " or " delimiters. --- @tparam table alternatives a table of strings --- @treturn string string of elements from alternatives delimited by ", " --- and " or " -local function concat (alternatives) - local t, i = {}, 1 - while i < #alternatives do - t[i] = alternatives[i] - i = i + 1 - end - if #alternatives > 1 then - t[#t] = t[#t] .. " or " .. alternatives[#alternatives] - else - t = alternatives - end - return table.concat (t, ", ") -end - - --- Print a debugging message. -- @param n debugging level, defaults to 1 -- @param ... objects to print (as for print) @@ -124,18 +103,12 @@ end -- Equivalent to luaL_argerror in the Lua C API. This function does not -- return. The `level` argument behaves just like the core `error` -- function. +-- @function argerror -- @string name function to callout in error message -- @int i argument number -- @string[opt] extramsg additional text to append to message inside parentheses -- @int[opt=1] level call stack level to blame for the error -local function argerror (name, i, extramsg, level) - level = level or 1 - local s = string.format ("bad argument #%d to '%s'", i, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) -end +local argerror = base.argerror --- Check the type of an argument against expected types. @@ -154,72 +127,21 @@ end -- Normally, you should not need to use the `level` parameter, as the -- default is to blame the caller of the function using `argcheck` in -- error messages; which is almost certainly what you want. +-- @function argcheck -- @string name function to blame in error message -- @int i argument number to blame in error message -- @tparam table|string expected a list of acceptable argument types -- @param actual argument passed -- @int[opt=2] level call stack level to blame for the error -local function argcheck (name, i, expected, actual, level) - level = level or 2 - if prototype (expected) ~= "table" then expected = {expected} end - - -- Check actual has one of the types from expected - local ok, actualtype = false, prototype (actual) - for i, check in ipairs (expected) do - if check == "any" then - expected[i] = "any value" - if actual ~= nil then - ok = true - end - - elseif check == "#table" then - if actualtype == "table" and next (actual) then - ok = true - end - - elseif check == "list" then - if typeof (actual) == "table" and #actual > 0 then - ok = true - end - - elseif check == "object" then - if actualtype ~= "table" and typeof (actual) == "table" then - ok = true - end - - elseif check == actualtype then - ok = true - end - - if ok then break end - end - - if not ok then - if actualtype == "nil" then - actualtype = "no value" - elseif actualtype == "table" and next (actual) == nil then - actualtype = "empty table" - elseif actualtype == "List" and #actual == 0 then - actualtype = "empty List" - end - expected = concat (expected):gsub ("#table", "non-empty table") - argerror (name, i, expected .. " expected, got " .. actualtype, level + 1) - end -end +local argcheck = base.argcheck --- Check that all arguments match specified types. +-- @function argscheck -- @string name function to blame in error message -- @tparam table|string expected a list of lists of acceptable argument types -- @tparam table|any actual argument value, or table of argument values -local function argscheck (name, expected, actual) - if typeof (expected) ~= "table" then expected = {expected} end - if typeof (actual) ~= "table" then actual = {actual} end - - for i, v in ipairs (expected) do - argcheck (name, i, expected[i], actual[i], 3) - end -end +local argscheck = base.argscheck --- @export @@ -232,21 +154,6 @@ local M = { } --- Turn off argument checking if _DEBUG is false, or a table containing --- a false valued `argcheck` field. - -local _ARGCHECK = init._DEBUG -if type (init._DEBUG) == "table" then - _ARGCHECK = init._DEBUG.argcheck - if _ARGCHECK == nil then _ARGCHECK= true end -end - -if not _ARGCHECK then - M.argcheck = function () end - M.argscheck = function () end -end - - for k, v in pairs (debug) do M[k] = M[k] or v end From 1249c82c300b2959fdc07816283cb02190d857e2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 May 2014 18:23:30 +0700 Subject: [PATCH 171/703] refactor: don't pull in all of debug's dependencies for array. * lib/std/array.lua, lib/std/base_array.lua: Import argcheck and argscheck from std.base. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 4 ++-- lib/std/base_array.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index 46d5536..24b45c9 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -30,8 +30,8 @@ ]] -local debug = require "std.debug" -local argcheck, argscheck = debug.argcheck, debug.argscheck +local base = require "std.base" +local argcheck, argscheck = base.argcheck, base.argscheck local Object = require "std.object" local prototype = Object.prototype diff --git a/lib/std/base_array.lua b/lib/std/base_array.lua index f6cc411..61cf658 100644 --- a/lib/std/base_array.lua +++ b/lib/std/base_array.lua @@ -2,8 +2,8 @@ -- @module std.base_array -local debug = require "std.debug" -local argcheck, argscheck = debug.argcheck, debug.argscheck +local base = require "std.base" +local argcheck, argscheck = base.argcheck, base.argscheck local Object = require "std.object" local prototype = Object.prototype From 83999d36215cbaea194dc46a3080a96abe4d908c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 May 2014 21:29:10 +0700 Subject: [PATCH 172/703] base: use argscheck for api functions. * specs/base_spec.yaml (deprecate), specs/list_spec.yaml (elems): Specify bad argument behaviours. * specs/table_spec.yaml (metamethod): Add missing specifications, including bad argument behaviours. * lib/std/base.lua (deprecate, elems, metamethod): Use argscheck to implement specified behaviours. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 24 +++++++++++++++++++----- specs/base_spec.yaml | 9 ++++++--- specs/list_spec.yaml | 6 ++++++ specs/table_spec.yaml | 27 +++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 06544d1..170b3a9 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -126,13 +126,19 @@ end --- Write a deprecation warning to stderr on first call. --- @func fn deprecated function --- @string[opt] name function name for automatic warning message. +-- @func fn deprecated function +-- @string[opt] name function name for automatic warning message. -- @string[opt] warnmsg full specified warning message (overrides *name*) -- @return a function to show the warning on first call, and hand off to *fn* +-- @usage funcname = deprecate (function (...) ... end, "funcname") local function deprecate (fn, name, warnmsg) - assert (name or warnmsg, - "missing argument to 'deprecate', expecting 2 or 3 parameters") + argscheck ("std.base.deprecate", + {"function", {"string", "nil"}, {"string", "nil"}}, + {fn, name, warnmsg}) + if not (name or warnmsg) then + error ("missing argument to 'std.base.deprecate' (2 or 3 arguments expected)", 2) + end + warnmsg = warnmsg or (name .. " is deprecated, and will go away in a future release.") local warnp = true return function (...) @@ -149,6 +155,8 @@ end -- Doc-commented in list.lua... local function elems (l) + argcheck ("std.list.elems", 1, {"List", "table"}, l) + local n = 0 return function (l) n = n + 1 @@ -160,7 +168,11 @@ local function elems (l) end --- Iterator returning leaf nodes from nested tables. +--- Iterator returning leaf nodes from nested tables. +-- @tparam function it table iterator function +-- @tparam tree|table tr tree or tree-like table +-- @treturn function iterator function +-- @treturn tree|table the tree `tr` local function leaves (it, tr) local function visit (n) if type (n) == "table" then @@ -177,6 +189,8 @@ end -- Doc-commented in table.lua... local function metamethod (x, n) + argscheck ("std.table.metamethod", {{"object", "table"}, "string"}, {x, n}) + local _, m = pcall (function (x) return getmetatable (x)[n] end, diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index cae43d1..42c5f08 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -14,10 +14,13 @@ specify std.base: "('" .. table.concat (args or {}, "', '") .. "')" ) end - - it diagnoses missing arguments: - expect (deprecate ()).to_error "missing argument" + - it diagnoses missing arguments: | + expect (deprecate ()). + to_error "bad argument #1 to 'std.base.deprecate' (function expected, got no value)" + expect (deprecate (function () end)). + to_error "missing argument to 'std.base.deprecate' (2 or 3 arguments expected)" - it returns a function: - f = deprecate (base.clone_rename, "clone_rename") + f = deprecate (function () end, "clone_rename") expect (type (f)).to_be "function" - context with deprecated function: - it executes the deprecated function: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 3ebf355..d16b473 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -182,6 +182,12 @@ specify std.list: - describe elems: + - it diagnoses missing arguments: | + expect (List.elems ()). + to_error "bad argument #1 to 'std.list.elems' (List or table expected, got no value)" + - it diagnoses wrong argument types: | + expect (List.elems (false)). + to_error "bad argument #1 to 'std.list.elems' (List or table expected, got boolean)" - it is an iterator over list members: t = {} for e in List.elems (l) do table.insert (t, e) end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index e0f72cf..a179bcb 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -290,6 +290,33 @@ specify std.table: expect (f ("foo", "bar")).to_error ("table expected") +- describe metamethod: + - before: + f = M.metamethod + Object = require "std.object" + objmethod = function () end + obj = Object { + _type = "DerivedObject", + _method = objmethod, + } + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.metamethod' (object or table expected, got no value)" + expect (f (obj)). + to_error "bad argument #2 to 'std.table.metamethod' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.metamethod' (object or table expected, got boolean)" + expect (f (obj, false)). + to_error "bad argument #2 to 'std.table.metamethod' (string expected, got boolean)" + - it returns nil for missing metamethods: + expect (f (obj, "not a method on obj")).to_be (nil) + - it returns nil for non-function metatable entries: + expect (f (obj, "_type")).to_be (nil) + - it returns a method from the metatable: + expect (f (obj, "_method")).to_be (objmethod) + + - describe monkey_patch: - before: f = M.monkey_patch From 7e245a071485c67f71798a4e8b599e40a9a5b66a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 May 2014 22:23:19 +0700 Subject: [PATCH 173/703] refactor: omit spurious parentheses around require result dereferences. * lib/std/container.lua, lib/std/object.lua, lib/std/set.lua, lib/std/string.lua, lib/std/tree.lua, specs/container_spec.yaml, specs/list_spec.yaml, specs/object_spec.yaml, specs/set_spec.yaml, specs/spec_helper.lua.in: Omit spurious parentheses around require result dereferences. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 8 ++++---- lib/std/object.lua | 2 +- lib/std/set.lua | 2 +- lib/std/string.lua | 2 +- lib/std/tree.lua | 2 +- specs/container_spec.yaml | 6 +++--- specs/list_spec.yaml | 4 ++-- specs/object_spec.yaml | 4 ++-- specs/set_spec.yaml | 4 ++-- specs/spec_helper.lua.in | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 4172720..86cdb86 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -65,7 +65,7 @@ local base = require "std.base" -- -- This is equivalent to: -- --- base.merge (base.clone (proto), t or {}) +-- table.merge (table.clone (proto), t or {}) -- -- But, not typechecking arguments or checking for metatables, is -- slightly faster. @@ -152,10 +152,10 @@ local function mapfields (obj, src, map) end --- Type of this container. +-- Type of an object. -- @static --- @tparam std.container o an container --- @treturn string type of the container +-- @tparam std.object obj an object +-- @treturn string type of the object -- @see std.object.prototype local prototype = base.prototype diff --git a/lib/std/object.lua b/lib/std/object.lua index 34eef40..32f57fe 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -65,7 +65,7 @@ local Container = require "std.container" -local metamethod = (require "std.base").metamethod +local metamethod = require "std.base".metamethod --- Root object. diff --git a/lib/std/set.lua b/lib/std/set.lua index e7e6802..ec50f65 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -13,7 +13,7 @@ local base = require "std.base" local Container = require "std.container" -local prototype = (require "std.object").prototype +local prototype = require "std.object".prototype local Set -- forward declaration diff --git a/lib/std/string.lua b/lib/std/string.lua index 7855ea4..9b8f5c8 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -35,7 +35,7 @@ local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" -local metamethod = (require "std.base").metamethod +local metamethod = require "std.base".metamethod local _format = string.format local _tostring = _G.tostring diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 2776a78..be6be26 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -16,7 +16,7 @@ local Container = require "std.container" local list = require "std.list" local func = require "std.functional" -local prototype = (require "std.object").prototype +local prototype = require "std.object".prototype local Tree -- forward declaration diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 9e8d85c..faf7804 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -1,6 +1,6 @@ before: Container = require "std.container" - prototype = (require "std.object").prototype + prototype = require "std.object".prototype specify std.container: - context when required: @@ -37,7 +37,7 @@ specify std.container: expect (getmetatable (things)._baz).to_be "quux" - context with module functions: - before: - fold = (require "std.functional").fold + fold = require "std.functional".fold functions = { count = function (bag) return fold (function (r, k) return r + bag[k] end, 0, pairs, bag) @@ -103,7 +103,7 @@ specify std.container: - describe tablification: - before: - totable = (require "std.table").totable + totable = require "std.table".totable Derived = Container {_type = "Derived", "one", "two", three = true} - it returns a table: expect (prototype (totable (Derived))).to_be "table" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index d16b473..4f46216 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -255,7 +255,7 @@ specify std.list: - describe foldl: - before: - op = (require "std.functional").op + op = require "std.functional".op l = List {1, 10, 100} - context when called as a list object method: @@ -268,7 +268,7 @@ specify std.list: - describe foldr: - before: - op = (require "std.functional").op + op = require "std.functional".op l = List {1, 10, 100} - context when called as a list object method: diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index cc12538..964318f 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -75,7 +75,7 @@ specify std.object: - describe instantiation from a prototype: - before: - totable = (require "std.table").totable + totable = require "std.table".totable - context when _init is nil: - before: @@ -267,7 +267,7 @@ specify std.object: - describe __totable: - before: - totable = (require "std.table").totable + totable = require "std.table".totable Derived = Object {_type = "Derived", "one", "two", three = true} - it returns a table: diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index e401aef..817abff 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -1,7 +1,7 @@ before: Set = require "std.set" - prototype = (require "std.object").prototype - totable = (require "std.table").totable + prototype = require "std.object".prototype + totable = require "std.table".totable s = Set {"foo", "bar", "bar"} specify std.set: diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 99a0fae..57b637c 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -124,7 +124,7 @@ end -- Not local, so that it is available in spec examples. -totable = (require "std.table").totable +totable = require "std.table".totable -- Stub inprocess.capture if necessary; new in Specl 12. From 24a236fa7783694b42618c86541cfb926174be3c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 27 May 2014 11:38:55 +0700 Subject: [PATCH 174/703] functional: use argscheck for api functions. * specs/functional_spec.yaml (metamethod): Remove. This method has moved to std.table. (bind, case, collect, compose, curry, eval, filter, fold, map) (memoize): Specify behaviour with missing or wrong type arguments. * lib/std/functional.yaml (bind, case, collect, compose, curry) (eval, filter, fold, map, memoize): Add argscheck calls to validate arguments when _DEBUG or _DEBUG.argcheck are not false. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 28 +++++++++++++ specs/functional_spec.yaml | 85 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 5118c51..bbb31d7 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -3,6 +3,9 @@ @module std.functional ]] +local base = require "std.base" +local argcheck, argscheck = base.argcheck, base.argscheck + local functional -- forward declaration @@ -19,6 +22,8 @@ end -- @tparam t table {p1=a1, ..., pn=an} table of parameters to bind to given arguments -- @return function with pi already bound local function bind (f, ...) + argscheck ("std.functional.bind", "function", f) + local fix = {...} -- backwards compatibility with old API; DEPRECATED: remove in first release after 2015-04-21 if type (fix[1]) == "table" and fix[2] == nil then fix = fix[1] @@ -48,6 +53,8 @@ end -- @tparam table branches map possible matches to functions -- @return the return value from function with a matching key, or nil. local function case (with, branches) + argcheck ("std.functional.case", 2, "#table", branches) + local fn = branches[with] or branches[1] if fn then return fn (with) end end @@ -58,6 +65,8 @@ end -- @param n number of arguments -- @return curried version of f local function curry (f, n) + argscheck ("std.functional.curry", {"function", "number"}, {f, n}) + if n <= 1 then return f else @@ -79,6 +88,13 @@ end -- can be read from top to bottom. local function compose (...) local arg = {...} + if #arg < 1 then + argcheck ("std.functional.compose", 1, "function", nil) + end + for i in ipairs (arg) do + argcheck ("std.functional.compose", i, "function", arg[i]) + end + local fns, n = arg, #arg return function (...) local arg = {...} @@ -106,6 +122,9 @@ end -- @param normalize[opt] function to normalize arguments -- @return memoized function local function memoize (fn, normalize) + argscheck ("std.functional.memoize", {"function", {"function", "nil"}}, + {fn, normalize}) + if normalize == nil then -- Call require here, to avoid pulling in all of 'std.string' -- even when memoize is never called. @@ -131,6 +150,7 @@ end -- @param s string -- @return value of string local function eval (s) + argscheck ("std.functional.eval", "string", s) return loadstring ("return " .. s)() end @@ -139,6 +159,8 @@ end -- @param i iterator -- @return results of running the iterator on its arguments local function collect (i, ...) + argcheck ("std.functional.collect", 1, "function", i) + local t = {} for e in i (...) do t[#t + 1] = e @@ -152,6 +174,8 @@ end -- @param i iterator -- @return result table local function map (f, i, ...) + argscheck ("std.functional.map", {"function", "function"}, {f, i}) + local t = {} for e in i (...) do local r = f (e) @@ -168,6 +192,8 @@ end -- @param i iterator -- @return result table containing elements e for which p (e) local function filter (p, i, ...) + argscheck ("std.functional.filter", {"function", "function"}, {p, i}) + local t = {} for e in i (...) do if p (e) then @@ -184,6 +210,8 @@ end -- @param i iterator -- @return result local function fold (f, d, i, ...) + argscheck ("std.functional.fold", {"function", "any", "function"}, {f, d, i}) + local r = d for e in i (...) do r = f (r, e) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index a3f75dd..decc657 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -18,6 +18,12 @@ specify std.functional: - describe bind: + - it diagnoses missing arguments: | + expect (M.bind ()). + to_error "bad argument #1 to 'std.functional.bind' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.bind (false)). + to_error "bad argument #1 to 'std.functional.bind' (function expected, got boolean)" - it does not affect normal operation if no arguments are bound: expect (M.bind (math.min, {}) (2, 3, 4)). to_equal (2) @@ -35,6 +41,12 @@ specify std.functional: no = function () return false end default = function (s) return s end branches = { yes = yes, no = no, default } + - it diagnoses missing arguments: | + expect (M.case (nil)). + to_error "bad argument #2 to 'std.functional.case' (non-empty table expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.case ("no", false)). + to_error "bad argument #2 to 'std.functional.case' (non-empty table expected, got boolean)" - it matches against branch keys: expect (M.case ("yes", branches)).to_be (true) expect (M.case ("no", branches)).to_be (false) @@ -54,33 +66,100 @@ specify std.functional: - describe collect: + - it diagnoses missing arguments: | + expect (M.collect ()). + to_error "bad argument #1 to 'std.functional.collect' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.collect (false)). + to_error "bad argument #1 to 'std.functional.collect' (function expected, got boolean)" - describe compose: + - it diagnoses missing arguments: | + expect (M.compose ()). + to_error "bad argument #1 to 'std.functional.compose' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.compose (false)). + to_error "bad argument #1 to 'std.functional.compose' (function expected, got boolean)" + expect (M.compose (M.id, false)). + to_error "bad argument #2 to 'std.functional.compose' (function expected, got boolean)" - it composes functions in the correct order: expect (M.compose (math.sin, math.cos) (1)). to_equal (math.cos (math.sin (1))) - describe curry: + - it diagnoses missing arguments: | + expect (M.curry ()). + to_error "bad argument #1 to 'std.functional.curry' (function expected, got no value)" + expect (M.curry (M.id)). + to_error "bad argument #2 to 'std.functional.curry' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.curry (false)). + to_error "bad argument #1 to 'std.functional.curry' (function expected, got boolean)" + expect (M.curry (M.id, false)). + to_error "bad argument #2 to 'std.functional.curry' (number expected, got boolean)" - describe eval: + - it diagnoses missing arguments: | + expect (M.eval ()). + to_error "bad argument #1 to 'std.functional.eval' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.eval (false)). + to_error "bad argument #1 to 'std.functional.eval' (string expected, got boolean)" - describe filter: + - it diagnoses missing arguments: | + expect (M.filter ()). + to_error "bad argument #1 to 'std.functional.filter' (function expected, got no value)" + expect (M.filter (M.id)). + to_error "bad argument #2 to 'std.functional.filter' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.filter (false)). + to_error "bad argument #1 to 'std.functional.filter' (function expected, got boolean)" + expect (M.filter (M.id, false)). + to_error "bad argument #2 to 'std.functional.filter' (function expected, got boolean)" - describe fold: + - it diagnoses missing arguments: | + expect (M.fold ()). + to_error "bad argument #1 to 'std.functional.fold' (function expected, got no value)" + expect (M.fold (M.id)). + to_error "bad argument #2 to 'std.functional.fold' (any value expected, got no value)" + expect (M.fold (M.id, 1)). + to_error "bad argument #3 to 'std.functional.fold' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.fold (false)). + to_error "bad argument #1 to 'std.functional.fold' (function expected, got boolean)" + expect (M.fold (M.id, 1, false)). + to_error "bad argument #3 to 'std.functional.fold' (function expected, got boolean)" - describe id: - describe map: + - it diagnoses missing arguments: | + expect (M.map ()). + to_error "bad argument #1 to 'std.functional.map' (function expected, got no value)" + expect (M.map (M.id)). + to_error "bad argument #2 to 'std.functional.map' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.map (false)). + to_error "bad argument #1 to 'std.functional.map' (function expected, got boolean)" + expect (M.map (M.id, false)). + to_error "bad argument #2 to 'std.functional.map' (function expected, got boolean)" - describe memoize: - - -- describe metamethod: + - it diagnoses missing arguments: | + expect (M.memoize ()). + to_error "bad argument #1 to 'std.functional.memoize' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.memoize (false)). + to_error "bad argument #1 to 'std.functional.memoize' (function expected, got boolean)" + expect (M.memoize (M.id, false)). + to_error "bad argument #2 to 'std.functional.memoize' (function or nil expected, got boolean)" From 7e5ed02b1c6fcf686bc50b5e0c81c78b9c0c08f5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 27 May 2014 14:48:41 +0700 Subject: [PATCH 175/703] functional: bind should not require respecifying fixed args. * specs/functional_spec.yaml (bind): Move incumbent examples to a new legacy example. Rewrite original examples with new api. Add specifications for behaviour when not all arguments are given in the call to the bound function. * lib/std/functional.lua (bind): Fill initial argument positions from original bind arguments, and then propagate final call arguments into non-fixed parameter positions. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 17 +++++++++++++++++ lib/std/functional.lua | 7 ++++++- specs/functional_spec.yaml | 18 ++++++++++++------ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 8836190..653cbda 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,23 @@ Stdlib NEWS - User visible changes - New `std.array` object, for clean and fast queue-like or stack-like container management. +** Incompatible changes: + + - `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. + + 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. + + ** Bug fixes: - Removed LDocs for unused `_DEBUG.std` field. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index bbb31d7..546c879 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -29,10 +29,15 @@ local function bind (f, ...) fix = fix[1] end return function (...) - local arg = {...} + 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 diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index decc657..61a0e75 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -25,14 +25,20 @@ specify std.functional: expect (M.bind (false)). to_error "bad argument #1 to 'std.functional.bind' (function expected, got boolean)" - it does not affect normal operation if no arguments are bound: - expect (M.bind (math.min, {}) (2, 3, 4)). - to_equal (2) + expect (M.bind (math.min) (2, 3, 4)).to_be (2) - it takes the extra arguments into account: - expect (M.bind (math.min, {1, 0}) (2, 3, 4)). - to_equal (0) + expect (M.bind (math.min, 1, 0) (2, 3, 4)).to_be (0) + - it appends final call arguments: + expect (M.bind (math.max, 2, 3) (4, 5, 1)).to_be (5) + - it does not require all arguments in final call: + div = function (a, b) return a / b end + expect (M.bind (div, 100) (25)).to_be (4) - it supports out of order extra arguments: - expect (M.bind (math.pow, {[2] = 3}) (2)). - to_equal (8) + expect (M.bind (math.pow, nil, 3) (2)).to_be (8) + - it supports the legacy api: + expect (M.bind (math.min, {}) (2, 3, 4)).to_be (2) + expect (M.bind (math.min, {1, 0}) (2, 3, 4)).to_be (0) + expect (M.bind (math.pow, {[2] = 3}) (2)).to_be (8) - describe case: From c9b13fb0d9a5c57826c5d7bae8aca6e0e46d7b5f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 27 May 2014 16:37:16 +0700 Subject: [PATCH 176/703] debug: argcheck can match functable with "function". * specs/debug_spec.yaml (argcheck): Specify correct behaviour when matching a functable against a "function" argument. * lib/std/base.lua (argcheck): Allow functables when a "function" type argument is required. * lib/std/debug.lua (argcheck): Update LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 7 +++++++ lib/std/debug.lua | 1 + specs/debug_spec.yaml | 11 ++++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 170b3a9..76415eb 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -79,6 +79,13 @@ else ok = true end + elseif check == "function" then + if actualtype == "function" or + (getmetatable (actual) or {}).__call ~= nil + then + ok = true + end + elseif check == actualtype then ok = true end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index b953c50..ed223b1 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -118,6 +118,7 @@ local argerror = base.argerror -- or one of the special options below: -- -- #table accept any non-empty table +-- function accept a function, or object with a __call metamethod -- list accept a table with a non-empty array part -- object accept any std.Object derived type -- any accept any non-nil argument type diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 86a39c8..c30db2d 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -109,13 +109,11 @@ specify std.debug: expect (fn ("boolean", nil)).to_error "boolean expected, got no value" expect (fn ("number", nil)).to_error "number expected, got no value" expect (fn ("string", nil)).to_error "string expected, got no value" - expect (fn ("function", nil)).to_error "function expected, got no value" expect (fn ("table", nil)).to_error "table expected, got no value" - it diagnoses mismatched primitive types: expect (fn ("boolean", {0})).to_error "boolean expected, got table" expect (fn ("number", {0})).to_error "number expected, got table" expect (fn ("string", {0})).to_error "string expected, got table" - expect (fn ("function", {0})).to_error "function expected, got table" expect (fn ("table", false)).to_error "table expected, got boolean" expect (fn ("table", require "std.object")). to_error "table expected, got Object" @@ -123,8 +121,15 @@ specify std.debug: expect (fn ("boolean", true)).not_to_error () expect (fn ("number", 1)).not_to_error () expect (fn ("string", "s")).not_to_error () - expect (fn ("function", function () end)).not_to_error () expect (fn ("table", {})).not_to_error () + - it diagnoses missing callable types: + expect (fn ("function", nil)).to_error "function expected, got no value" + - it diagnoses mismatched callable types: + expect (fn ("function", {0})).to_error "function expected, got table" + - it matches callable types: + expect (fn ("function", function () end)).not_to_error () + expect (fn ("function", setmetatable ({}, {__call = function () end}))). + not_to_error () - it diagnoses missing non-empty table types: expect (fn ("#table", nil)). to_error "non-empty table expected, got no value" From 843f10980ce4c8f662cc01cc864635a30dbf55d2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 27 May 2014 17:22:54 +0700 Subject: [PATCH 177/703] functional: complete specs and improve LDocs. * lib/std/functional.lua (bind, case, curry, compose, eval) (collect, map, filter, fold): Improve LDocs with usage examples, and cross-references. * specs/functional_spec.yaml (collect, compose, curry, eval) (filter, fold, id, map, memoize): Add missing specifications. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 60 ++++++++++++++++++------ specs/functional_spec.yaml | 95 +++++++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 15 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 546c879..d152dd7 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -21,6 +21,10 @@ end -- @param f function to apply partially -- @tparam t table {p1=a1, ..., pn=an} table of parameters to bind to given arguments -- @return function with pi already bound +-- @usage +-- > cube = bind (math.pow, {[2] = 3}) +-- > =cube (2) +-- 8 local function bind (f, ...) argscheck ("std.functional.bind", "function", f) @@ -47,16 +51,15 @@ end -- Match `with` against keys in `branches` table, and return the result -- of running the function in the table value for the matching key, or -- the first non-key value function if no key matches. --- --- return case (type (object), { --- table = function () return something end, --- string = function () return something else end, --- function (s) error ("unhandled type: "..s) end, --- }) --- -- @param with expression to match -- @tparam table branches map possible matches to functions -- @return the return value from function with a matching key, or nil. +-- @usage +-- return case (type (object), { +-- table = function () return something end, +-- string = function () return something else end, +-- function (s) error ("unhandled type: "..s) end, +-- }) local function case (with, branches) argcheck ("std.functional.case", 2, "#table", branches) @@ -69,6 +72,11 @@ end -- @param f function to curry -- @param n number of arguments -- @return curried version of f +-- @usage +-- > add = curry (function (x, y) return x + y end, 2) +-- > incr, decr = add (1), add (-1) +-- > =incr (99), decr (99) +-- 100 98 local function curry (f, n) argscheck ("std.functional.curry", {"function", "number"}, {f, n}) @@ -83,7 +91,7 @@ end --- Compose functions. --- @param f1...fn functions to compose +-- @tparam function ... functions to compose -- @return composition of fn (... (f1) ...): note that this is the reverse -- of what you might expect, but means that code like: -- @@ -91,6 +99,12 @@ end -- function (x) return g (x) end)) -- -- can be read from top to bottom. +-- @usage +-- > vpairs = compose (table.invert, pairs) +-- > for v in vpairs {"a", "b", "c"} do print (v) end +-- b +-- c +-- a local function compose (...) local arg = {...} if #arg < 1 then @@ -152,8 +166,9 @@ end --- Evaluate a string. --- @param s string --- @return value of string +-- @string s string of Lua code +-- @return result of evaluating `s` +-- @usage eval "math.pow (2, 10)" local function eval (s) argscheck ("std.functional.eval", "string", s) return loadstring ("return " .. s)() @@ -161,8 +176,14 @@ end --- Collect the results of an iterator. --- @param i iterator --- @return results of running the iterator on its arguments +-- @tparam function i iterator +-- @param ... arguments +-- @return results of running the iterator on *arguments +-- @see filter +-- @see map +-- @usage +-- > =collect (std.list.relems, {"a", "b", "c"}) +-- {"c", "b", "a"} local function collect (i, ...) argcheck ("std.functional.collect", 1, "function", i) @@ -175,9 +196,13 @@ end --- Map a function over an iterator. --- @param f function --- @param i iterator +-- @tparam function f function +-- @tparam function i iterator -- @return result table +-- @see filter +-- @usage +-- > map (function (e) return e % 2 end, std.list.elements, {1, 2, 3, 4}) +-- {1, 0, 1, 0} local function map (f, i, ...) argscheck ("std.functional.map", {"function", "function"}, {f, i}) @@ -196,6 +221,10 @@ end -- @param p predicate -- @param i iterator -- @return result table containing elements e for which p (e) +-- @see collect +-- @usage +-- > filter (function (e) return e % 2 == 0 end, std.list.elements, {1, 2, 3, 4}) +-- {2, 4} local function filter (p, i, ...) argscheck ("std.functional.filter", {"function", "function"}, {p, i}) @@ -214,6 +243,9 @@ end -- @param d initial first argument -- @param i iterator -- @return result +-- @see std.list.foldl +-- @see std.list.foldr +-- @usage fold (math.pow, 1, std.list.elems, {2, 3, 4}) local function fold (f, d, i, ...) argscheck ("std.functional.fold", {"function", "any", "function"}, {f, d, i}) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 61a0e75..08cc5a4 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -78,6 +78,8 @@ specify std.functional: - it diagnoses wrong argument types: | expect (M.collect (false)). to_error "bad argument #1 to 'std.functional.collect' (function expected, got boolean)" + - it collects iterator results: + expect (M.collect (ipairs, {"a", "b", "c"})).to_equal {1, 2, 3} - describe compose: @@ -89,9 +91,11 @@ specify std.functional: to_error "bad argument #1 to 'std.functional.compose' (function expected, got boolean)" expect (M.compose (M.id, false)). to_error "bad argument #2 to 'std.functional.compose' (function expected, got boolean)" + - it composes a single function correctly: + expect (M.compose (M.id) (1)).to_be (1) - it composes functions in the correct order: expect (M.compose (math.sin, math.cos) (1)). - to_equal (math.cos (math.sin (1))) + to_be (math.cos (math.sin (1))) - describe curry: @@ -105,6 +109,18 @@ specify std.functional: to_error "bad argument #1 to 'std.functional.curry' (function expected, got boolean)" expect (M.curry (M.id, false)). to_error "bad argument #2 to 'std.functional.curry' (number expected, got boolean)" + - it returns a zero argument function uncurried: + expect (M.curry (M.id, 0)).to_be (M.id) + - it returns a one argument function uncurried: + expect (M.curry (M.id, 1)).to_be (M.id) + - it curries a two argument function: + expect (M.curry (M.id, 2)).not_to_be (M.id) + - it evaluates intermediate arguments one at a time: + expect (M.curry (math.min, 3) (2) (3) (4)).to_equal (2) + - it returns a curried function that can be partially applied: + bin = M.curry (math.pow, 2) (2) + expect (bin (2)).to_be (math.pow (2, 2)) + expect (bin (10)).to_be (math.pow (2, 10)) - describe eval: @@ -114,9 +130,17 @@ specify std.functional: - it diagnoses wrong argument types: | expect (M.eval (false)). to_error "bad argument #1 to 'std.functional.eval' (string expected, got boolean)" + - it diagnoses invalid lua: + # Some internal error when eval tries to call uncompilable "=" code. + expect (M.eval "=").to_error () + - it evaluates a string of lua code: + expect (M.eval "math.pow (2, 10)").to_be (math.pow (2, 10)) - describe filter: + - before: + elements = {"a", "b", "c", "d", "e"} + inverse = require "std.table".invert (elements) - it diagnoses missing arguments: | expect (M.filter ()). to_error "bad argument #1 to 'std.functional.filter' (function expected, got no value)" @@ -127,9 +151,25 @@ specify std.functional: to_error "bad argument #1 to 'std.functional.filter' (function expected, got boolean)" expect (M.filter (M.id, false)). to_error "bad argument #2 to 'std.functional.filter' (function expected, got boolean)" + - it iterates through element keys: + expect (M.filter (M.id, ipairs, elements)). + to_equal {1, 2, 3, 4, 5} + expect (M.filter (M.id, pairs, inverse)). + to_contain.a_permutation_of (elements) + - it passes each iterated element to filter function: + t = {} + M.filter (function (e) t[#t + 1] = e end, pairs, inverse) + expect (t).to_contain.a_permutation_of (elements) + - it returns a table of filtered keys: + expect (M.filter (function (e) return e % 2 == 0 end, ipairs, elements)). + to_equal {2, 4} + expect (M.filter (function (e) return e:match "[aeiou]" end, pairs, inverse)). + to_contain.a_permutation_of {"a", "e"} - describe fold: + - before: + list = require "std.list" - it diagnoses missing arguments: | expect (M.fold ()). to_error "bad argument #1 to 'std.functional.fold' (function expected, got no value)" @@ -142,12 +182,28 @@ specify std.functional: to_error "bad argument #1 to 'std.functional.fold' (function expected, got boolean)" expect (M.fold (M.id, 1, false)). to_error "bad argument #3 to 'std.functional.fold' (function expected, got boolean)" + - it calls a binary function over element keys: + expect (M.fold (M.op["+"], 2, list.elems, {3})). + to_be (2 + 3) + expect (M.fold (M.op["*"], 2, list.elems, {3, 4})). + to_be (2 * 3 * 4) + - it folds elements from left to right: + expect (M.fold (math.pow, 2, list.elems, {3, 4})). + to_be (math.pow (math.pow (2, 3), 4)) - describe id: + - it returns argument unchanged: + expect (M.id (true)).to_be (true) + expect (M.id {1, 1, 2, 3}).to_equal {1, 1, 2, 3} + - it returns multiple arguments unchanged: + expect ({M.id (1, "two", false)}).to_equal {1, "two", false} - describe map: + - before: + elements = {"a", "b", "c", "d", "e"} + inverse = require "std.table".invert (elements) - it diagnoses missing arguments: | expect (M.map ()). to_error "bad argument #1 to 'std.functional.map' (function expected, got no value)" @@ -158,9 +214,25 @@ specify std.functional: to_error "bad argument #1 to 'std.functional.map' (function expected, got boolean)" expect (M.map (M.id, false)). to_error "bad argument #2 to 'std.functional.map' (function expected, got boolean)" + - it iterates through element keys: + expect (M.map (M.id, ipairs, elements)). + to_equal {1, 2, 3, 4, 5} + expect (M.map (M.id, pairs, inverse)). + to_contain.a_permutation_of (elements) + - it passes each iterated element to map function: + t = {} + M.map (function (e) t[#t + 1] = e end, pairs, inverse) + expect (t).to_contain.a_permutation_of (elements) + - it returns a table of mapped keys: + expect (M.map (function (e) return e % 2 end, ipairs, elements)). + to_equal {1, 0, 1, 0, 1} + expect (M.map (function (e) return e .. "x" end, pairs, inverse)). + to_contain.a_permutation_of {"ax", "bx", "cx", "dx", "ex"} - describe memoize: + - before: + memfn = M.memoize (function (x) return {x} end) - it diagnoses missing arguments: | expect (M.memoize ()). to_error "bad argument #1 to 'std.functional.memoize' (function expected, got no value)" @@ -169,3 +241,24 @@ specify std.functional: to_error "bad argument #1 to 'std.functional.memoize' (function expected, got boolean)" expect (M.memoize (M.id, false)). to_error "bad argument #2 to 'std.functional.memoize' (function or nil expected, got boolean)" + - it returns the same object for the same arguments: + t = memfn (1) + expect (memfn (1)).to_be (t) + - it returns a different object for different arguments: + expect (memfn (1)).not_to_be (memfn (2)) + - it returns the same object for table valued arguments: + t = memfn {1, 2, 3} + expect (memfn {1, 2, 3}).to_be (t) + t = memfn {foo = "bar", baz = "quux"} + expect (memfn {foo = "bar", baz = "quux"}).to_be (t) + expect (memfn {baz = "quux", foo = "bar"}).to_be (t) + - it returns a different object for different table arguments: + expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2}) + expect (memfn {1, 2, 3}).not_to_be (memfn {3, 1, 2}) + expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2, 3, 4}) + - it accepts alternative normalization function: + normalize = function (...) return select ("#", ...) end + memfn = M.memoize (function (x) return {x} end, normalize) + expect (memfn "same").to_be (memfn "not same") + expect (memfn (1, 2)).to_be (memfn (false, "x")) + expect (memfn "one").not_to_be (memfn ("one", "two")) From d3b6e7a51c166803daec77f29a927c8b4fc8bcd4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 29 May 2014 22:14:09 +0700 Subject: [PATCH 178/703] refactor: merge std.base_array into std.array. Instead of a one-way degrading from alien.buffer managed elements to table managed elements with a sub-type, combine both sets of optimised methods and metamethods back into a single `std.array` container, which decides with each clone operation how to manage elements of the named type. * specs/array_spec.yaml: Specify behaviours for Array object that dispatches module function calls at runtime, and assigns optimized methods and metamethods on cloned objects according to element type. * lib/std/base_array.lua (pop, push, realloc, set, shift, unshift) (__index, __newindex, __len, __tostring): Move from here... * lib/std/array.lua (core_functions.pop, core_functions.push) (core_functions.realloc, core_functions.set, core_functions.shift) (core_functions.unshift, core_metatable.__index) (core_metatable.__newindex, core_metatable.__len) (core_metatable.__tostring: ...to here. (pop, push, realloc, set, shift, unshift, __index, __newindex): Move from here... (alien_functions.pop, alien_functions.push, alien_functions.set) (alien_functions.realloc, alien_functions,shift) (alien_functions.unshift, alien_metatable.__index) (alien_metatable.__newindex): ...to here. (core_metatable.__call): Clone a new Array object, setting the method and metatables with functions optimised for alien.buffer or Lua table based element management according to availability of alien, and element type name. (dispatch): New runtime virtual table dispatch function. (Array): Dispatch module functions at runtime based on element type. * lib/std/base_array.lua: Remove. * local.mk (dist_luastd_DATA): Remove lib/std/base_array.lua. * specs/base_array_spec.yaml: Remove. * specs/specs.mk (specl_SPECS): Remove specs/base_array_spec.yaml. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 7 +- lib/std/array.lua | 459 ++++++++++++++++++++++--------- lib/std/base_array.lua | 226 --------------- local.mk | 1 - specs/array_spec.yaml | 138 +++++++--- specs/base_array_spec.yaml | 546 ------------------------------------- specs/specs.mk | 1 - 7 files changed, 431 insertions(+), 947 deletions(-) delete mode 100644 lib/std/base_array.lua delete mode 100644 specs/base_array_spec.yaml diff --git a/NEWS b/NEWS index 653cbda..9bdedf8 100644 --- a/NEWS +++ b/NEWS @@ -15,7 +15,8 @@ Stdlib NEWS - User visible changes disables those functions too. - New `std.array` object, for clean and fast queue-like or stack-like - container management. + container management. When alien is installed, and element types + are compatible, use alien.buffers for efficient element management. ** Incompatible changes: @@ -31,8 +32,8 @@ Stdlib NEWS - User visible changes 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. - + the fixed argument positions in the `bind` invocation. + ** Bug fixes: diff --git a/lib/std/array.lua b/lib/std/array.lua index 24b45c9..0757475 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -1,8 +1,8 @@ --[[-- - Efficient array of homogenous objects. + Array of homogenous objects. - An Array is a block of contiguous memory, divided into equal sized - elements that can be indexed quickly. + An Array is usually a block of contiguous memory, divided into equal + sized elements that can be indexed quickly. Create a new Array with: @@ -11,18 +11,18 @@ > =array[1], array[2], array[3], array[-3], array[-4] 57005 48879 65261 57005 nil - All the indices passed to methods use 1-based counting. + All the indices passed to Array methods use 1-based counting. If the Lua alien module is installed, and the `type` argument passed - when cloning an Array object is suitable (i.e. the name of a numeric - C type that alien.array understands), then the array contents are - managed in an alien.array. + when cloning a new Array object is suitable (i.e. the name of a numeric + C type that `alien.sizeof` understands), then the array contents are + managed in an `alien.buffer`. If alien is not installed, or does not understand the `type` argument - it is given, then a much slower (but API compatible) Lua table is used - to manage elements. + given when cloning, then a much slower (but API compatible) Lua table + is transparently used to manage elements instead. - In either case, std.array provides a means for managing collections + In either case, `std.array` provides a means for managing collections of homogenous Lua objects with a vector-like, stack-like or queue-like API. @@ -33,10 +33,8 @@ local base = require "std.base" local argcheck, argscheck = base.argcheck, base.argscheck -local Object = require "std.object" -local prototype = Object.prototype - -local BaseArray = require "std.base_array" +local Container = require "std.container" +local prototype = Container.prototype local have_alien, alien = pcall (require, "alien") local buffer, memmove, memset @@ -48,42 +46,21 @@ end local typeof = type -local element_chunk_size = 16 - ---- Convert an array element index into a pointer. --- @tparam std.array self an array --- @int i[opt=1] an index into array --- @treturn alien.buffer.pointer suitable for memmove or memset -local function topointer (self, i) - i = i or 1 - return self.buffer:topointer ((i - 1) * self.size + 1) -end +-- Initial Array prototype object, plus any derived object containing -- +-- elements that don't fit in alien buffers use `core_functions` to -- +-- find object methods and `core_metatable` for metamethods. -- ---- Fast zeroing of a contiguous block of array elements for `alien.buffer`s. --- @tparam std.array self an array --- @int from index of first element to zero out --- @int n number of elements to zero out -local function setzero (self, from, n) - if n > 0 then memset (topointer (self, from), 0, n * self.size) end -end - - -local _functions = { +local core_functions = { --- Remove the right-most element. -- @function pop -- @return the right-most element pop = function (self) - argscheck ("pop", {"Array"}, {self}) + argcheck ("pop", 1, "Array", self) - local used = self.length - if used > 0 then - local elem = self[used] - self:realloc (used - 1) - return elem - end - return nil + self.length = math.max (self.length - 1, 0) + return table.remove (self.buffer) end, @@ -92,11 +69,11 @@ local _functions = { -- @param elem new element to be pushed -- @return elem push = function (self, elem) - argscheck ("push", {"Array", "number"}, {self, elem}) + argscheck ("push", {"Array", "any"}, {self, elem}) - local used = self.length + 1 - self:realloc (used) - self[used] = elem + local length = self.length + 1 + self.buffer[length] = elem + self.length = length return elem end, @@ -108,15 +85,11 @@ local _functions = { realloc = function (self, n) argscheck ("realloc", {"Array", "number"}, {self, n}) - if n > self.allocated or n < self.allocated / 2 then - self.allocated = n + element_chunk_size - self.buffer:realloc (self.allocated * self.size) - end - -- Zero padding for uninitialised elements. - local used = self.length + for i = self.length + 1, n do + self.buffer[i] = 0 + end self.length = n - setzero (self, used + 1, n - used) return self end, @@ -125,22 +98,21 @@ local _functions = { --- Set `n` elements starting at `from` to `v`. -- @function set -- @int from index of first element to set - -- @int v value to store + -- @param v value to store -- @int n number of elements to set -- @treturn Array the array set = function (self, from, v, n) - argscheck ("set", {"Array", "number", "number", "number"}, + argscheck ("set", {"Array", "number", "any", "number"}, {self, from, v, n}) - local used = self.length - if from < 0 then from = from + used + 1 end - assert (from > 0 and from <= used) + local length = self.length + if from < 0 then from = from + length + 1 end + assert (from > 0 and from <= length) - local i = from + n - 1 - while i >= from do + for i = from, from + n - 1 do self[i] = v - i = i - 1 end + return self end, @@ -150,16 +122,10 @@ local _functions = { -- @function shift -- @return the removed element. shift = function (self) - argscheck ("shift", {"Array"}, {self}) + argcheck ("shift", 1, "Array", self) - local n = self.length - 1 - if n >= 0 then - local elem = self[1] - memmove (topointer (self), topointer (self, 2), n * self.size) - self:realloc (n) - return elem - end - return nil + self.length = math.max (self.length - 1, 0) + return table.remove (self.buffer, 1) end, @@ -168,12 +134,10 @@ local _functions = { -- @param elem new element to be pushed -- @treturn elem unshift = function (self, elem) - argscheck ("unshift", {"Array", "number"}, {self, elem}) + argscheck ("unshift", {"Array", "any"}, {self, elem}) - local n = self.length - self:realloc (n + 1) - memmove (topointer (self, 2), topointer (self), n * self.size) - self[1] = elem + self.length = self.length + 1 + table.insert (self.buffer, 1, elem) return elem end, } @@ -188,32 +152,36 @@ local function sizeof (type) end ------- --- An efficient array of homogenous objects. --- @table Array --- @int length number of elements currently allocated --- @tfield alien.array array a block of indexable memory -local Array = Object { - _type = "Array", +--- Convert an array element index into a pointer. +-- @tparam std.array self an array +-- @int i[opt=1] an index into array +-- @treturn alien.buffer.pointer suitable for memmove or memset +local function topointer (self, i) + i = i or 1 + return self.buffer:topointer ((i - 1) * self.size + 1) +end - -- Prototype initial values. - allocated = 1, - buffer = buffer (sizeof "int"), - length = 0, - size = sizeof "int", - type = "int", +--- Fast zeroing of a contiguous block of array elements for `alien.buffer`s. +-- @tparam std.array self an array +-- @int from index of first element to zero out +-- @int n number of elements to zero out +local function setzero (self, from, n) + if n > 0 then memset (topointer (self, from), 0, n * self.size) end +end - -- Module functions. - _functions = _functions, +local core_metatable, alien_metatable -- forward declarations + +core_metatable = { + _type = "Array", --- Instantiate a newly cloned Array. -- If not specified, `type` will be the same as the prototype array being - -- cloned; otherwise, it can be any string. Only valid alien accepted by - -- `alien.array` will use the fast `alien.array` managed memory buffer for - -- Array contents; otherwise, a much slower Lua emulation is used. + -- cloned; otherwise, it can be any string. Only a type name accepted by + -- `alien.sizeof` will use the fast `alien.buffer` managed memory buffer + -- for Array contents; otherwise, a much slower Lua emulation is used. -- @function __call -- @string type element type name -- @tparam[opt] int|table init initial size or list of initial elements @@ -234,13 +202,6 @@ local Array = Object { type = type or self.type init = init or self.length - -- If type cannot be managed by an alien.buffer, revert to a table - -- based BaseArray object instead. - local size = sizeof (type) - if size == 0 then - return BaseArray (init) - end - -- This will become the cloned Array object. local obj = {} @@ -249,41 +210,68 @@ local Array = Object { obj[k] = v end end - obj.size = size - obj.type = type - if typeof (init) == "table" then - obj.length = #init - obj.allocated = #init - obj.buffer = buffer (size * #init) - for i = 1, #init do - obj.buffer:set ((i - 1) * size + 1, init[i], type) + local size = sizeof (type) + obj.size = size -- setzero uses self.size for byte calculations + + if size == 0 then + + -- Either alien is not installed, or it cannot handle elements + -- of `type`, so we'll use Lua tables and core_metatable: + local b = {} + if typeof (init) == "table" then + for i = 1, #init do + b[i] = init[i] + end + else + local plength = self.length + local length = init or plength + for i = 1, math.min (plength, length) do + b[i] = self.buffer[i] + end + for i = plength + 1, length do + b[i] = 0 + end end + obj.allocated = 0 + obj.buffer = b + obj.length = #b + + setmetatable (obj, core_metatable) + else - obj.length = init - obj.allocated = math.max (init or 0, 1) - obj.buffer = buffer (size * obj.allocated) - if size == self.size then - local bytes = math.min (init, self.length) * size - memmove (obj.buffer:topointer (), self.buffer:topointer (), bytes) + -- We have alien, and it knows how to manage elements of `type`, + -- so we'll use an alien.buffer and alien_metatable: + if typeof (init) == "table" then + obj.allocated = #init + obj.buffer = buffer (size * #init) + obj.length = #init + + for i = 1, #init do + obj.buffer:set ((i - 1) * size + 1, init[i], type) + end else - local a, b = obj.buffer, self.buffer - for i = 1, math.min (init, self.length) do a[i] = b[i] end + obj.allocated = math.max (init or 0, 1) + obj.buffer = buffer (size * obj.allocated) + obj.length = init + + if size == self.size then + local bytes = math.min (init, self.length) * size + memmove (obj.buffer:topointer (), self.buffer:topointer (), bytes) + else + local a, b = obj.buffer, self.buffer + for i = 1, math.min (init, self.length) do a[i] = b[i] end + end + setzero (obj, self.length + 1, init - self.length) end - setzero (obj, self.length + 1, init - self.length) - end - return setmetatable (obj, getmetatable (self)) - end, + setmetatable (obj, alien_metatable) + end + obj.type = type - --- Return the number of elements in this Array. - -- @function __len - -- @treturn int number of elements - __len = function (self) - argscheck ("__len", {"Array"}, {self}) - return self.length + return obj end, @@ -297,10 +285,10 @@ local Array = Object { if typeof (n) == "number" then if n < 0 then n = n + self.length + 1 end if n > 0 and n <= self.length then - return self.buffer:get ((n - 1) * self.size + 1, self.type) + return self.buffer[n] end else - return _functions[n] + return core_functions[n] end end, @@ -311,15 +299,16 @@ local Array = Object { -- @param elem value to store at index n -- @treturn Array the array __newindex = function (self, n, elem) - argscheck ("__newindex", {"Array", "number", "number"}, {self, n, elem}) + argscheck ("__newindex", {"Array", "number", "any"}, {self, n, elem}) if typeof (n) == "number" then local used = self.length if n == 0 or math.abs (n) > used then - error ("array access " .. n .. " out of bounds: 0 < n <= " .. tostring (self.length), 2) + error ("array access " .. n .. " out of bounds: 0 < abs (n) <= " .. + tostring (self.length), 2) end if n < 0 then n = n + used + 1 end - self.buffer:set ((n - 1) * self.size + 1, elem, self.type) + self.buffer[n] = elem else rawset (self, n, elem) end @@ -327,19 +316,221 @@ local Array = Object { end, + --- Return the number of elements in this Array. + -- @function __len + -- @treturn int number of elements + __len = function (self) + argcheck ("__len", 1, "Array", self) + return self.length + end, + + --- Return a string representation of the contents of this Array. -- @function __tostring -- @treturn string string representation __tostring = function (self) - argscheck ("__tostring", {"Array"}, {self}) + argcheck ("__tostring", 1, "Array", self) local t = {} for i = 1, self.length do t[#t + 1] = tostring (self[i]) end - t = { '"' .. self.type .. '"', "{" .. table.concat (t, ", ") .. "}" } - return prototype (self) .. " (" .. table.concat (t, ", ") .. ")" + return prototype (self) .. ' ("' .. self.type .. + '", {' .. table.concat (t, ", ") .. "})" + end, +} + + +-- Cloned Array objects with elements managed by an alien buffer use -- +-- `alien_functions` to find object methods and `alien_metatable` -- +-- for metamethods. -- + + +local element_chunk_size = 16 + + +local alien_functions = { + pop = function (self) + argscheck ("pop", {"Array"}, {self}) + + local used = self.length + if used > 0 then + local elem = self[used] + self:realloc (used - 1) + return elem + end + return nil + end, + + + push = function (self, elem) + argscheck ("push", {"Array", "number"}, {self, elem}) + + local used = self.length + 1 + self:realloc (used) + self[used] = elem + return elem + end, + + + realloc = function (self, n) + argscheck ("realloc", {"Array", "number"}, {self, n}) + + if n > self.allocated or n < self.allocated / 2 then + self.allocated = n + element_chunk_size + self.buffer:realloc (self.allocated * self.size) + end + + -- Zero padding for uninitialised elements. + local used = self.length + self.length = n + setzero (self, used + 1, n - used) + + return self + end, + + + set = function (self, from, v, n) + argscheck ("set", {"Array", "number", "number", "number"}, + {self, from, v, n}) + + local used = self.length + if from < 0 then from = from + used + 1 end + assert (from > 0 and from <= used) + + local i = from + n - 1 + while i >= from do + self[i] = v + i = i - 1 + end + return self + end, + + + shift = function (self) + argscheck ("shift", {"Array"}, {self}) + + local n = self.length - 1 + if n >= 0 then + local elem = self[1] + memmove (topointer (self), topointer (self, 2), n * self.size) + self:realloc (n) + return elem + end + return nil + end, + + + unshift = function (self, elem) + argscheck ("unshift", {"Array", "number"}, {self, elem}) + + local n = self.length + self:realloc (n + 1) + memmove (topointer (self, 2), topointer (self), n * self.size) + self[1] = elem + return elem end, } + +alien_metatable = { + _type = "Array", + + __index = function (self, n) + argscheck ("__index", {"Array", {"number", "string"}}, {self, n}) + + if typeof (n) == "number" then + if n < 0 then n = n + self.length + 1 end + if n > 0 and n <= self.length then + return self.buffer:get ((n - 1) * self.size + 1, self.type) + end + else + return alien_functions[n] + end + end, + + __newindex = function (self, n, elem) + argscheck ("__newindex", {"Array", "number", "number"}, {self, n, elem}) + + if typeof (n) == "number" then + local used = self.length + if n == 0 or math.abs (n) > used then + error ("array access " .. n .. " out of bounds: 0 < n <= " .. tostring (self.length), 2) + end + if n < 0 then n = n + used + 1 end + self.buffer:set ((n - 1) * self.size + 1, elem, self.type) + else + rawset (self, n, elem) + end + return self + end, + + __call = core_metatable.__call, + __len = core_metatable.__len, + __tostring = core_metatable.__tostring, +} + + + +--- Return a function that dispatches to a virtual function table. +-- The __call metamethod ensures that cloned Array objects are assigned +-- a metatable and method table optimised for the element storage method +-- (either alien buffer, or Lua table element containers), but the Array +-- prototype returned by this module needs to dispatch to the correct +-- function according to the element type at run-time, because we want +-- to support passing either object as an argument to a module function. +-- @string name method name to dispatch +-- @treturn function call `alien_function[name]` or -- `core_function[name]` +-- as appropriate to the element manager of array +local function dispatch (name) + return function (array, ...) + argcheck (name, 1, "Array", array) + local vfns = array.size > 0 and alien_functions or core_functions + return vfns[name] (array, ...) + end +end + + + +------ +-- An efficient array of homogenous objects. +-- @table Array +-- @int allocated number of allocated element slots, for `alien.buffer` +-- managed elements +-- @tfield alien.buffer|table buffer a block of indexable memory +-- @int length number of elements currently stored +-- @int size length of each stored element, or 0 when `alien.buffer` is +-- not managing this Array +-- @string type type name for elements +local Array = Container { + _type = "Array", + + + -- Prototype initial values. + allocated = 0, + buffer = {}, + length = 0, + size = 0, + type = "any", + + + _functions = { + pop = dispatch "pop", + push = dispatch "push", + realloc = dispatch "realloc", + set = dispatch "set", + shift = dispatch "shift", + unshift = dispatch "unshift", + }, + + + __index = dispatch "__index", + __newindex = dispatch "__newindex", + + __call = core_metatable.__call, + __len = core_metatable.__len, + __tostring = core_metatable.__tostring, +} + + return Array diff --git a/lib/std/base_array.lua b/lib/std/base_array.lua deleted file mode 100644 index 61cf658..0000000 --- a/lib/std/base_array.lua +++ /dev/null @@ -1,226 +0,0 @@ ------- --- @module std.base_array - - -local base = require "std.base" -local argcheck, argscheck = base.argcheck, base.argscheck - -local Object = require "std.object" -local prototype = Object.prototype - -local typeof = type - - -local _functions = { - --- Remove the right-most element. - -- @function pop - -- @return the right-most element - pop = function (self) - argcheck ("pop", 1, "Array", self) - - self.length = math.max (self.length - 1, 0) - return table.remove (self.buffer) - end, - - - --- Add elem as the new right-most element. - -- @function push - -- @param elem new element to be pushed - -- @return elem - push = function (self, elem) - argscheck ("push", {"Array", "any"}, {self, elem}) - - local length = self.length + 1 - self.buffer[length] = elem - self.length = length - return elem - end, - - - --- Change the number of elements allocated to be at least `n`. - -- @function realloc - -- @int n the number of elements required - -- @treturn Array the array - realloc = function (self, n) - argscheck ("realloc", {"Array", "number"}, {self, n}) - - -- Zero padding for uninitialised elements. - for i = self.length + 1, n do - self.buffer[i] = 0 - end - self.length = n - - return self - end, - - - --- Set `n` elements starting at `from` to `v`. - -- @function set - -- @int from index of first element to set - -- @param v value to store - -- @int n number of elements to set - -- @treturn Array the array - set = function (self, from, v, n) - argscheck ("set", {"Array", "number", "any", "number"}, - {self, from, v, n}) - - local length = self.length - if from < 0 then from = from + length + 1 end - assert (from > 0 and from <= length) - - for i = from, from + n - 1 do - self[i] = v - end - - return self - end, - - - --- Shift the whole array to the left by removing the left-most element. - -- This makes the array 1 element shorter than it was before the shift. - -- @function shift - -- @return the removed element. - shift = function (self) - argcheck ("shift", 1, "Array", self) - - self.length = math.max (self.length - 1, 0) - return table.remove (self.buffer, 1) - end, - - - --- Shift the whole array to the right by inserting a new left-most element. - -- @function unshift - -- @param elem new element to be pushed - -- @treturn elem - unshift = function (self, elem) - argscheck ("unshift", {"Array", "any"}, {self, elem}) - - self.length = self.length + 1 - table.insert (self.buffer, 1, elem) - return elem - end, -} - - ------- --- A container for contiguous objects. --- @table Array --- @tfield table buffer contained objects --- @int length number of elements -local Array = Object { - _type = "Array", - - - -- Prototype initial values. - buffer = {}, - length = 0, - - - -- Module functions. - _functions = _functions, - - - --- Instantiate a newly cloned Array. - -- @function __call - -- @string[opt] type element type name - -- @tparam[opt] int|table init initial size or list of initial elements - -- @treturn Array a new Array object - _init = function (self, type, init) - if init ~= nil then - -- When called with 2 arguments: - argcheck ("Array", 1, "string", type) - argcheck ("Array", 2, {"number", "table"}, init) - elseif type ~= nil then - argcheck ("Array", 1, {"number", "string", "table"}, type) - end - - -- Non-string argument 1 is reall an init argument. - if typeof (type) ~= "string" then type, init = nil, type end - - local b = {} - if typeof (init) == "table" then - for i = 1, #init do - b[i] = init[i] - end - else - local plength = self.length - local length = init or plength - - for i = 1, math.min (plength, length) do - b[i] = self.buffer[i] - end - for i = plength + 1, length do - b[i] = 0 - end - end - self.buffer = b - self.length = #b - return self - end, - - - --- Return the number of elements in this Array. - -- @function __len - -- @treturn int number of elements - __len = function (self) - argcheck ("__len", 1, "Array", self) - return self.length - end, - - - --- Return the `n`th character in this Array. - -- @function __index - -- @int n 1-based index, or negative to index starting from the right - -- @treturn string the element at index `n` - __index = function (self, n) - argscheck ("__index", {"Array", {"number", "string"}}, {self, n}) - - if typeof (n) == "number" then - if n < 0 then n = n + self.length + 1 end - if n > 0 and n <= self.length then - return self.buffer[n] - end - else - return _functions[n] - end - end, - - - --- Set the `n`th element of this Array to `elem`. - -- @function __newindex - -- @int n 1-based index - -- @param elem value to store at index n - -- @treturn Array the array - __newindex = function (self, n, elem) - argscheck ("__newindex", {"Array", "number", "any"}, {self, n, elem}) - - if typeof (n) == "number" then - local used = self.length - if n == 0 or math.abs (n) > used then - error ("array access " .. n .. " out of bounds: 0 < abs (n) <= " .. - tostring (self.length), 2) - end - if n < 0 then n = n + used + 1 end - self.buffer[n] = elem - else - rawset (self, n, elem) - end - return self - end, - - - --- Return a string representation of the contents of this Array. - -- @function __tostring - -- @treturn string string representation - __tostring = function (self) - argcheck ("__tostring", 1, "Array", self) - - local t = {} - for i = 1, self.length do - t[#t + 1] = tostring (self[i]) - end - return prototype (self) .. " {" .. table.concat (t, ", ") .. "}" - end, -} - -return Array diff --git a/local.mk b/local.mk index 6ce9418..a2a909a 100644 --- a/local.mk +++ b/local.mk @@ -64,7 +64,6 @@ luastddir = $(luadir)/std dist_luastd_DATA = \ lib/std/array.lua \ lib/std/base.lua \ - lib/std/base_array.lua \ lib/std/container.lua \ lib/std/debug.lua \ lib/std/functional.lua \ diff --git a/specs/array_spec.yaml b/specs/array_spec.yaml index 99e703b..7ac5201 100644 --- a/specs/array_spec.yaml +++ b/specs/array_spec.yaml @@ -14,6 +14,9 @@ specify Array: -- discard overflowed element array:pop () + local aliens = Array ("int", {42}) + have_alien = aliens.allocated > 0 + - describe __call: - it diagnoses wrong argument types: | expect (Array (1, 2)). @@ -69,6 +72,19 @@ specify Array: end expect (a.type).to_be (array.type) - context with specified element type: + - it constructs an alien managed array when possible: + if have_alien then + a = array ("int", {1, 1, 2, 3, 5}) + expect (a.allocated).not_to_be (0) + b = a ("int", {1, 4, 9, 16, 25}) + expect (b.allocated).not_to_be (0) + end + - it constructs a tabular array when necessary: + aliens = Array ("int", {1, 1, 2, 3, 5}) + a = aliens ("table", {1, 2, 5}) + expect (a.allocated).to_be (0) + b = a ("table", {1, 4, 9, 16, 25}) + expect (a.allocated).to_be (0) - it constructs an empty array: array = Array "double" expect (array.length).to_be (0) @@ -119,33 +135,46 @@ specify Array: array = Array ("table", { {v=1}, {v=1}, {v=2}, {v=3}, {v=5}, {v=8}, {v=13}, {v=21}, {v=34} }) + - it constructs an alien managed array when possible: + if have_alien then + a = array ("int", {1, 1, 2, 3, 5}) + expect (a.allocated).not_to_be (0) + b = array ("int", {1, 4, 9, 16, 25}) + expect (a.allocated).not_to_be (0) + end + - it constructs a tabular array when necessary: + aliens = Array ("int", {1, 1, 2, 3, 5}) + a = aliens ("table", {1, 2, 5}) + expect (a.allocated).to_be (0) + b = a ("table", {1, 4, 9, 16, 25}) + expect (a.allocated).to_be (0) - it constructs an empty array: array = Array "table" expect (array.length).to_be (0) - expect (array.type).to_be (nil) + expect (array.allocated).to_be (0) - it constructs a sized array: array = Array ("table", 100) expect (array.length).to_be (100) - expect (array.type).to_be (nil) + expect (array.allocated).to_be (0) - it sets uninitialised elements to zero: array = Array ("table", 100) for i = 1, 100 do expect (array[i]).to_be (0) end - expect (array.type).to_be (nil) + expect (array.allocated).to_be (0) - it initialises values from a table: array = Array ("table", {{v=1}, {v=4}, {v=9}, {v=16}, {v=25}}) expect (array.length).to_be (5) for i = 1, array.length do expect (array[i].v).to_be (i * i) end - expect (array.type).to_be (nil) + expect (array.allocated).to_be (0) - it contains values from prototype array: a = array () for i = 3, array.length do expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) end - expect (a.type).to_be (nil) + expect (a.allocated).to_be (0) - it truncates copied prototype values: c = math.floor (array.length / 2) a = array ("table", c) @@ -153,7 +182,7 @@ specify Array: for i = 3, a.length do expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) end - expect (a.type).to_be (nil) + expect (a.allocated).to_be (0) - it zero pads copied prototype values: a = array ("table", array.length * 2) expect (a.length).to_be (array.length * 2) @@ -163,14 +192,26 @@ specify Array: for i = array.length + 1, a.length do expect (a[i]).to_be (0) end - expect (a.type).to_be (nil) + expect (a.allocated).to_be (0) - describe __len: - - it returns the number of elements stored: - array = Array "char" - expect (array.length).to_be (0) - array = Array ("short", {1, 2, 3}) - expect (array.length).to_be (3) + - before: | + -- Some luajit releases, and PUC RIO 5.1 don't respect __len + -- metamethod for # operator. + local t = setmetatable ({}, {__len = function () return 42 end }) + meta__len = #t == 42 + - it returns the number of elements stored: | + empty = Array "char" + trio = Array ("short", {1, 2, 3}) + if meta__len then + -- __len metamethod support available + expect (#empty).to_be (0) + expect (#trio).to_be (3) + else + -- have to get the length explicitly + expect (empty.length).to_be (0) + expect (trio.length).to_be (3) + end - describe __index: - it returns nil for an empty array: @@ -284,13 +325,18 @@ specify Array: - it diagnoses missing arguments: | expect (Array.push ()). to_error "bad argument #1 to 'push' (Array expected, got no value)" - expect (Array.push (array)). - to_error "bad argument #2 to 'push' (number expected, got no value)" + if array.allocated > 0 then + -- non-alien managed arrays don't require number valued argument #2 + expect (Array.push (array)). + to_error "bad argument #2 to 'push' (number expected, got no value)" + end - it diagnoses wrong argument types: | expect (Array.push (1234)). to_error "bad argument #1 to 'push' (Array expected, got number)" - expect (Array.push (array, "short")). - to_error "bad argument #2 to 'push' (number expected, got string)" + if array.allocated > 0 then + expect (Array.push (array, "short")). + to_error "bad argument #2 to 'push' (number expected, got string)" + end - it adds a single element to an empty array: array = Array "int" Array.push (array, 42) @@ -314,11 +360,15 @@ specify Array: # use an intentionally short array for this example. array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - it diagnoses missing arguments: | - expect (array:push ()). - to_error "bad argument #2 to 'push' (number expected, got no value)" + if array.allocated > 0 then + expect (array:push ()). + to_error "bad argument #2 to 'push' (number expected, got no value)" + end - it diagnoses wrong argument type: | - expect (array:push ("short")). - to_error "bad argument #2 to 'push' (number expected, got string)" + if array.allocated > 0 then + expect (array:push ("short")). + to_error "bad argument #2 to 'push' (number expected, got string)" + end - it adds a single element to an empty array: array = Array "int" array:push (42) @@ -414,8 +464,10 @@ specify Array: to_error "bad argument #1 to 'set' (Array expected, got no value)" expect (Array.set (array)). to_error "bad argument #2 to 'set' (number expected, got no value)" - expect (Array.set (array, 1)). - to_error "bad argument #3 to 'set' (number expected, got no value)" + if array.allocated > 0 then + expect (Array.set (array, 1)). + to_error "bad argument #3 to 'set' (number expected, got no value)" + end expect (Array.set (array, 1, 0)). to_error "bad argument #4 to 'set' (number expected, got no value)" - it diagnoses wrong argument types: | @@ -423,8 +475,10 @@ specify Array: to_error "bad argument #1 to 'set' (Array expected, got number)" expect (Array.set (array, "bogus")). to_error "bad argument #2 to 'set' (number expected, got string)" - expect (Array.set (array, 1, {0})). - to_error "bad argument #3 to 'set' (number expected, got table)" + if array.allocated > 0 then + expect (Array.set (array, 1, {0})). + to_error "bad argument #3 to 'set' (number expected, got table)" + end expect (Array.set (array, 1, 0, function () end)). to_error "bad argument #4 to 'set' (number expected, got function)" - it changes the value of a subsequence of elements: @@ -469,15 +523,19 @@ specify Array: - it diagnoses missing arguments: | expect (array:set ()). to_error "bad argument #2 to 'set' (number expected, got no value)" - expect (array:set (1)). - to_error "bad argument #3 to 'set' (number expected, got no value)" + if array.allocated > 0 then + expect (array:set (1)). + to_error "bad argument #3 to 'set' (number expected, got no value)" + end expect (array:set (1, 0)). to_error "bad argument #4 to 'set' (number expected, got no value)" - it diagnoses wrong argument types: | expect (array:set "bogus"). to_error "bad argument #2 to 'set' (number expected, got string)" - expect (array:set (1, {0})). - to_error "bad argument #3 to 'set' (number expected, got table)" + if array.allocated > 0 then + expect (array:set (1, {0})). + to_error "bad argument #3 to 'set' (number expected, got table)" + end expect (array:set (1, 0, function () end)). to_error "bad argument #4 to 'set' (number expected, got function)" - it changes the value of a subsequence of elements: @@ -579,13 +637,17 @@ specify Array: - it diagnoses missing arguments: | expect (Array.unshift ()). to_error "bad argument #1 to 'unshift' (Array expected, got no value)" - expect (Array.unshift (array)). - to_error "bad argument #2 to 'unshift' (number expected, got no value)" + if array.allocated > 0 then + expect (Array.unshift (array)). + to_error "bad argument #2 to 'unshift' (number expected, got no value)" + end - it diagnoses wrong argument types: | expect (Array.unshift (1234)). to_error "bad argument #1 to 'unshift' (Array expected, got number)" - expect (Array.unshift (array, "short")). - to_error "bad argument #2 to 'unshift' (number expected, got string)" + if array.allocated > 0 then + expect (Array.unshift (array, "short")). + to_error "bad argument #2 to 'unshift' (number expected, got string)" + end - it adds a single element to an empty array: array = Array "int" Array.unshift (array, 42) @@ -610,11 +672,15 @@ specify Array: # use an intentionally short array for this example. array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - it diagnoses missing arguments: | - expect (array:unshift ()). - to_error "bad argument #2 to 'unshift' (number expected, got no value)" + if array.allocated > 0 then + expect (array:unshift ()). + to_error "bad argument #2 to 'unshift' (number expected, got no value)" + end - it diagnoses wrong argument type: | - expect (array:unshift ("short")). - to_error "bad argument #2 to 'unshift' (number expected, got string)" + if array.allocated > 0 then + expect (array:unshift ("short")). + to_error "bad argument #2 to 'unshift' (number expected, got string)" + end - it adds a single element to an empty array: array = Array "int" array:unshift (42) diff --git a/specs/base_array_spec.yaml b/specs/base_array_spec.yaml deleted file mode 100644 index 9d7d353..0000000 --- a/specs/base_array_spec.yaml +++ /dev/null @@ -1,546 +0,0 @@ -before: - Object = require "std.object" - Array = require "std.base_array" - - prototype = Object.prototype - -specify Array: -- before: | - array = Array {1, 1} - -- append fibonacci numbers until long word overflows - repeat - array:push (array[-1] + array[-2]) - until array.length > 50 or array[-1] < array[-2] - -- discard overflowed element - array:pop () - -- describe __call: - - it diagnoses wrong argument types: | - expect (Array (1, 2)). - to_error "bad argument #1 to 'Array' (string expected, got number)" - expect (Array (function () end)). - to_error "bad argument #1 to 'Array' (number, string or table expected, got function)" - expect (Array ("int", function () end)). - to_error "bad argument #2 to 'Array' (number or table expected, got function)" - - context with inherited element type: - - it constructs an empty array: - array = Array () - expect (array.length).to_be (0) - - it constructs a sized array: - array = Array (100) - expect (array.length).to_be (100) - - it sets uninitialised elements to zero: - array = Array (100) - for i = 1, 100 do - expect (array[i]).to_be (0) - end - - it initialises values from a table: - array = Array {1, 4, 9, 16, 25, 36, 49, 64, 81} - expect (array.length).to_be (9) - for i = 1, array.length do - expect (array[i]).to_be (i * i) - end - - it contains values from prototype array: - a = array () - for i = 3, array.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it truncates copied prototype values: - c = math.floor (array.length / 2) - a = array (c) - expect (a.length).to_be (c) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it zero pads copied prototype values: - a = array (array.length * 2) - expect (a.length).to_be (array.length * 2) - for i = 3, array.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - for i = array.length + 1, a.length do - expect (a[i]).to_be (0) - end - - context with specified element type: - - it constructs an empty array: - array = Array () - expect (array.length).to_be (0) - array = Array {} - expect (array.length).to_be (0) - - it constructs a sized array: - array = Array (100) - expect (array.length).to_be (100) - - it sets uninitialised elements to zero: - array = Array (100) - for i = 1, 100 do - expect (array[i]).to_be (0) - end - - it initialises values from a table: - array = Array {1, 4, 9, 16, 25, 36, 49, 64, 81} - expect (array.length).to_be (9) - for i = 1, array.length do - expect (array[i]).to_be (i * i) - end - - it contains values from prototype array: - a = array () - for i = 3, array.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it truncates copied prototype values: - c = math.floor (array.length / 2) - a = array (c) - expect (a.length).to_be (c) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it zero pads copied prototype values: - a = array (array.length * 2) - expect (a.length).to_be (array.length * 2) - for i = 3, array.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - for i = array.length + 1, a.length do - expect (a[i]).to_be (0) - end - -- describe __len: - - it returns the number of elements stored: - array = Array () - expect (array.length).to_be (0) - array = Array {1, 2, 3} - expect (array.length).to_be (3) - -- describe __index: - - it returns nil for an empty array: - array = Array () - expect (array[1]).to_be (nil) - expect (array[-1]).to_be (nil) - - it retrieves a value stored at that index: - expect (array[1]).to_be (1) - expect (array[2]).to_be (1) - expect (array[3]).to_be (2) - expect (array[4]).to_be (3) - expect (array[5]).to_be (5) - - it retrieves negative indices counting from the right: - expect (array[-1]).to_be (array[array.length]) - expect (array[-2]).to_be (array[array.length - 1]) - expect (array[-(array.length - 1)]).to_be (array[2]) - expect (array[-(array.length)]).to_be (array[1]) - - it returns nil for out of bounds indices: - expect (array[-(array.length * 2)]).to_be (nil) - expect (array[-(array.length + 1)]).to_be (nil) - expect (array[0]).to_be (nil) - expect (array[array.length + 1]).to_be (nil) - expect (array[array.length * 2]).to_be (nil) - - it retrieves method names: - expect (type (array.push)).to_be "function" - expect (type (array.pop)).to_be "function" - - it diagnoses undefined methods: - expect (array.notamethod ()).to_error "attempt to call field 'notamethod'" - -- describe __newindex: - - it sets a new value at that index: - array[2] = 2 - expect (array[2]).to_be (2) - - it sets negative indexed elements counting from the right: - for i = 1, array.length do array[-i] = array.length - i + 1 end - for i = 1, array.length do - expect (array[i]).to_be (i) - end - - it diagnoses out of bounds indices: - for _, i in ipairs {array.length * -2, -1 - array.length, 0, - array.length + 1, array.length * 2} do - expect ((function () array[i] = i end) ()). - to_error "out of bounds" - end - -- describe __tostring: - - it renders all elements of the array: - array = Array {1, 4, 9, 16, 25} - expect (tostring (array)).to_be 'Array {1, 4, 9, 16, 25}' - expect (tostring (Array ())).to_be 'Array {}' - -- describe pop: - - before: - array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} - - context when called as a module function: - - it diagnoses missing arguments: | - expect (Array.pop ()). - to_error "bad argument #1 to 'pop' (Array expected, got no value)" - - it returns nil for an empty array: - array = Array () - expect (Array.pop (array)).to_be (nil) - - it removes an element from the array: - count = array.length - repeat - expect (array.length).to_be (count) - count = count - 1 - until Array.pop (array) == nil - expect (array.length).to_be (0) - - it returns the removed element: - while array.length > 2 do - expect (Array.pop (array)).to_be (array[-1] + array[-2]) - end - - it does not perturb existing elements: - Array.pop (array) - for i = 3, array.length do - expect (array[i]).to_be (array[i -1] + array[i - 2]) - end - - context when called as an object method: - - before: - array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} - - it returns nil for an empty array: - array = Array () - expect (array:pop ()).to_be (nil) - - it removes an element from the array: - count = array.length - repeat - expect (array.length).to_be (count) - count = count - 1 - until array:pop () == nil - expect (array.length).to_be (0) - - it returns the removed element: - while array.length > 2 do - expect (array:pop ()).to_be (array[-1] + array[-2]) - end - - it does not perturb existing elements: - array:pop () - for i = 3, array.length do - expect (array[i]).to_be (array[i -1] + array[i - 2]) - end - -- describe push: - - context when called as a module function: - - before: - array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} - - it diagnoses missing arguments: | - expect (Array.push ()). - to_error "bad argument #1 to 'push' (Array expected, got no value)" - expect (Array.push (Array)). - to_error "bad argument #2 to 'push' (any value expected, got no value)" - - it diagnoses wrong argument types: | - expect (Array.push (1234)). - to_error "bad argument #1 to 'push' (Array expected, got number)" - - it adds a single element to an empty array: - array = Array () - Array.push (array, 42) - expect (array[1]).to_be (array[-1]) - - it adds an element to an array: - count = array.length - Array.push (array, 42) - expect (array[-1]).to_be (42) - expect (array.length).to_be (count + 1) - Array.push (array, -273) - expect (array[-1]).to_be (-273) - expect (array.length).to_be (count + 2) - - it does not perturb existing elements: - Array.push (array, 42) - for i = 3, array.length - 1 do - expect (array[i]).to_be (array[i - 1] + array[i - 2]) - end - - context when called as an object method: - - before: - array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} - - it diagnoses missing arguments: | - expect (array:push ()). - to_error "bad argument #2 to 'push' (any value expected, got no value)" - - it adds a single element to an empty array: - array = Array () - array:push (42) - expect (array[1]).to_be (array[-1]) - - it adds an element to an array: - count = array.length - array:push (42) - expect (array[-1]).to_be (42) - expect (array.length).to_be (count + 1) - array:push (-273) - expect (array[-1]).to_be (-273) - expect (array.length).to_be (count + 2) - - it returns pushed value: - expect (array:push (42)).to_be (42) - expect (array:push (-273)).to_be (-273) - - it does not perturb existing elements: - array:push (42) - for i = 3, array.length - 1 do - expect (array[i]).to_be (array[i - 1] + array[i -2]) - end - -- describe realloc: - - context when called as a module function: - - it diagnoses missing arguments: | - expect (Array.realloc ()). - to_error "bad argument #1 to 'realloc' (Array expected, got no value)" - expect (Array.realloc (array)). - to_error "bad argument #2 to 'realloc' (number expected, got no value)" - - it diagnoses wrong argument types: | - expect (Array.realloc (1234)). - to_error "bad argument #1 to 'realloc' (Array expected, got number)" - expect (Array.realloc (array, "string")). - to_error "bad argument #2 to 'realloc' (number expected, got string)" - - it reduces the number of usable elements: - array = Array (100) - Array.realloc (array, 50) - expect (array.length).to_be (50) - - it truncates existing elements when reducing size: - a = array (100) - Array.realloc (a, 50) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it increases the number of usable elements: - array = Array (50) - Array.realloc (array, 100) - expect (array.length).to_be (100) - - it does not perturb existing element values: - a = array (50) - Array.realloc (a, 100) - for i = 3, 50 do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it sets new elements to zero: - a = array (50) - Array.realloc (a, 100) - for i = 51, a.length do - expect (a[i]).to_be (0) - end - - context when called as an object method: - - it diagnoses missing arguments: | - expect (array:realloc ()). - to_error "bad argument #2 to 'realloc' (number expected, got no value)" - - it diagnoses wrong argument types: | - expect (array:realloc "string"). - to_error "bad argument #2 to 'realloc' (number expected, got string)" - - it reduces the number of usable elements: - array = Array (100):realloc (50) - expect (array.length).to_be (50) - - it truncates existing elements when reducing size: - a = array (100):realloc (50) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it increases the number of usable elements: - array = Array (50):realloc (100) - expect (array.length).to_be (100) - - it does not perturb existing element values: - a = array (50):realloc (100) - for i = 3, 50 do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it sets new elements to zero: - a = array (50):realloc (100) - for i = 51, a.length do - expect (a[i]).to_be (0) - end - -- describe set: - - context when called as a module function: - - it diagnoses missing arguments: | - expect (Array.set ()). - to_error "bad argument #1 to 'set' (Array expected, got no value)" - expect (Array.set (array)). - to_error "bad argument #2 to 'set' (number expected, got no value)" - expect (Array.set (array, 1)). - to_error "bad argument #3 to 'set' (any value expected, got no value)" - expect (Array.set (array, 1, 0)). - to_error "bad argument #4 to 'set' (number expected, got no value)" - - it diagnoses wrong argument types: | - expect (Array.set (100)). - to_error "bad argument #1 to 'set' (Array expected, got number)" - expect (Array.set (array, "bogus")). - to_error "bad argument #2 to 'set' (number expected, got string)" - expect (Array.set (array, 1, 0, function () end)). - to_error "bad argument #4 to 'set' (number expected, got function)" - - it changes the value of a subsequence of elements: - array = Array (100) - Array.set (array, 25, 1, 50) - for i = 1, array.length do - if i >= 25 and i < 75 then - expect (array[i]).to_be (1) - else - expect (array[i]).to_be (0) - end - end - - it understands negative from index: - array = Array (100) - Array.set (array, -50, 1, 50) - for i = 1, array.length do - if i <= 50 then - expect (array[i]).to_be (0) - else - expect (array[i]).to_be (1) - end - end - - it does not affect the prototype array elements: - a = array (100) - Array.set (a, 25, 1, 50) - for i = 3, array.length do - expect (array[i]).to_be (array[i - 1] + array[i - 2]) - end - - it does not affect elements outside range being set: - a = array (100) - Array.set (a, 25, 1, 50) - for i = 1, a.length do - if i >= 25 and i < 75 then - expect (a[i]).to_be (1) - elseif i <= array.length then - expect (a[i]).to_be (array[i]) - else - expect (a[i]).to_be (0) - end - end - - context when called as an object method: - - it diagnoses missing arguments: | - expect (array:set ()). - to_error "bad argument #2 to 'set' (number expected, got no value)" - expect (array:set (1)). - to_error "bad argument #3 to 'set' (any value expected, got no value)" - expect (array:set (1, 0)). - to_error "bad argument #4 to 'set' (number expected, got no value)" - - it diagnoses wrong argument types: | - expect (array:set "bogus"). - to_error "bad argument #2 to 'set' (number expected, got string)" - expect (array:set (1, 0, function () end)). - to_error "bad argument #4 to 'set' (number expected, got function)" - - it changes the value of a subsequence of elements: - array = Array (100):set (25, 1, 50) - for i = 1, array.length do - if i >= 25 and i < 75 then - expect (array[i]).to_be (1) - else - expect (array[i]).to_be (0) - end - end - - it understands negative from index: - array = Array (100):set (-50, 1, 50) - for i = 1, array.length do - if i <= 50 then - expect (array[i]).to_be (0) - else - expect (array[i]).to_be (1) - end - end - - it does not affect the prototype array elements: - a = array (100):set (25, 1, 50) - for i = 3, array.length do - expect (array[i]).to_be (array[i - 1] + array[i - 2]) - end - - it does not affect elements outside range being set: - a = array (100):set (25, 1, 50) - for i = 1, a.length do - if i >= 25 and i < 75 then - expect (a[i]).to_be (1) - elseif i <= array.length then - expect (a[i]).to_be (array[i]) - else - expect (a[i]).to_be (0) - end - end - -- describe shift: - - context when called as a module function: - - before: - array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} - - it diagnoses missing arguments: | - expect (Array.shift ()). - to_error "bad argument #1 to 'shift' (Array expected, got no value)" - - it returns nil for an empty array: - array = Array () - expect (Array.shift (array)).to_be (nil) - - it removes an element from the array: - count = array.length - repeat - expect (array.length).to_be (count) - count = count - 1 - until Array.shift (array) == nil - expect (array.length).to_be (0) - - it returns the removed element: - while array.length > 2 do - expect (Array.shift (array)).to_be (array[2] - array[1]) - end - - it shifts existing elements one position left: - shiftme = array () - Array.shift (shiftme) - for i = 1, shiftme.length do - expect (shiftme[i]).to_be (array[i + 1]) - end - - context when called as an object method: - - before: - array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} - - it returns nil for an empty array: - array = Array () - expect (array:shift ()).to_be (nil) - - it removes an element from the array: - count = array.length - repeat - expect (array.length).to_be (count) - count = count - 1 - until array:shift () == nil - expect (array.length).to_be (0) - - it returns the removed element: - while array.length > 2 do - expect (array:shift ()).to_be (array[2] - array[1]) - end - - it shifts existing elements one position left: - shiftme = array () - shiftme:shift () - for i = 1, shiftme.length do - expect (shiftme[i]).to_be (array[i + 1]) - end - -- describe unshift: - - context when called as a module function: - - before: - array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} - - it diagnoses missing arguments: | - expect (Array.unshift ()). - to_error "bad argument #1 to 'unshift' (Array expected, got no value)" - expect (Array.unshift (array)). - to_error "bad argument #2 to 'unshift' (any value expected, got no value)" - - it diagnoses wrong argument types: | - expect (Array.unshift (1234)). - to_error "bad argument #1 to 'unshift' (Array expected, got number)" - - it adds a single element to an empty array: - array = Array () - Array.unshift (array, 42) - expect (array[1]).to_be (array[-1]) - - it inserts an element into an array: - count = array.length - Array.unshift (array, 42) - expect (array[1]).to_be (42) - expect (array.length).to_be (count + 1) - Array.unshift (array, -273) - expect (array[1]).to_be (-273) - expect (array.length).to_be (count + 2) - - it shifts existing elements one position right: - unshiftme = array () - Array.unshift (unshiftme, 42) - for i = 1, array.length do - expect (unshiftme[i + 1]).to_be (array[i]) - end - - context when called as an object method: - - before: - array = Array {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89} - - it diagnoses missing arguments: | - expect (array:unshift ()). - to_error "bad argument #2 to 'unshift' (any value expected, got no value)" - - it adds a single element to an empty array: - array = Array () - array:unshift (42) - expect (array[1]).to_be (array[-1]) - - it adds an element to an array: - count = array.length - array:unshift (42) - expect (array[1]).to_be (42) - expect (array.length).to_be (count + 1) - array:unshift (-273) - expect (array[1]).to_be (-273) - expect (array.length).to_be (count + 2) - - it returns unshifted value: - expect (array:unshift (42)).to_be (42) - expect (array:unshift (-273)).to_be (-273) - - it shifts existing elements one position right: - unshiftme = array () - unshiftme:unshift (42) - for i = 1, array.length do - expect (unshiftme[i + 1]).to_be (array[i]) - end diff --git a/specs/specs.mk b/specs/specs.mk index 66a5b72..5653006 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -26,7 +26,6 @@ SPECL_OPTS = --unicode specl_SPECS = \ $(srcdir)/specs/array_spec.yaml \ $(srcdir)/specs/base_spec.yaml \ - $(srcdir)/specs/base_array_spec.yaml \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ $(srcdir)/specs/functional_spec.yaml \ From e851224a8af4576e1c48f8058b8584836cedccd6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 May 2014 10:40:01 +0700 Subject: [PATCH 179/703] maint: demonstrate new and legacy bind api correctly. Seems like I was confused over which functional.bind api was the old, and which was the new. Correct that. * specs/functional_spec.yaml (bind): Swap examples of new bind api labelled legacy and vice versa. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 2 +- specs/functional_spec.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 9bdedf8..c815c6b 100644 --- a/NEWS +++ b/NEWS @@ -27,7 +27,7 @@ Stdlib NEWS - User visible changes an error previously, it now prints "100" as expected. local function add (a, b) return a + b end - local incr = functional.bind (add, 1) + local incr = functional.bind (add, {1}) print (incr (99)) If you have any code that calls functions returned from `bind`, you diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 08cc5a4..2d31946 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -25,20 +25,20 @@ specify std.functional: expect (M.bind (false)). to_error "bad argument #1 to 'std.functional.bind' (function expected, got boolean)" - it does not affect normal operation if no arguments are bound: - expect (M.bind (math.min) (2, 3, 4)).to_be (2) + expect (M.bind (math.min, {}) (2, 3, 4)).to_be (2) - it takes the extra arguments into account: - expect (M.bind (math.min, 1, 0) (2, 3, 4)).to_be (0) + expect (M.bind (math.min, {1, 0}) (2, 3, 4)).to_be (0) - it appends final call arguments: - expect (M.bind (math.max, 2, 3) (4, 5, 1)).to_be (5) + expect (M.bind (math.max, {2, 3}) (4, 5, 1)).to_be (5) - it does not require all arguments in final call: div = function (a, b) return a / b end - expect (M.bind (div, 100) (25)).to_be (4) + expect (M.bind (div, {100}) (25)).to_be (4) - it supports out of order extra arguments: - expect (M.bind (math.pow, nil, 3) (2)).to_be (8) - - it supports the legacy api: - expect (M.bind (math.min, {}) (2, 3, 4)).to_be (2) - expect (M.bind (math.min, {1, 0}) (2, 3, 4)).to_be (0) expect (M.bind (math.pow, {[2] = 3}) (2)).to_be (8) + - it supports the legacy api: + expect (M.bind (math.min) (2, 3, 4)).to_be (2) + expect (M.bind (math.min, 1, 0) (2, 3, 4)).to_be (0) + expect (M.bind (math.pow, nil, 3) (2)).to_be (8) - describe case: From 6bac775826bb06fc8c4e8c3a28c989dcb5b97641 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 May 2014 19:42:45 +0700 Subject: [PATCH 180/703] array: support ipairs iteration over elements. * specs/array_spec.yaml (__ipairs): Specify behaviour of ipairs with arrays. * lib/std/array.lua (core_metatable.__ipairs) (alien_metatable.__ipairs): Implement ipairs support for table and alien.buffer managed table elements. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 24 ++++++++++++++++++++++++ specs/array_spec.yaml | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/lib/std/array.lua b/lib/std/array.lua index 0757475..fe24104 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -275,6 +275,20 @@ core_metatable = { end, + --- Iterate consecutively over all elements with `ipairs (array)`. + -- @function __ipairs + -- @treturn function iterator function + __ipairs = function (self) + local i, n = 0, self.length + return function () + i = i + 1 + if i <= n then + return i, self.buffer[i] + end + end + end, + + --- Return the `n`th character in this Array. -- @function __index -- @int n 1-based index, or negative to index starting from the right @@ -436,6 +450,16 @@ local alien_functions = { alien_metatable = { _type = "Array", + __ipairs = function (self) + local i, n = 0, self.length + return function () + i = i + 1 + if i <= n then + return i, self.buffer:get ((i - 1) * self.size + 1, self.type) + end + end + end, + __index = function (self, n) argscheck ("__index", {"Array", {"number", "string"}}, {self, n}) diff --git a/specs/array_spec.yaml b/specs/array_spec.yaml index 7ac5201..5fa9a7b 100644 --- a/specs/array_spec.yaml +++ b/specs/array_spec.yaml @@ -194,6 +194,41 @@ specify Array: end expect (a.allocated).to_be (0) + +- describe __ipairs: + - before: + -- Some luajit builds, and PUC RIO 5.1 don't respect __ipairs + local t = setmetatable ({}, { + __ipairs = function (self) + local i = 0 + return function () + i = i + 1 + if i == 1 then return 1, 42 end + end + end, + }) + meta__ipairs = false + for _ in ipairs (t) do meta__ipairs = true end + - context with alien buffer elements: + - it iterates over elements: + elements = {1, 4, 9, 16, 25} + array = Array ("int", elements) + if meta__ipairs then + local t = {} + for i, v in ipairs (array) do t[i] = v end + expect (t).to_equal (elements) + end + - context with table elements: + - it iterates over elements: + elements = {"a", "b", {{"c"}, "d"}, "e"} + array = Array ("any", elements) + if meta__ipairs then + local t = {} + for i, v in ipairs (array) do t[i] = v end + expect (t).to_equal (elements) + end + + - describe __len: - before: | -- Some luajit releases, and PUC RIO 5.1 don't respect __len From 5867e52775c9bcf882a2ea9256e671270143a9a0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 31 May 2014 21:31:53 +0700 Subject: [PATCH 181/703] io: don't pull in tree and dependencies with `require "io"`. * lib/std/io.lua (tree): Remove requirement. (base): Replace with much lighter module. (writelines): Call base.leaves instead of tree.ileaves. In addition to reducing coupling, this also saves another round of `argscheck`ing, and a callstack frame. Signed-off-by: Gary V. Vaughan --- lib/std/io.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/io.lua b/lib/std/io.lua index 4681126..d733878 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -3,8 +3,8 @@ @module std.io ]] +local base = require "std.base" local string = require "std.string" -local tree = require "std.tree" local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), @@ -59,7 +59,7 @@ local function writelines (h, ...) io.write (h, "\n") h = io.output () end - for v in tree.ileaves ({...}) do + for v in base.leaves (ipairs, {...}) do h:write (v, "\n") end end From 392153a56c80cd9fadf2fda8a2ac807272d29129 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 31 May 2014 21:14:39 +0700 Subject: [PATCH 182/703] debug: add file object matching to argcheck. * specs/debug_spec.yaml (argcheck): Specify behaviour when matching against a file type argument. * lib/std/debug.lua (argcheck): Add LDocs for file type. * lib/std/base.lua (argcheck): When the required type is "file" use io.type () to ensure that an open file object argument was passed. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 25 +++++++++++++++---------- lib/std/debug.lua | 3 ++- specs/debug_spec.yaml | 3 +++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 76415eb..b3d2a9f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -58,17 +58,29 @@ else -- Check actual has one of the types from expected local ok, actualtype = false, prototype (actual) for i, check in ipairs (expected) do - if check == "any" then + if check == "#table" then + if actualtype == "table" and next (actual) then + ok = true + end + + elseif check == "any" then expected[i] = "any value" if actual ~= nil then ok = true end - elseif check == "#table" then - if actualtype == "table" and next (actual) then + elseif check == "file" then + if io.type (actual) == "file" then ok = true end + elseif check == "function" then + if actualtype == "function" or + (getmetatable (actual) or {}).__call ~= nil + then + ok = true + end + elseif check == "list" then if typeof (actual) == "table" and #actual > 0 then ok = true @@ -79,13 +91,6 @@ else ok = true end - elseif check == "function" then - if actualtype == "function" or - (getmetatable (actual) or {}).__call ~= nil - then - ok = true - end - elseif check == actualtype then ok = true end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index ed223b1..687682e 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -118,10 +118,11 @@ local argerror = base.argerror -- or one of the special options below: -- -- #table accept any non-empty table +-- any accept any non-nil argument type +-- file accept an open file object -- function accept a function, or object with a __call metamethod -- list accept a table with a non-empty array part -- object accept any std.Object derived type --- any accept any non-nil argument type -- -- Call `argerror` if there is a type mismatch. -- diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index c30db2d..0a053ec 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -107,11 +107,13 @@ specify std.debug: to_contain_error "bad argument" - it diagnoses missing primitive types: expect (fn ("boolean", nil)).to_error "boolean expected, got no value" + expect (fn ("file", nil)).to_error "file expected, got no value" expect (fn ("number", nil)).to_error "number expected, got no value" expect (fn ("string", nil)).to_error "string expected, got no value" expect (fn ("table", nil)).to_error "table expected, got no value" - it diagnoses mismatched primitive types: expect (fn ("boolean", {0})).to_error "boolean expected, got table" + expect (fn ("file", {0})).to_error "file expected, got table" expect (fn ("number", {0})).to_error "number expected, got table" expect (fn ("string", {0})).to_error "string expected, got table" expect (fn ("table", false)).to_error "table expected, got boolean" @@ -119,6 +121,7 @@ specify std.debug: to_error "table expected, got Object" - it matches primitive Lua types: expect (fn ("boolean", true)).not_to_error () + expect (fn ("file", io.stderr)).not_to_error () expect (fn ("number", 1)).not_to_error () expect (fn ("string", "s")).not_to_error () expect (fn ("table", {})).not_to_error () From e8f7a51d58d40cebaa85df82aa441b5b18afa408 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Jun 2014 09:45:09 +0700 Subject: [PATCH 183/703] io: use argcheck for api functions. * specs/io_spec.yaml (catdir, catfile, die, monkey_patch) (process_files, readlines, shell, slurp, splitdir, warn) (writelines): Specify argument type mismatch messages. * lib/std/io.lua (catdir, catfile, die, monkey_patch) (process_files, readlines, shell, slurp, splitdir, warn) (writelines): Call argcheck to validate arguments and meet specifications. Signed-off-by: Gary V. Vaughan --- lib/std/io.lua | 75 ++++++++++++++++++++++++++++++++++++---------- specs/io_spec.yaml | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 16 deletions(-) diff --git a/lib/std/io.lua b/lib/std/io.lua index d733878..21096c8 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -10,11 +10,13 @@ local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } +local argcheck = base.argcheck + local M -- forward declaration -- Get an input file handle. --- @param h file handle or name (default: `io.input ()`) +-- @tparam[opt=io.input()] file|string h file handle or name -- @return file handle, or nil on error local function input_handle (h) if h == nil then @@ -25,10 +27,13 @@ local function input_handle (h) return h end + --- Slurp a file handle. --- @param h file handle or name (default: `io.input ()`) +-- @tparam[opt=io.input()] file|string h file handle or name -- @return contents of file or handle, or nil if error local function slurp (h) + argcheck ("std.io.slurp", 1, {"file", "string", "nil"}, h) + h = input_handle (h) if h then local s = h:read ("*a") @@ -37,11 +42,14 @@ local function slurp (h) end end + --- Read a file or file handle into a list of lines. --- @param h file handle or name (default: `io.input ()`); --- if h is a handle, the file is closed after reading +-- @tparam[opt=io.input()] file|string h file handle or name +-- if h is a file, that file is closed after reading -- @return list of lines local function readlines (h) + argcheck ("std.io.readlines", 1, {"file", "string", "nil"}, h) + h = input_handle (h) local l = {} for line in h:lines () do @@ -51,10 +59,13 @@ local function readlines (h) return l end + --- Write values adding a newline after each. --- @param h file handle (default: `io.output ()`) +-- @tparam[opt=io.output()] file|string h file handle or name -- @param ... values to write (as for write) local function writelines (h, ...) + argcheck ("std.io.writelines", 1, {"file", "string", "nil"}, h) + if io.type (h) ~= "file" then io.write (h, "\n") h = io.output () @@ -64,12 +75,15 @@ local function writelines (h, ...) end end + --- Overwrite core methods and metamethods with `std` enhanced versions. -- -- Adds `readlines` and `writelines` metamethods to core file objects. -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table local function monkey_patch (namespace) + argcheck ("std.io.monkey_patch", 1, "table", namespace) + namespace = namespace or _G assert (type (namespace) == "table", @@ -82,42 +96,63 @@ local function monkey_patch (namespace) return M end + --- Split a directory path into components. -- Empty components are retained: the root directory becomes `{"", ""}`. -- @param path path -- @return list of path components local function splitdir (path) + argcheck ("std.io.splitdir", 1, "string", path) + return string.split (path, package.dirsep) end + --- Concatenate one or more directories and a filename into a path. --- @param ... path components --- @return path +-- @string ... path components +-- @treturn string path local function catfile (...) - return table.concat ({...}, package.dirsep) + local t = {...} + for i, v in ipairs (t) do + argcheck ("std.io.catfile", i, "string", v) + end + + return table.concat (t, package.dirsep) end + --- Concatenate two or more directories into a path, removing the trailing slash. -- @param ... path components -- @return path local function catdir (...) - return (string.gsub (catfile (...), "^$", package.dirsep)) + t = {...} + for i, v in ipairs (t) do + argcheck ("std.io.catdir", i, "string", v) + end + + return (string.gsub (table.concat (t, package.dirsep), "^$", package.dirsep)) end + --- Perform a shell command and return its output. -- @param c command -- @return output, or nil if error local function shell (c) + argcheck ("std.io.shell", 1, "string", c) + return slurp (io.popen (c)) end + --- Process files specified on the command-line. -- If no files given, process `io.stdin`; in list of files, -- `-` means `io.stdin`. -- @todo Make the file list an argument to the function. --- @param f function to process files with, which is passed +-- @tparam function f function to process files with, which is passed -- `(name, arg_no)` local function process_files (f) + argcheck ("std.io.process_files", 1, "function", f) + -- N.B. "arg" below refers to the global array of command-line args if #arg == 0 then arg[#arg + 1] = "-" @@ -132,6 +167,7 @@ local function process_files (f) end 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 @@ -147,9 +183,12 @@ end -- require "std.io".warn "oh noes!" -- end -- --- @param ... arguments for format +-- @string msg format string +-- @param ... additional arguments to plug format string specifiers -- @see std.optparse:parse -local function warn (...) +local function warn (msg, ...) + argcheck ("std.io.warn", 1, "string", msg) + local prefix = "" if (prog or {}).name then prefix = prog.name .. ":" @@ -168,16 +207,20 @@ local function warn (...) end end if #prefix > 0 then prefix = prefix .. " " end - writelines (io.stderr, prefix .. string.format (...)) + writelines (io.stderr, prefix .. string.format (msg, ...)) end + --- Die with error. -- This function uses the same rules to build a message prefix -- as @{std.io.warn}. --- @param ... arguments for format +-- @string msg format string +-- @param ... additional arguments to plug format string specifiers -- @see std.io.warn -local function die (...) - warn (...) +local function die (msg, ...) + argcheck ("std.io.die", 1, "string", msg) + + warn (msg, ...) error () end diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 33ab3df..bf90f78 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -32,14 +32,34 @@ specify std.io: - describe catdir: + - it diagnoses wrong argument types: | + expect (M.catdir (false)). + to_error "bad argument #1 to 'std.io.catdir' (string expected, got boolean)" + expect (M.catdir ("", false)). + to_error "bad argument #2 to 'std.io.catdir' (string expected, got boolean)" + expect (M.catdir ("", "false", false)). + to_error "bad argument #3 to 'std.io.catdir' (string expected, got boolean)" - describe catfile: + - it diagnoses wrong argument types: | + expect (M.catfile (false)). + to_error "bad argument #1 to 'std.io.catfile' (string expected, got boolean)" + expect (M.catfile ("", false)). + to_error "bad argument #2 to 'std.io.catfile' (string expected, got boolean)" + expect (M.catfile ("", "false", false)). + to_error "bad argument #3 to 'std.io.catfile' (string expected, got boolean)" - describe die: - before: script = [[require "std.io".die "By 'eck!"]] + - it diagnoses missing arguments: | + expect (M.die ()). + to_error "bad argument #1 to 'std.io.die' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.die (false)). + to_error "bad argument #1 to 'std.io.die' (string expected, got boolean)" - it outputs a message to stderr: expect (luaproc (script)).to_fail_with "By 'eck!\n" - it ignores `prog.line` without `prog.file` or `prog.name`: @@ -102,6 +122,12 @@ specify std.io: stderr = setmetatable ({}, mt), }, } + - it diagnoses missing arguments: | + expect (M.monkey_patch ()). + to_error "bad argument #1 to 'std.io.monkey_patch' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.monkey_patch (false)). + to_error "bad argument #1 to 'std.io.monkey_patch' (table expected, got boolean)" - it installs readlines metamethod: f (t) expect (mt.readlines).to_be (M.readlines) @@ -113,23 +139,53 @@ specify std.io: - describe process_files: + - it diagnoses missing arguments: | + expect (M.process_files ()). + to_error "bad argument #1 to 'std.io.process_files' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.process_files (false)). + to_error "bad argument #1 to 'std.io.process_files' (function expected, got boolean)" - describe readlines: + - it diagnoses wrong argument types: | + expect (M.readlines (false)). + to_error "bad argument #1 to 'std.io.readlines' (file, string or nil expected, got boolean)" - describe shell: + - it diagnoses missing arguments: | + expect (M.shell ()). + to_error "bad argument #1 to 'std.io.shell' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.shell (false)). + to_error "bad argument #1 to 'std.io.shell' (string expected, got boolean)" - describe slurp: + - it diagnoses wrong argument types: | + expect (M.slurp (false)). + to_error "bad argument #1 to 'std.io.slurp' (file, string or nil expected, got boolean)" - describe splitdir: + - it diagnoses missing arguments: | + expect (M.splitdir ()). + to_error "bad argument #1 to 'std.io.splitdir' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.splitdir (false)). + to_error "bad argument #1 to 'std.io.splitdir' (string expected, got boolean)" - describe warn: - before: script = [[require "std.io".warn "Ayup!"]] + - it diagnoses missing arguments: | + expect (M.warn ()). + to_error "bad argument #1 to 'std.io.warn' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.warn (false)). + to_error "bad argument #1 to 'std.io.warn' (string expected, got boolean)" - 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`: @@ -179,3 +235,6 @@ specify std.io: - describe writelines: + - it diagnoses wrong argument types: | + expect (M.writelines (false)). + to_error "bad argument #1 to 'std.io.writelines' (file, string or nil expected, got boolean)" From 23c8b93dd3e6753fb142987dfe3abf6f9dc74bbb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Jun 2014 19:02:15 +0700 Subject: [PATCH 184/703] object: enhance prototype to recognize file objects. * specs/object_spec.yaml (prototype): Specify results of passing file handles and Lua primitives to prototype. * lib/std/object.lua (prototype): Improve LDocs. * lib/std/base.lua (prototype): Enhance implementation to meet new specifications. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/base.lua | 4 ++-- lib/std/object.lua | 36 +++++++++++++++++++++++------------- specs/object_spec.yaml | 12 ++++++++++++ 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index c815c6b..8416e23 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,9 @@ Stdlib NEWS - User visible changes ** New features: + - `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`. diff --git a/lib/std/base.lua b/lib/std/base.lua index b3d2a9f..eeb1a3a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -3,9 +3,9 @@ local typeof = type --- Doc-commented in container.lua +-- Doc-commented in object.lua local function prototype (o) - return (getmetatable (o) or {})._type or type (o) + return (getmetatable (o) or {})._type or io.type (o) or type (o) end diff --git a/lib/std/object.lua b/lib/std/object.lua index 32f57fe..6678cae 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -102,23 +102,33 @@ return Container { -- string valued `_type` field, which can then be queried using this -- function. -- - -- Stack = Object { - -- _type = "Stack", - -- - -- __tostring = function (self) ... end, - -- - -- __index = { - -- push = function (self) ... end, - -- pop = function (self) ... end, - -- }, - -- } - -- stack = Stack {} - -- - -- stack:prototype () --> "Stack" + -- Additionally, this function returns the results of `io.type` for + -- file objects, or `type` otherwise. -- -- @function prototype -- @param x anything -- @treturn string type of `x` + -- @usage + -- local Stack = Object { + -- _type = "Stack", + -- + -- __tostring = function (self) ... end, + -- + -- __index = { + -- push = function (self) ... end, + -- pop = function (self) ... end, + -- }, + -- } + -- local stack = Stack {} + -- assert (stack:prototype () == getmetatable (stack)._type) + -- + -- local prototype = Object.prototype + -- assert (prototype (stack) == getmetatable (stack)._type) + -- + -- local h = io.open (os.tmpname (), "w") + -- assert (prototype (h) == io.type (h)) + -- + -- assert (prototype {} == type {}) prototype = Container.prototype.call, diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 964318f..e93baea 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -53,6 +53,18 @@ specify std.object: p = Portal {} expect (prototype (p)).to_be "Demon" expect (prototype (p {})).to_be "Demon" + - it recognizes a file object: + h = io.open (os.tmpname ()) + expect (prototype (h)).to_be "file" + h:close () + expect (prototype (h)).to_be "closed file" + - it recognizes a primitive object: + expect (prototype (nil)).to_be "nil" + expect (prototype (false)).to_be "boolean" + expect (prototype (0.0)).to_be "number" + expect (prototype "0.0").to_be "string" + expect (prototype (function () end)).to_be "function" + expect (prototype {}).to_be "table" - context when called as an object method: - it reports the type stored in the object's metatable: expect (o:prototype ()).to_be "Object" From a7586d1a56122488cc9c40177c9cf8e371389098 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 2 Jun 2014 09:24:39 +0700 Subject: [PATCH 185/703] io: complete specs and improve LDocs. * lib/std/io.lua (catdir, catfile, die, monkey_patch) (process_files, readlines, shell, slurp, splitdir, warn) (writelines): Improve LDocs with usage examples and cross- references. * specs/io_spec.yaml (catdir, catfile, die, monkey_patch) (process_files, readlines, shell, slurp, splitdir, warn) (writelines): Add missing specificatons. * specs/spec_helper.yaml (concat_file_content): New function to support new specifications. (luaproc): Support subprocess arguments and standard input. Signed-off-by: Gary V. Vaughan --- lib/std/io.lua | 107 ++++++++++++++++++--------- specs/io_spec.yaml | 152 +++++++++++++++++++++++++++++++++++++++ specs/spec_helper.lua.in | 35 +++++++-- 3 files changed, 255 insertions(+), 39 deletions(-) diff --git a/lib/std/io.lua b/lib/std/io.lua index 21096c8..0b84dc4 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -10,31 +10,36 @@ local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } -local argcheck = base.argcheck +local argcheck, argerror = base.argcheck, base.argerror local M -- forward declaration --- Get an input file handle. +--- Get an input file handle. -- @tparam[opt=io.input()] file|string h file handle or name -- @return file handle, or nil on error local function input_handle (h) if h == nil then - h = io.input () + return io.input () elseif type (h) == "string" then - h = io.open (h) + return io.open (h) end return h end --- Slurp a file handle. --- @tparam[opt=io.input()] file|string h file handle or name +-- @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 -local function slurp (h) - argcheck ("std.io.slurp", 1, {"file", "string", "nil"}, h) +-- @see std.io.process_files +-- @usage contents = slurp (filename) +local function slurp (file) + argcheck ("std.io.slurp", 1, {"file", "string", "nil"}, file) + + local h, err = input_handle (file) + if h == nil then argerror ("std.io.slurp", 1, err, 2) end - h = input_handle (h) if h then local s = h:read ("*a") h:close () @@ -44,13 +49,16 @@ end --- Read a file or file handle into a list of lines. --- @tparam[opt=io.input()] file|string h file handle or name --- if h is a file, that file is closed after reading +-- @tparam[opt=io.input()] file|string file file handle or name +-- if file is a file handle, that file is closed after reading -- @return list of lines -local function readlines (h) - argcheck ("std.io.readlines", 1, {"file", "string", "nil"}, h) +-- @usage list = readlines "/etc/passwd" +local function readlines (file) + argcheck ("std.io.readlines", 1, {"file", "string", "nil"}, file) + + local h, err = input_handle (file) + if h == nil then argerror ("std.io.readlines", 1, err, 2) end - h = input_handle (h) local l = {} for line in h:lines () do l[#l + 1] = line @@ -61,8 +69,10 @@ end --- Write values adding a newline after each. --- @tparam[opt=io.output()] file|string h file handle or name +-- @tparam[opt=io.output()] file h file handle or name +-- the file is **not** closed after writing -- @param ... values to write (as for write) +-- @usage writelines (io.stdout, "first line", "next line") local function writelines (h, ...) argcheck ("std.io.writelines", 1, {"file", "string", "nil"}, h) @@ -81,6 +91,7 @@ end -- Adds `readlines` and `writelines` metamethods to core file objects. -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table +-- @usage local io = require "std.io".monkey_patch () local function monkey_patch (namespace) argcheck ("std.io.monkey_patch", 1, "table", namespace) @@ -101,6 +112,8 @@ end -- Empty components are retained: the root directory becomes `{"", ""}`. -- @param path path -- @return list of path components +-- @see std.io.catdir +-- @usage dir_components = splitdir (filepath) local function splitdir (path) argcheck ("std.io.splitdir", 1, "string", path) @@ -111,8 +124,14 @@ end --- Concatenate one or more directories and a filename into a path. -- @string ... path components -- @treturn string path +-- @see std.io.catdir +-- @see std.io.splitdir +-- @usage filepath = catfile ("relative", "path", "filename") local function catfile (...) local t = {...} + if #t == 0 then + argcheck ("std.io.catfile", 1, "string", nil) + end for i, v in ipairs (t) do argcheck ("std.io.catfile", i, "string", v) end @@ -121,9 +140,11 @@ local function catfile (...) end ---- Concatenate two or more directories into a path, removing the trailing slash. +--- Concatenate directory names into a path. -- @param ... path components --- @return path +-- @return path without trailing separator +-- @see std.io.catfile +-- @usage dirpath = catdir ("", "absolute", "directory") local function catdir (...) t = {...} for i, v in ipairs (t) do @@ -135,8 +156,11 @@ end --- Perform a shell command and return its output. --- @param c command --- @return output, or nil if error +-- @string c command +-- @treturn string output, or nil if error +-- @see std.io.slurp +-- @see os.execute +-- @usage users = shell [[cat /etc/passwd | awk -F: '{print $1;}']] local function shell (c) argcheck ("std.io.shell", 1, "string", c) @@ -144,14 +168,28 @@ local function shell (c) end +------ +-- Signature of `process_files` callback function. +-- @function process_files_callback +-- @string[opt] filename filename +-- @int[opt] i argument number of *filename* + + --- Process files specified on the command-line. --- If no files given, process `io.stdin`; in list of files, --- `-` means `io.stdin`. +-- 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. --- @tparam function f function to process files with, which is passed --- `(name, arg_no)` -local function process_files (f) - argcheck ("std.io.process_files", 1, "function", f) +-- @tparam process_files_callback fn callback function for each file +-- argument +-- @see std.io.process_files_callback +-- @usage #! /usr/bin/env lua +-- -- minimal cat command +-- local io = require "std.io" +-- io.process_files (function () io.write (io.slurp ()) end) +local function process_files (fn) + argcheck ("std.io.process_files", 1, "function", fn) -- N.B. "arg" below refers to the global array of command-line args if #arg == 0 then @@ -163,7 +201,7 @@ local function process_files (f) else io.input (v) end - f (v, i) + fn (v, i) end end @@ -174,18 +212,18 @@ end -- if there is a global `opts` table, prefix the message with -- `opts.program` and `opts.line` if any. @{std.optparse:parse} -- returns an `opts` table that provides the required `program` --- field, as long as you assign it back to `_G.opts`: --- --- 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 --- +-- field, as long as you assign it back to `_G.opts`. -- @string msg format string -- @param ... additional arguments to plug format string specifiers -- @see std.optparse:parse +-- @see std.io.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 local function warn (msg, ...) argcheck ("std.io.warn", 1, "string", msg) @@ -217,6 +255,7 @@ end -- @string msg format string -- @param ... additional arguments to plug format string specifiers -- @see std.io.warn +-- @usage die ("oh noes! (%s)", tostring (obj)) local function die (msg, ...) argcheck ("std.io.die", 1, "string", msg) diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index bf90f78..3fe9bbd 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -32,6 +32,8 @@ specify std.io: - describe catdir: + - before: + dirsep = string.match (package.config, "^([^\n]+)\n") - it diagnoses wrong argument types: | expect (M.catdir (false)). to_error "bad argument #1 to 'std.io.catdir' (string expected, got boolean)" @@ -39,9 +41,26 @@ specify std.io: to_error "bad argument #2 to 'std.io.catdir' (string expected, got boolean)" expect (M.catdir ("", "false", false)). to_error "bad argument #3 to 'std.io.catdir' (string expected, got boolean)" + - it treats no arguments as root directory: + expect (M.catdir ()).to_be (dirsep) + - it treats initial empty string as root directory: + expect (M.catdir ("")).to_be (dirsep) + expect (M.catdir ("", "")).to_be (dirsep) + expect (M.catdir ("", "root")).to_be (dirsep .. "root") + - it returns a single argument unchanged: + expect (M.catdir ("hello")).to_be "hello" + - it joins multiple arguments with platform directory separator: + expect (M.catdir ("one", "two")).to_be ("one" .. dirsep .. "two") + expect (M.catdir ("1", "2", "3", "4", "5")). + to_be (table.concat ({"1", "2", "3", "4", "5"}, dirsep)) - describe catfile: + - before: + dirsep = string.match (package.config, "^([^\n]+)\n") + - it diagnoses missing arguments: | + expect (M.catfile ()). + to_error "bad argument #1 to 'std.io.catfile' (string expected, got no value)" - it diagnoses wrong argument types: | expect (M.catfile (false)). to_error "bad argument #1 to 'std.io.catfile' (string expected, got boolean)" @@ -49,6 +68,16 @@ specify std.io: to_error "bad argument #2 to 'std.io.catfile' (string expected, got boolean)" expect (M.catfile ("", "false", false)). to_error "bad argument #3 to 'std.io.catfile' (string expected, got boolean)" + - it treats initial empty string as root directory: + expect (M.catfile ("", "")).to_be (dirsep) + expect (M.catfile ("", "root")).to_be (dirsep .. "root") + - it returns a single argument unchanged: + expect (M.catfile ("")).to_be "" + expect (M.catfile ("hello")).to_be "hello" + - it joins multiple arguments with platform directory separator: + expect (M.catfile ("one", "two")).to_be ("one" .. dirsep .. "two") + expect (M.catfile ("1", "2", "3", "4", "5")). + to_be (table.concat ({"1", "2", "3", "4", "5"}, dirsep)) - describe die: @@ -139,18 +168,77 @@ specify std.io: - describe process_files: + - before: + name = "specs/spec_helper.lua" + names = {"Makefile", "config.log", "config.status", "specs/spec_helper.lua"} + ascript = [[ + require "std.io".process_files (function (a) print (a) end) + ]] + 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) + ]] - it diagnoses missing arguments: | expect (M.process_files ()). to_error "bad argument #1 to 'std.io.process_files' (function expected, got no value)" - it diagnoses wrong argument types: | expect (M.process_files (false)). to_error "bad argument #1 to 'std.io.process_files' (function expected, got boolean)" + expect (luaproc (ascript, "not-an-existing-file")). + to_contain_error "cannot open file 'not-an-existing-file'" + - 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\n4\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 = "specs/spec_helper.lua" + h = io.open (name) + lines = {} for l in h:lines () do lines[#lines + 1] = l end + h:close () + + defaultin = io.input () + - after: + if io.type (defaultin) ~= "closed file" then io.input (defaultin) end - it diagnoses wrong argument types: | expect (M.readlines (false)). to_error "bad argument #1 to 'std.io.readlines' (file, string or nil expected, got boolean)" + expect (M.readlines "not-an-existing-file"). + to_error "bad argument #1 to 'std.io.readlines' (" -- system dependent error message + - it diagnoses closed file argument: | + closed = io.open (name, "r") closed:close () + expect (M.readlines (closed)). + to_error "bad argument #1 to 'std.io.readlines' (file, string or nil expected, got closed file)" + - it closes file handle upon completion: + h = io.open (name) + expect (io.type (h)).not_to_be "closed file" + M.readlines (h) + expect (io.type (h)).to_be "closed file" + - it reads lines from an existing named file: + expect (M.readlines (name)).to_equal (lines) + - it reads lines from an open file handle: + expect (M.readlines (io.open (name))).to_equal (lines) + - it reads from default input stream with no arguments: + io.input (name) + expect (M.readlines ()).to_equal (lines) - describe shell: @@ -160,21 +248,58 @@ specify std.io: - it diagnoses wrong argument types: | expect (M.shell (false)). to_error "bad argument #1 to 'std.io.shell' (string expected, got boolean)" + - it returns the output from a shell command string: + expect (M.shell [[printf '%s\n' 'foo' 'bar']]).to_be "foo\nbar\n" - describe slurp: + - before: | + name = "specs/spec_helper.lua" + h = io.open (name) + content = h:read "*a" + h:close () + + defaultin = io.input () + - after: + if io.type (defaultin) ~= "closed file" then io.input (defaultin) end - it diagnoses wrong argument types: | expect (M.slurp (false)). to_error "bad argument #1 to 'std.io.slurp' (file, string or nil expected, got boolean)" + expect (M.slurp "not-an-existing-file"). + to_error "bad argument #1 to 'std.io.slurp' (" -- system dependent error message + - it reads content from an existing named file: + expect (M.slurp (name)).to_be (content) + - it reads content from an open file handle: + expect (M.slurp (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" + M.slurp (h) + expect (io.type (h)).to_be "closed file" + - it reads from default input stream with no arguments: + io.input (name) + expect (M.slurp ()).to_be (content) - describe splitdir: + - before: + dirsep = string.match (package.config, "^([^\n]+)\n") - it diagnoses missing arguments: | expect (M.splitdir ()). to_error "bad argument #1 to 'std.io.splitdir' (string expected, got no value)" - it diagnoses wrong argument types: | expect (M.splitdir (false)). to_error "bad argument #1 to 'std.io.splitdir' (string expected, got boolean)" + - it returns a filename as a one element list: + expect (M.splitdir ("hello")).to_equal {"hello"} + - it splits root directory in two empty elements: + expect (M.splitdir (dirsep)).to_equal {"", ""} + - it returns initial empty string for absolute path: + expect (M.splitdir (dirsep .. "root")).to_equal {"", "root"} + - it returns multiple components split at platform directory separator: + expect (M.splitdir ("one" .. dirsep .. "two")).to_equal {"one", "two"} + expect (M.splitdir (table.concat ({"1", "2", "3", "4", "5"}, dirsep))). + to_equal {"1", "2", "3", "4", "5"} - describe warn: @@ -235,6 +360,33 @@ specify std.io: - describe writelines: + - before: | + name = os.tmpname () + h = io.open (name, "w") + lines = M.readlines (io.open "specs/spec_helper.lua") + + defaultout = io.output () + - after: + if io.type (defaultout) ~= "closed file" then io.output (defaultout) end + h:close () + os.remove (name) - it diagnoses wrong argument types: | expect (M.writelines (false)). to_error "bad argument #1 to 'std.io.writelines' (file, string or nil expected, got boolean)" + - it diagnoses closed file argument: | + closed = io.open (name) closed:close () + expect (M.writelines (closed)). + to_error "bad argument #1 to 'std.io.writelines' (file, string or nil expected, got closed file)" + - it does not close the file handle upon completion: + expect (io.type (h)).not_to_be "closed file" + M.writelines (h, "foo") + expect (io.type (h)).not_to_be "closed file" + - it writes lines to an open file handle: + M.writelines (h, unpack (lines)) + h:flush () + expect (M.readlines (io.open (name))).to_equal (lines) + - it writes to default output stream with non-file first argument: + io.output (h) + M.writelines (unpack (lines)) + h:flush () + expect (M.readlines (io.open (name))).to_equal (lines) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 57b637c..91ea6d0 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -18,16 +18,41 @@ local function mkscript (code) return f end -function luaproc (code) + +--- 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) - local proc = hell.spawn { - LUA, f; - env = { LUA_PATH=package.path, LUA_INIT="", LUA_INIT_5_2="" }, - } + 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 + +--- 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 From cb46a549bf5f738a1f3053499f68f6030f079cba Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 2 Jun 2014 09:24:39 +0700 Subject: [PATCH 186/703] std: improve LDocs. * lib/std.lua.in: Improve LDocs with usage examples and clearer language. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 74b8eb3..eda66fe 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -11,9 +11,7 @@ stdlib from the REPL, or in a prototype where you want to throw caution to the wind and compatibility with other modules be damned, for example. In that case, you can give stdlib permission to scribble all over your - namespaces with: - - local std = require "std".monkey_patch () + namespaces with the various `monkey_patch` calls in the library. @todo Write a style guide (indenting/wrapping, capitalisation, function and variable names); library functions should call @@ -34,6 +32,7 @@ local M -- forward declaration -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table +-- @usage local std = require "std".monkey_patch () local function monkey_patch (namespace) namespace = namespace or _G @@ -56,6 +55,7 @@ end -- @function barrel -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table module table +-- @usage local std = require "std".barrel () local function barrel (namespace) namespace = namespace or _G @@ -91,10 +91,7 @@ end -- -- Lazy load 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: --- --- local std = require "std" --- local prototype = std.container.prototype +-- metatable will automatically require submodules on first reference. -- @table std -- @field version release version string local version = "General Lua libraries / @VERSION@" @@ -117,6 +114,9 @@ return setmetatable (M, { -- @function __index -- @string name submodule name -- @return the submodule that was loaded to satisfy the missing `name` + -- @usage + -- local std = require "std" + -- local prototype = std.object.prototype __index = function (self, name) local ok, t = pcall (require, "std." .. name) if ok then From 37160005cee99fd6e0a1d7591bb0382f803191e3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 15:31:56 +0700 Subject: [PATCH 187/703] specs: account for different error messages between 5.1 and 5.2. * specs/io_spec.yaml (process_files): Accept either of the error message formats for passing a non-existent file to io.input for Lua 5.1 or Lua 5.2. Signed-off-by: Gary V. Vaughan --- specs/io_spec.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 3fe9bbd..b6556c6 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -186,8 +186,10 @@ specify std.io: - it diagnoses wrong argument types: | expect (M.process_files (false)). to_error "bad argument #1 to 'std.io.process_files' (function expected, got boolean)" - expect (luaproc (ascript, "not-an-existing-file")). - to_contain_error "cannot open file 'not-an-existing-file'" + 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 + } - it defaults to `-` if no arguments were passed: expect (luaproc (ascript)).to_output "-\n" - it iterates over arguments with supplied function: From e7ccd0b2cd9f2a4c6fd845ca5dafda655d1a242e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 2 Jun 2014 09:24:39 +0700 Subject: [PATCH 188/703] debug: complete specs and improve LDocs. * lib/std/debug.lua (__call, _DEBUG, argcheck, argerror) (argscheck, say, trace): Improve LDocs with usage examples and cross-references. * specs/debug_spec.yaml (_DEBUG): Remove. _DEBUG behaviours are specified in the api calls that are affected by it. (say, trace): Add missing specificatons. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 44 ++++++++-- specs/debug_spec.yaml | 181 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 214 insertions(+), 11 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 687682e..f28b892 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -1,5 +1,5 @@ --[[-- - Additions to the debug module. + Additions to the core debug module. The behaviour of the functions in this module are controlled by the value of the global `_DEBUG`. Not setting `_DEBUG` prior to requiring any of @@ -38,11 +38,20 @@ local string = require "std.string" -- @field argcheck honor argcheck and argscheck calls -- @field call do call trace debugging -- @field level debugging level +-- @usage _DEBUG = { argcheck = false, level = 9 } ---- Print a debugging message. --- @param n debugging level, defaults to 1 +--- Print a debugging message to `io.stderr`. +-- Display arguments passed through `std.string.tostring` and separated by tab +-- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` +-- is a number greater than or equal to *n*. If `_DEBUG` is false or +-- nil, nothing is written. +-- @int[opt=1] n debugging level, smaller is higher priority -- @param ... objects to print (as for print) +-- @usage +-- local _DEBUG = require "std.debug_init"._DEBUG +-- _DEBUG.level = 3 +-- say (2, "_DEBUG table contents:", _DEBUG) local function say (n, ...) local level = 1 local arg = {n, ...} @@ -58,14 +67,17 @@ local function say (n, ...) end end + +local level = 0 + --- Trace function calls. -- Use as debug.sethook (trace, "cr"), which is done automatically --- when _DEBUG.call is set. +-- when `_DEBUG.call` is set. -- Based on test/trace-calls.lua from the Lua distribution. --- @class function --- @name trace --- @param event event causing the call -local level = 0 +-- @string event event causing the call +-- @usage +-- _DEBUG = { call = true } +-- local debug = require "std.debug" local function trace (event) local t = debug.getinfo (3) local s = " >>> " .. string.rep (" ", level) @@ -108,6 +120,11 @@ end -- @int i argument number -- @string[opt] extramsg additional text to append to message inside parentheses -- @int[opt=1] level call stack level to blame for the error +-- @usage +-- local function slurp (file) +-- local h, err = input_handle (file) +-- if h == nil then argerror ("std.io.slurp", 1, err, 2) end +-- ... local argerror = base.argerror @@ -135,6 +152,10 @@ local argerror = base.argerror -- @tparam table|string expected a list of acceptable argument types -- @param actual argument passed -- @int[opt=2] level call stack level to blame for the error +-- @usage +-- local function case (with, branches) +-- argcheck ("std.functional.case", 2, "#table", branches) +-- ... local argcheck = base.argcheck @@ -143,6 +164,10 @@ local argcheck = base.argcheck -- @string name function to blame in error message -- @tparam table|string expected a list of lists of acceptable argument types -- @tparam table|any actual argument value, or table of argument values +-- @usage +-- local function curry (f, n) +-- argscheck ("std.functional.curry", {"function", "number"}, {f, n}) +-- ... local argscheck = base.argscheck @@ -163,6 +188,9 @@ end --- Equivalent to calling `debug.say (1, ...)` -- @function debug -- @see say +-- @usage +-- local debug = require "std.debug" +-- debug "oh noes!" local metatable = { __call = function (self, ...) say (1, ...) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 0a053ec..ae11c69 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -29,9 +29,6 @@ specify std.debug: to_equal {} -- describe _DEBUG: - - - describe argerror: - before: | fn = M.argerror @@ -290,9 +287,187 @@ specify std.debug: - describe debug: + - before: | + function mkwrap (x) + local fmt = "%s" + if type (x) == "string" then fmt = "%q" end + return string.format (fmt, require "std.string".tostring (x)) + end + + function mkdebug (debugp, ...) + return string.format ([[ + _DEBUG = %s + require "std.debug" (%s) + ]], + require "std.string".tostring (debugp), + table.concat (require "std.list".map (mkwrap, {...}), ", ")) + end + - it does nothing when _DEBUG is disabled: + expect (luaproc (mkdebug (false, "nothing to see here"))). + not_to_contain_error "nothing to see here" + - it writes to stderr when _DEBUG is not set: + expect (luaproc (mkdebug (nil, "debugging"))). + to_contain_error "debugging" + - it writes to stderr when _DEBUG is enabled: + expect (luaproc (mkdebug (true, "debugging"))). + to_contain_error "debugging" + - it writes to stderr when _DEBUG.level is not set: + expect (luaproc (mkdebug ({}, "debugging"))). + to_contain_error "debugging" + - it writes to stderr when _DEBUG.level is specified: + expect (luaproc (mkdebug ({level = 0}, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mkdebug ({level = 1}, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mkdebug ({level = 2}, "debugging"))). + to_contain_error "debugging" - describe say: + - before: | + function mkwrap (x) + local fmt = "%s" + if type (x) == "string" then fmt = "%q" end + return string.format (fmt, require "std.string".tostring (x)) + end + + function mksay (debugp, ...) + return string.format ([[ + _DEBUG = %s + require "std.debug".say (%s) + ]], + require "std.string".tostring (debugp), + table.concat (require "std.list".map (mkwrap, {...}), ", ")) + end + - context when _DEBUG is disabled: + - it does nothing when message level is not set: + expect (luaproc (mksay (false, "nothing to see here"))). + not_to_contain_error "nothing to see here" + - it does nothing when message is set: + expect (luaproc (mksay (false, -999, "nothing to see here"))). + not_to_contain_error "nothing to see here" + expect (luaproc (mksay (false, 0, "nothing to see here"))). + not_to_contain_error "nothing to see here" + expect (luaproc (mksay (false, 1, "nothing to see here"))). + not_to_contain_error "nothing to see here" + expect (luaproc (mksay (false, 2, "nothing to see here"))). + not_to_contain_error "nothing to see here" + expect (luaproc (mksay (false, 999, "nothing to see here"))). + not_to_contain_error "nothing to see here" + - context when _DEBUG is not set: + - it writes to stderr when message level is not set: + expect (luaproc (mksay (nil, "debugging"))). + to_contain_error "debugging" + - it writes to stderr when message level is 1 or lower: + expect (luaproc (mksay (nil, -999, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mksay (nil, 0, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mksay (nil, 1, "debugging"))). + to_contain_error "debugging" + - it does nothing when message level is 2 or higher: + expect (luaproc (mksay (nil, 2, "nothing to see here"))). + not_to_contain_error "nothing to see here" + expect (luaproc (mksay (nil, 999, "nothing to see here"))). + not_to_contain_error "nothing to see here" + - context when _DEBUG is enabled: + - it writes to stderr when message level is not set: + expect (luaproc (mksay (true, "debugging"))). + to_contain_error "debugging" + - it writes to stderr when message level is 1 or lower: + expect (luaproc (mksay (true, -999, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mksay (true, 0, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mksay (true, 1, "debugging"))). + to_contain_error "debugging" + - it does nothing when message level is 2 or higher: + expect (luaproc (mksay (true, 2, "nothing to see here"))). + not_to_contain_error "nothing to see here" + expect (luaproc (mksay (true, 999, "nothing to see here"))). + not_to_contain_error "nothing to see here" + - context when _DEBUG.level is not set: + - it writes to stderr when message level is not set: + expect (luaproc (mksay ({}, "debugging"))). + to_contain_error "debugging" + - it writes to stderr when message level is 1 or lower: + expect (luaproc (mksay ({}, -999, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mksay ({}, 0, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mksay ({}, 1, "debugging"))). + to_contain_error "debugging" + - it does nothing when message level is 2 or higher: + expect (luaproc (mksay ({}, 2, "nothing to see here"))). + not_to_contain_error "nothing to see here" + expect (luaproc (mksay ({}, 999, "nothing to see here"))). + not_to_contain_error "nothing to see here" + - context when _DEBUG.level is specified: + - it writes to stderr when message level is 1 or lower: + expect (luaproc (mksay ({level = 0}, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mksay ({level = 1}, "debugging"))). + to_contain_error "debugging" + expect (luaproc (mksay ({level = 2}, "debugging"))). + to_contain_error "debugging" + - it does nothing when message level is higher than debug level: + expect (luaproc (mksay ({level = 2}, 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 (mksay ({level = 2}, 2, "debugging"))). + to_contain_error "debugging" + - it writes to stderr when message level is lower than debug level: + expect (luaproc (mksay ({level = 2}, 1, "debugging"))). + to_contain_error "debugging" - describe trace: + - it does nothing when _DEBUG is disabled: + expect (luaproc [[ + _DEBUG = false + require "std.debug" + os.exit (0) + ]]).to_succeed_with "" + - it does nothing when _DEBUG is not set: + expect (luaproc [[ + require "std.debug" + os.exit (0) + ]]).to_succeed_with "" + - it does nothing when _DEBUG is enabled: + expect (luaproc [[ + _DEBUG = true + require "std.debug" + os.exit (0) + ]]).to_succeed_with "" + - it enables automatically when _DEBUG.call is set: | + expect (luaproc [[ + _DEBUG = {call = true} + local debug = 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$" From 427c305d48f7a648f10a8e373c1abf006881919c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 15:45:04 +0700 Subject: [PATCH 189/703] maint: use 2 blank lines between function definitions. * lib/std/package.lua, lib/std/strict.lua, lib/std/string.lua: Use 2 blank lines between function definitions. Signed-off-by: Gary V. Vaughan --- lib/std/package.lua | 1 + lib/std/strict.lua | 3 +++ lib/std/string.lua | 26 +++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/std/package.lua b/lib/std/package.lua index 4034aa8..dfa0ee1 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -159,6 +159,7 @@ M = { remove = remove, } + --- Make named constants for `package.config` -- (undocumented in 5.1; see luaconf.h for C equivalents). -- @table package diff --git a/lib/std/strict.lua b/lib/std/strict.lua index 5c06551..9a7590b 100644 --- a/lib/std/strict.lua +++ b/lib/std/strict.lua @@ -19,11 +19,13 @@ end mt.__declared = {} + local function what () local d = getinfo (3, "S") return d and d.what or "C" end + --- Detect assignment to undeclared global. -- @function __newindex mt.__newindex = function (t, n, v) @@ -37,6 +39,7 @@ mt.__newindex = function (t, n, v) rawset (t, n, v) end + --- Detect derefrence of undeclared global. -- @function __index mt.__index = function (t, n) diff --git a/lib/std/string.lua b/lib/std/string.lua index 9b8f5c8..fba60e0 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -42,6 +42,7 @@ local _tostring = _G.tostring local M = {} + --- String append operation. -- @param s string -- @param c character (1-character string) @@ -50,6 +51,7 @@ local function __append (s, c) return s .. c end + --- String concatenation operation. -- @param s string -- @param o object @@ -58,6 +60,7 @@ local function __concat (s, o) return M.tostring (s) .. M.tostring (o) end + --- String subscript operation. -- @param s string -- @param i index @@ -73,7 +76,6 @@ local function __index (s, i) end - --- Extend to work better with one argument. -- If only one argument is passed, no formatting is attempted. -- @param f format @@ -88,6 +90,7 @@ local function format (f, arg1, ...) end end + --- Extend to allow formatted arguments. -- @param v value to assert -- @param f format @@ -103,6 +106,7 @@ local function assert (v, f, ...) return v end + --- Do find, returning captures as a list. -- @param s target string -- @param p pattern @@ -120,6 +124,7 @@ local function tfind (s, p, init, plain) return pack (p.find (s, p, init, plain)) end + --- Do multiple `find`s on a string. -- @param s target string -- @param p pattern @@ -140,9 +145,11 @@ local function finds (s, p, init, plain) return l 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 @@ -160,6 +167,7 @@ local function split (s, sep) return t end + --- Require a module with a particular version. -- @param module module to require -- @param min lowest acceptable version (default: any) @@ -253,21 +261,25 @@ local function render (x, open, close, elem, pair, sep, roots) end end + --- -- @function render_OpenRenderer -- @param t table -- @return open table string + --- -- @function render_CloseRenderer -- @param t table -- @return close table string + --- -- @function render_ElementRenderer -- @param e element -- @return element string + --- NB. the function should not try to render i and v, or treat them recursively. -- @function render_PairRenderer -- @param t table @@ -277,6 +289,7 @@ end -- @param vs value string -- @return element string + --- -- @function render_SeparatorRenderer -- @param t table @@ -286,6 +299,7 @@ end -- @param w following value (nil on last call) -- @return separator string + --- Extend `tostring` to work better on tables. -- @function tostring -- @param x object to convert to string @@ -430,6 +444,7 @@ local function caps (s) end)) end + --- Remove any final newline from a string. -- @param s string to process -- @return processed string @@ -437,6 +452,7 @@ local function chomp (s) return (string.gsub (s, "\n$", "")) end + --- Escape a string to be used as a pattern. -- @param s string to process -- @return processed string @@ -444,6 +460,7 @@ local function escape_pattern (s) return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) end + --- Escape a string to be used as a shell token. -- Quotes spaces, parentheses, brackets, quotes, apostrophes and -- whitespace. @@ -453,6 +470,7 @@ local function escape_shell (s) return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) end + --- Return the English suffix for an ordinal. -- @param n number of the day -- @return suffix @@ -470,6 +488,7 @@ local function ordinal_suffix (n) end end + --- Justify a string. -- When the string is longer than w, it is truncated (left or right -- according to the sign of w). @@ -486,6 +505,7 @@ local function pad (s, w, p) return string.sub (s .. p, 1, w) end + --- Wrap a string into a paragraph. -- @param s string to wrap -- @param w width to wrap to (default: 78) @@ -521,6 +541,7 @@ local function wrap (s, w, ind, ind1) return r:tostring () end + --- Write a number using SI suffixes. -- The number is always written to 3 s.f. -- @param n number @@ -543,6 +564,7 @@ local function numbertosi (n) return tostring (man) .. s end + --- Remove leading matter from a string. -- @param s string -- @param r leading pattern (default: `"%s+"`) @@ -552,6 +574,7 @@ local function ltrim (s, r) return (string.gsub (s, "^" .. r, "")) end + --- Remove trailing matter from a string. -- @param s string -- @param r trailing pattern (default: `"%s+"`) @@ -561,6 +584,7 @@ local function rtrim (s, r) return (string.gsub (s, r .. "$", "")) end + --- Remove leading and trailing matter from a string. -- @param s string -- @param r leading/trailing pattern (default: `"%s+"`) From 06c4db2b1d6cb2f548d1c1593874f387552d85d3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 15:58:24 +0700 Subject: [PATCH 190/703] refactor: move split implementation from string to base. Decouple std.io from std.string (and hence std.table, std.list, std.functional, std.object, and std.container) by moving implementation of split into lib/std/base.lua. * lib/std/string.lua (split): Move from here... * lib/std/base.lua (base): ...to here, and export. * lib/std/string.lua: Re-export base.split as string.split. * lib/std/io.lua: Don't pull all of string.lua and it's dependencies into memory; use base.split instead of string.split. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 16 ++++++++++++++++ lib/std/io.lua | 8 ++++---- lib/std/string.lua | 15 ++------------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index eeb1a3a..276a8f9 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -214,6 +214,21 @@ local function metamethod (x, n) end +-- Doc-commented in string.lua... +local function split (s, sep) + assert (type (s) == "string", + "bad argument #1 to 'split' (string expected, got " .. type (s) .. ")") + local b, len, t, patt = 0, #s, {}, "(.-)" .. sep + if sep == "" then patt = "(.)"; t[#t + 1] = "" end + while b <= len do + local e, n, m = string.find (s, patt, b + 1) + t[#t + 1] = m or s:sub (b + 1, len) + b = n or len + 1 + end + return t +end + + local M = { argcheck = argcheck, argerror = argerror, @@ -223,6 +238,7 @@ local M = { leaves = leaves, metamethod = metamethod, prototype = prototype, + split = split, } diff --git a/lib/std/io.lua b/lib/std/io.lua index 0b84dc4..88c9cff 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -4,13 +4,13 @@ ]] local base = require "std.base" -local string = require "std.string" local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } -local argcheck, argerror = base.argcheck, base.argerror +local argcheck, argerror, leaves, split = + base.argcheck, base.argerror, base.leaves, base.split local M -- forward declaration @@ -80,7 +80,7 @@ local function writelines (h, ...) io.write (h, "\n") h = io.output () end - for v in base.leaves (ipairs, {...}) do + for v in leaves (ipairs, {...}) do h:write (v, "\n") end end @@ -117,7 +117,7 @@ end local function splitdir (path) argcheck ("std.io.splitdir", 1, "string", path) - return string.split (path, package.dirsep) + return split (path, package.dirsep) end diff --git a/lib/std/string.lua b/lib/std/string.lua index fba60e0..0caf0e3 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -31,11 +31,12 @@ @module std.string ]] +local base = require "std.base" local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" -local metamethod = require "std.base".metamethod +local metamethod, split = base.metamethod, base.split local _format = string.format local _tostring = _G.tostring @@ -154,18 +155,6 @@ end -- @string[opt="%s*"] sep separator pattern -- @return list of strings -- @return list of strings -local function split (s, sep) - assert (type (s) == "string", - "bad argument #1 to 'split' (string expected, got " .. type (s) .. ")") - local b, len, t, patt = 0, #s, {}, "(.-)" .. sep - if sep == "" then patt = "(.)"; t[#t + 1] = "" end - while b <= len do - local e, n, m = string.find (s, patt, b + 1) - t[#t + 1] = m or s:sub (b + 1, len) - b = n or len + 1 - end - return t -end --- Require a module with a particular version. From 64021d41e9f3e0fc947c4038f553da33e14e9aa6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 16:10:29 +0700 Subject: [PATCH 191/703] maint: add an LDoc module header to private std.base module. * lib/std/base.lua: Add LDoc module header. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 276a8f9..c39fb63 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -1,5 +1,18 @@ ------- --- @module std.base +--[[-- + 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. + + @module std.base +]] local typeof = type From 35109ea46e4ecc995902b484744509c8dc79bebb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 2 Jun 2014 09:24:39 +0700 Subject: [PATCH 192/703] math: complete specs and improve LDocs. * lib/std/math.lua (floor, monkey_patch, round): Improve LDocs with usage examples. * specs/io_spec.yaml (floor, round): Add missing specifications. Signed-off-by: Gary V. Vaughan --- lib/std/math.lua | 21 ++++++++++++--------- specs/math_spec.yaml | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/lib/std/math.lua b/lib/std/math.lua index b595bb1..979bb6d 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -1,5 +1,6 @@ --[[-- - Additions to the math module. + Additions to the core math module. + @module std.math ]] @@ -9,10 +10,10 @@ local _floor = math.floor --- Extend `math.floor` to take the number of decimal places. --- @function floor --- @param n number --- @param p number of decimal places to truncate to (default: 0) --- @return `n` truncated to `p` decimal places +-- @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) local function floor (n, p) if p and p ~= 0 then local e = 10 ^ p @@ -28,6 +29,7 @@ end -- Replaces core `math.floor` with `std.math` version. -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table +-- @usage require "std.math".monkey_patch () local function monkey_patch (namespace) namespace = namespace or _G assert (type (namespace) == "table", @@ -39,16 +41,17 @@ end --- Round a number to a given number of decimal places --- @function round --- @param n number --- @param p number of decimal places to round to (default: 0) --- @return `n` rounded to `p` decimal places +-- @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) local function round (n, p) local e = 10 ^ (p or 0) return _floor (n * e + 0.5) / e end +--- @export local M = { floor = floor, monkey_patch = monkey_patch, diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 8d05107..527d170 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -30,6 +30,21 @@ specify std.math: - describe floor: + - before: + f = M.floor + - 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 monkey_patch: @@ -46,3 +61,25 @@ specify std.math: - describe round: + - before: + f = M.round + - 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) From 1a32b5ceb61dcea342fe94f7c8ca3adae3e1b249 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 17:17:46 +0700 Subject: [PATCH 193/703] debug: support "int" parameter type with argcheck. * specs/debug_spec.yaml (argcheck): Specify behaviours of using "int" as a parameter type. * lib/std/base.lua (argcheck): Implement "int" parameter checking. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 5 +++++ specs/debug_spec.yaml | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/std/base.lua b/lib/std/base.lua index c39fb63..d36edc6 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -94,6 +94,11 @@ else ok = true end + elseif check == "int" then + if actualtype == "number" and actual == math.floor (actual) then + ok = true + end + elseif check == "list" then if typeof (actual) == "table" and #actual > 0 then ok = true diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index ae11c69..f3c99f8 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -122,6 +122,17 @@ specify std.debug: expect (fn ("number", 1)).not_to_error () expect (fn ("string", "s")).not_to_error () expect (fn ("table", {})).not_to_error () + - it diagnoses missing int types: + expect (fn ("int", nil)).to_error "int expected, got no value" + - it diagnoses mismatched int types: + expect (fn ("int", false)).to_error "int expected, got boolean" + expect (fn ("int", 1.234)).to_error "int expected, got number" + expect (fn ("int", 1234e-3)).to_error "int expected, got number" + - it matches int types: + expect (fn ("int", 1)).not_to_error () + expect (fn ("int", 1.0)).not_to_error () + expect (fn ("int", 0x1234)).not_to_error () + expect (fn ("int", 1.234e3)).not_to_error () - it diagnoses missing callable types: expect (fn ("function", nil)).to_error "function expected, got no value" - it diagnoses mismatched callable types: From 62fe8ab1949cc88677da086a56151a2bf602d4da Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 17:26:30 +0700 Subject: [PATCH 194/703] math: check api call argument types. * specs/math_spec.yaml (floor, round): Specify bad argument behaviours. * lib/std/math.yaml (floor, round): Use argscheck to diagnose bad arguments. Signed-off-by: Gary V. Vaughan --- lib/std/math.lua | 11 ++++++++++- specs/math_spec.yaml | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/std/math.lua b/lib/std/math.lua index 979bb6d..9d8ffaf 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -4,9 +4,14 @@ @module std.math ]] -local M -- forward declaration +local base = require "std.base" + local _floor = math.floor +local argscheck = base.argscheck + + +local M -- forward declaration --- Extend `math.floor` to take the number of decimal places. @@ -15,6 +20,8 @@ local _floor = math.floor -- @treturn number `n` truncated to `p` decimal places -- @usage tenths = floor (magnitude, 1) local function floor (n, p) + argscheck ("std.math.floor", {"number", {"int", "nil"}}, {n, p}) + if p and p ~= 0 then local e = 10 ^ p return _floor (n * e) / e @@ -46,6 +53,8 @@ end -- @treturn number `n` rounded to `p` decimal places -- @usage roughly = round (exactly, 2) local function round (n, p) + argscheck ("std.math.floor", {"number", {"int", "nil"}}, {n, p}) + local e = 10 ^ (p or 0) return _floor (n * e + 0.5) / e end diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 527d170..c889cee 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -32,6 +32,14 @@ specify std.math: - describe floor: - before: f = M.floor + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.math.floor' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (1.2, false)). + to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got boolean)" + expect (f (1.2, 3.4)). + to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got number)" - it rounds to the nearest smaller integer: expect (f (1.2)).to_be (1) expect (f (1.9)).to_be (1) @@ -63,6 +71,14 @@ specify std.math: - describe round: - before: f = M.round + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.math.floor' (number expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (1.2, false)). + to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got boolean)" + expect (f (1.2, 3.4)). + to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got number)" - it rounds to the nearest integer: expect (f (1.2)).to_be (1) expect (f (1.9)).to_be (2) From 4468660cac49c18078323d72434d54c4f3a9cdf5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 17:42:12 +0700 Subject: [PATCH 195/703] maint: use "int" argcheck type as appropriate. * lib/std/array.lua, lib/std/functional.lua: Use "int" parameter type with argcheck as appropriate. * lib/std/debug.lua (argscheck): Adjust usage example. * specs/array_spec.yaml, specs/functional_spec.yaml: Adjust. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 20 ++++++++++---------- lib/std/debug.lua | 2 +- lib/std/functional.lua | 2 +- specs/array_spec.yaml | 28 ++++++++++++++-------------- specs/functional_spec.yaml | 6 +++--- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index fe24104..b7041b2 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -83,7 +83,7 @@ local core_functions = { -- @int n the number of elements required -- @treturn Array the array realloc = function (self, n) - argscheck ("realloc", {"Array", "number"}, {self, n}) + argscheck ("realloc", {"Array", "int"}, {self, n}) -- Zero padding for uninitialised elements. for i = self.length + 1, n do @@ -102,7 +102,7 @@ local core_functions = { -- @int n number of elements to set -- @treturn Array the array set = function (self, from, v, n) - argscheck ("set", {"Array", "number", "any", "number"}, + argscheck ("set", {"Array", "int", "any", "int"}, {self, from, v, n}) local length = self.length @@ -190,10 +190,10 @@ core_metatable = { if init ~= nil then -- When called with 2 arguments: argcheck ("Array", 1, "string", type) - argcheck ("Array", 2, {"number", "table"}, init) + argcheck ("Array", 2, {"int", "table"}, init) elseif type ~= nil then -- When called with 1 argument: - argcheck ("Array", 1, {"number", "string", "table"}, type) + argcheck ("Array", 1, {"int", "string", "table"}, type) end -- Non-string argument 1 is really an init argument. @@ -294,7 +294,7 @@ core_metatable = { -- @int n 1-based index, or negative to index starting from the right -- @treturn string the element at index `n` __index = function (self, n) - argscheck ("__index", {"Array", {"number", "string"}}, {self, n}) + argscheck ("__index", {"Array", {"int", "string"}}, {self, n}) if typeof (n) == "number" then if n < 0 then n = n + self.length + 1 end @@ -313,7 +313,7 @@ core_metatable = { -- @param elem value to store at index n -- @treturn Array the array __newindex = function (self, n, elem) - argscheck ("__newindex", {"Array", "number", "any"}, {self, n, elem}) + argscheck ("__newindex", {"Array", "int", "any"}, {self, n, elem}) if typeof (n) == "number" then local used = self.length @@ -388,7 +388,7 @@ local alien_functions = { realloc = function (self, n) - argscheck ("realloc", {"Array", "number"}, {self, n}) + argscheck ("realloc", {"Array", "int"}, {self, n}) if n > self.allocated or n < self.allocated / 2 then self.allocated = n + element_chunk_size @@ -405,7 +405,7 @@ local alien_functions = { set = function (self, from, v, n) - argscheck ("set", {"Array", "number", "number", "number"}, + argscheck ("set", {"Array", "int", "number", "int"}, {self, from, v, n}) local used = self.length @@ -461,7 +461,7 @@ alien_metatable = { end, __index = function (self, n) - argscheck ("__index", {"Array", {"number", "string"}}, {self, n}) + argscheck ("__index", {"Array", {"int", "string"}}, {self, n}) if typeof (n) == "number" then if n < 0 then n = n + self.length + 1 end @@ -474,7 +474,7 @@ alien_metatable = { end, __newindex = function (self, n, elem) - argscheck ("__newindex", {"Array", "number", "number"}, {self, n, elem}) + argscheck ("__newindex", {"Array", "int", "number"}, {self, n, elem}) if typeof (n) == "number" then local used = self.length diff --git a/lib/std/debug.lua b/lib/std/debug.lua index f28b892..d107084 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -166,7 +166,7 @@ local argcheck = base.argcheck -- @tparam table|any actual argument value, or table of argument values -- @usage -- local function curry (f, n) --- argscheck ("std.functional.curry", {"function", "number"}, {f, n}) +-- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) -- ... local argscheck = base.argscheck diff --git a/lib/std/functional.lua b/lib/std/functional.lua index d152dd7..809270c 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -78,7 +78,7 @@ end -- > =incr (99), decr (99) -- 100 98 local function curry (f, n) - argscheck ("std.functional.curry", {"function", "number"}, {f, n}) + argscheck ("std.functional.curry", {"function", "int"}, {f, n}) if n <= 1 then return f diff --git a/specs/array_spec.yaml b/specs/array_spec.yaml index 5fa9a7b..6bae193 100644 --- a/specs/array_spec.yaml +++ b/specs/array_spec.yaml @@ -22,9 +22,9 @@ specify Array: expect (Array (1, 2)). to_error "bad argument #1 to 'Array' (string expected, got number)" expect (Array (function () end)). - to_error "bad argument #1 to 'Array' (number, string or table expected, got function)" + to_error "bad argument #1 to 'Array' (int, string or table expected, got function)" expect (Array ("int", function () end)). - to_error "bad argument #2 to 'Array' (number or table expected, got function)" + to_error "bad argument #2 to 'Array' (int or table expected, got function)" - context with inherited element type: - it constructs an empty array: array = Array () @@ -431,12 +431,12 @@ specify Array: expect (Array.realloc ()). to_error "bad argument #1 to 'realloc' (Array expected, got no value)" expect (Array.realloc (array)). - to_error "bad argument #2 to 'realloc' (number expected, got no value)" + to_error "bad argument #2 to 'realloc' (int expected, got no value)" - it diagnoses wrong argument types: | expect (Array.realloc (1234)). to_error "bad argument #1 to 'realloc' (Array expected, got number)" expect (Array.realloc (array, "string")). - to_error "bad argument #2 to 'realloc' (number expected, got string)" + to_error "bad argument #2 to 'realloc' (int expected, got string)" - it reduces the number of usable elements: array = Array (100) Array.realloc (array, 50) @@ -466,10 +466,10 @@ specify Array: - context when called as an object method: - it diagnoses missing arguments: | expect (array:realloc ()). - to_error "bad argument #2 to 'realloc' (number expected, got no value)" + to_error "bad argument #2 to 'realloc' (int expected, got no value)" - it diagnoses wrong argument types: | expect (array:realloc "string"). - to_error "bad argument #2 to 'realloc' (number expected, got string)" + to_error "bad argument #2 to 'realloc' (int expected, got string)" - it reduces the number of usable elements: array = Array (100):realloc (50) expect (array.length).to_be (50) @@ -498,24 +498,24 @@ specify Array: expect (Array.set ()). to_error "bad argument #1 to 'set' (Array expected, got no value)" expect (Array.set (array)). - to_error "bad argument #2 to 'set' (number expected, got no value)" + to_error "bad argument #2 to 'set' (int expected, got no value)" if array.allocated > 0 then expect (Array.set (array, 1)). to_error "bad argument #3 to 'set' (number expected, got no value)" end expect (Array.set (array, 1, 0)). - to_error "bad argument #4 to 'set' (number expected, got no value)" + to_error "bad argument #4 to 'set' (int expected, got no value)" - it diagnoses wrong argument types: | expect (Array.set (100)). to_error "bad argument #1 to 'set' (Array expected, got number)" expect (Array.set (array, "bogus")). - to_error "bad argument #2 to 'set' (number expected, got string)" + to_error "bad argument #2 to 'set' (int expected, got string)" if array.allocated > 0 then expect (Array.set (array, 1, {0})). to_error "bad argument #3 to 'set' (number expected, got table)" end expect (Array.set (array, 1, 0, function () end)). - to_error "bad argument #4 to 'set' (number expected, got function)" + to_error "bad argument #4 to 'set' (int expected, got function)" - it changes the value of a subsequence of elements: array = Array (100) Array.set (array, 25, 1, 50) @@ -557,22 +557,22 @@ specify Array: - context when called as an object method: - it diagnoses missing arguments: | expect (array:set ()). - to_error "bad argument #2 to 'set' (number expected, got no value)" + to_error "bad argument #2 to 'set' (int expected, got no value)" if array.allocated > 0 then expect (array:set (1)). to_error "bad argument #3 to 'set' (number expected, got no value)" end expect (array:set (1, 0)). - to_error "bad argument #4 to 'set' (number expected, got no value)" + to_error "bad argument #4 to 'set' (int expected, got no value)" - it diagnoses wrong argument types: | expect (array:set "bogus"). - to_error "bad argument #2 to 'set' (number expected, got string)" + to_error "bad argument #2 to 'set' (int expected, got string)" if array.allocated > 0 then expect (array:set (1, {0})). to_error "bad argument #3 to 'set' (number expected, got table)" end expect (array:set (1, 0, function () end)). - to_error "bad argument #4 to 'set' (number expected, got function)" + to_error "bad argument #4 to 'set' (int expected, got function)" - it changes the value of a subsequence of elements: array = Array (100):set (25, 1, 50) for i = 1, array.length do diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 2d31946..2f02a0a 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -103,12 +103,12 @@ specify std.functional: expect (M.curry ()). to_error "bad argument #1 to 'std.functional.curry' (function expected, got no value)" expect (M.curry (M.id)). - to_error "bad argument #2 to 'std.functional.curry' (number expected, got no value)" + to_error "bad argument #2 to 'std.functional.curry' (int expected, got no value)" - it diagnoses wrong argument types: | expect (M.curry (false)). to_error "bad argument #1 to 'std.functional.curry' (function expected, got boolean)" - expect (M.curry (M.id, false)). - to_error "bad argument #2 to 'std.functional.curry' (number expected, got boolean)" + expect (M.curry (M.id, 1.234)). + to_error "bad argument #2 to 'std.functional.curry' (int expected, got number)" - it returns a zero argument function uncurried: expect (M.curry (M.id, 0)).to_be (M.id) - it returns a one argument function uncurried: From 22ea866ed0ccdb3c0561d5074174400c8bae2e5b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 18:25:34 +0700 Subject: [PATCH 196/703] refactor: reduce module dependencies of std.debug. * lib/std/debug.lua: Remove std.io dependency by using core file:write instead of std.io.writelines. Remove std.list dependency by using lighter functional.map instead of list.map. Remove unused std.object dependency. (tabify): Instead of a deeply nested in-situ call to list.map and others, functionally compose an equivalent and use that to simplify. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index d107084..65ce0ab 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -22,12 +22,10 @@ @module std.debug ]] -local base = require "std.base" -local init = require "std.debug_init" -local io = require "std.io" -local list = require "std.list" -local Object = require "std.object" -local string = require "std.string" +local base = require "std.base" +local init = require "std.debug_init" +local functional = require "std.functional" +local string = require "std.string" --- Control std.debug function behaviour. @@ -41,6 +39,17 @@ local string = require "std.string" -- @usage _DEBUG = { argcheck = false, level = 9 } +--- Stringify a list of objects, then tabulate the resulting list of strings. +-- @tparam table list of elements +-- @treturn string tab delimited string +-- @usage s = tabify {...} +local tabify = functional.compose ( + -- map (elementfn, iterfn, unnbound_table_arg) + functional.bind (functional.map, {string.tostring, base.elems}), + -- table.concat (unbound_strbuf_table, "\t") + functional.bind (table.concat, {[2] = "\t"})) + + --- Print a debugging message to `io.stderr`. -- Display arguments passed through `std.string.tostring` and separated by tab -- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` @@ -63,7 +72,7 @@ local function say (n, ...) ((type (init._DEBUG) == "table" and type (init._DEBUG.level) == "number" and init._DEBUG.level >= level) or level <= 1) then - io.writelines (io.stderr, table.concat (list.map (string.tostring, arg), "\t")) + io.stderr:write (tabify (arg) .. "\n") end end @@ -102,7 +111,7 @@ local function trace (event) else s = s .. event .. " " .. (t.name or "(C)") .. " [" .. t.what .. "]" end - io.writelines (io.stderr, s) + io.stderr:write (s .. "\n") end -- Set hooks according to init._DEBUG From dd6d32142cf160ae1c0944c7ebbfde9d53379398 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 17:17:46 +0700 Subject: [PATCH 197/703] debug: support ":foo" parameter types with argcheck. * specs/debug_spec.yaml (argcheck): Specify behaviours of using ":foo" as a parameter type. * lib/std/base.lua (argcheck): Implement ":foo" parameter checking. * lib/std/debug.lua (argcheck): Update LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 7 +++++++ lib/std/debug.lua | 9 +++++++++ specs/debug_spec.yaml | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/lib/std/base.lua b/lib/std/base.lua index d36edc6..c89e17a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -109,6 +109,13 @@ else ok = true end + elseif typeof (check) == "string" and check:sub (1, 1) == ":" then + if check == actual then + ok = true + elseif actualtype == "string" and actual:sub (1, 1) == ":" then + actualtype = actual + end + elseif check == actualtype then ok = true end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 65ce0ab..a291887 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -147,8 +147,17 @@ local argerror = base.argerror -- any accept any non-nil argument type -- file accept an open file object -- function accept a function, or object with a __call metamethod +-- int accept an integer valued number -- list accept a table with a non-empty array part -- object accept any std.Object derived type +-- :foo accept only the exact string ":foo", works for any :-prefixed string +-- +-- The `:foo` format allows for type-checking of self-documenting +-- boolean-like constant string parameters predicated on `nil` versus +-- `:option` instead of `false` versus `true`. Or you could support +-- both: +-- +-- argcheck ("table.copy", 2, {"boolean", ":nometa"}, nometa) -- -- Call `argerror` if there is a type mismatch. -- diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index f3c99f8..61a57b2 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -133,6 +133,14 @@ specify std.debug: expect (fn ("int", 1.0)).not_to_error () expect (fn ("int", 0x1234)).not_to_error () expect (fn ("int", 1.234e3)).not_to_error () + - it diagnoses missing constant string types: + expect (fn (":foo", nil)).to_error ":foo expected, got no value" + - it diagnoses mismatched constant string types: + expect (fn (":foo", false)).to_error ":foo expected, got boolean" + expect (fn (":foo", ":bar")).to_error ":foo expected, got :bar" + expect (fn (":foo", "foo")).to_error ":foo expected, got string" + - it matches constant string types: + expect (fn (":foo", ":foo")).not_to_error () - it diagnoses missing callable types: expect (fn ("function", nil)).to_error "function expected, got no value" - it diagnoses mismatched callable types: From adbbc20d64faa380d8e115dd4b6b09fe3f81adb5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 3 Jun 2014 17:26:30 +0700 Subject: [PATCH 198/703] package: check api call argument types, and improve LDocs. * specs/package_spec.yaml (find, insert, mappath, normalize) (remove): Specify bad argument behaviours. * lib/std/package.lua (find, insert, mappath, normalize) (remove): Use argscheck to diagnose bad arguments. Add usage examples to LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/package.lua | 64 +++++++++++++++++++++++++++-------------- specs/package_spec.yaml | 61 ++++++++++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 33 deletions(-) diff --git a/lib/std/package.lua b/lib/std/package.lua index dfa0ee1..5f0f2d7 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -1,5 +1,6 @@ --[[-- - Additions to the package module. + Additions to the core package module. + @module std.package ]] @@ -7,9 +8,14 @@ local M -- forward declaration -local string = require "std.string" +local base = require "std.base" +local case = require "std.functional".case +local catfile = require "std.io".catfile +local invert = require "std.table".invert +local escape_pattern = require "std.string".escape_pattern -local split, escape_pattern = string.split, string.escape_pattern +local argcheck, argscheck, split = + base.argcheck, base.argscheck, base.split --- Look for a path segment match of `patt` in `pathstrings`. @@ -22,11 +28,12 @@ local split, escape_pattern = string.split, string.escape_pattern -- 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 .. "/]") local function find (pathstrings, patt, init, plain) - assert (type (pathstrings) == "string", - "bad argument #1 to find (string expected, got " .. type (pathstrings) .. ")") - assert (type (patt) == "string", - "bad argument #2 to find (string expected, got " .. type (patt) .. ")") + argscheck ("std.package.find", + {"string", "string", {"int", "nil"}, {"boolean", ":plain", "nil"}}, + {pathstrings, patt, init, plain}) + local paths = split (pathstrings, M.pathsep) if plain then patt = escape_pattern (patt) end init = init or 1 @@ -37,8 +44,6 @@ local function find (pathstrings, patt, init, plain) end -local case = require "std.functional".case - --- Substitute special characters in a path string. -- Characters prefixed with `%` have the `%` stripped, but are not -- subject to further substitution. @@ -56,9 +61,6 @@ local function pathsub (path) end -local catfile = require "std.io".catfile -local invert = require "std.table".invert - --- Normalize a path list. -- Removing redundant `.` and `..` directories, and keep only the first -- instance of duplicate elements. Each argument can contain any number @@ -67,9 +69,15 @@ local invert = require "std.table".invert -- `path_mark` (unless immediately preceded by a `%` character). -- @param ... path elements -- @treturn string a single normalized `pathsep` delimited paths string +-- @usage package.path = normalize (user_paths, sys_paths, package.path) local function normalize (...) - assert (select ("#", ...) > 0, "wrong number of arguments to 'normalize'") - local i, paths, pathstrings = 1, {}, table.concat ({...}, M.pathsep) + local t = {...} + if #t < 1 then argcheck ("std.package.normalize", 1, "string") end + for i, v in ipairs (t) do + argcheck ("std.package.normalize", i, "string", v) + end + + local i, paths, pathstrings = 1, {}, table.concat (t, M.pathsep) for _, path in ipairs (split (pathstrings, M.pathsep)) do path = pathsub (path): gsub (catfile ("^[^", "]"), catfile (".", "%0")): @@ -98,12 +106,22 @@ end -- 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") local unpack = unpack or table.unpack local function insert (pathstrings, ...) - assert (type (pathstrings) == "string", - "bad argument #1 to insert (string expected, got " .. type (pathstrings) .. ")") + local args, types = {pathstrings, ...} + if #args == 1 then + types = {"string", {"int", "string"}} + elseif #args == 2 then + types = {"string", "string"} + else + types = {"string", "int", "string"} + end + argscheck ("std.package.insert", types, args) + local paths = split (pathstrings, M.pathsep) table.insert (paths, ...) return normalize (unpack (paths)) @@ -124,11 +142,11 @@ end -- @tparam mappath_callback 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) local function mappath (pathstrings, callback, ...) - assert (type (pathstrings) == "string", - "bad argument #1 to mappath (string expected, got " .. type (pathstrings) .. ")") - assert (type (callback) == "function", - "bad argument #2 to mappath (function expected, got " .. type (pathstrings) .. ")") + argscheck ("std.package.mappath", + {"string", "function"}, {pathstrings, callback}) + for _, path in ipairs (split (pathstrings, M.pathsep)) do local r = callback (path, ...) if r ~= nil then return r end @@ -141,9 +159,11 @@ end -- @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) local function remove (pathstrings, pos) - assert (type (pathstrings) == "string", - "bad argument #1 to remove (string expected, got " .. type (pathstrings) .. ")") + argscheck ("std.package.remove", + {"string", {"int", "nil"}}, {pathstrings, pos}) + local paths = split (pathstrings, M.pathsep) table.remove (paths, pos) return table.concat (paths, M.pathsep) diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index 184cd00..eac459b 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -40,8 +40,19 @@ specify std.package: - describe find: - before: path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) - it diagnoses missing arguments: | - expect (M.find ()).to_error "bad argument #1 to find" - expect (M.find (path)).to_error "bad argument #2 to find" + expect (M.find ()). + to_error "bad argument #1 to 'std.package.find' (string expected, got no value)" + expect (M.find (path)). + to_error "bad argument #2 to 'std.package.find' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.find (false)). + to_error "bad argument #1 to 'std.package.find' (string expected, got boolean)" + expect (M.find (path, false)). + to_error "bad argument #2 to 'std.package.find' (string expected, got boolean)" + expect (M.find (path, "foo", false)). + to_error "bad argument #3 to 'std.package.find' (int or nil expected, got boolean)" + expect (M.find (path, "foo", 1, 2)). + to_error "bad argument #4 to 'std.package.find' (boolean, :plain or nil expected, got number)" - it returns nil for unmatched element: expect (M.find (path, "unmatchable")).to_be (nil) - it returns the element index for a matched element: @@ -60,8 +71,17 @@ specify std.package: - describe insert: - it diagnoses missing arguments: | - expect (M.insert ()).to_error "bad argument #1 to insert" - expect (M.insert (path)).to_error "wrong number of arguments" + expect (M.insert ()). + to_error "bad argument #1 to 'std.package.insert' (string expected, got no value)" + expect (M.insert (path)). + to_error "bad argument #2 to 'std.package.insert' (int or string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.insert (false)). + to_error "bad argument #1 to 'std.package.insert' (string expected, got boolean)" + expect (M.insert (path, false)). + to_error "bad argument #2 to 'std.package.insert' (string expected, got boolean)" + expect (M.insert (path, 1, false)). + to_error "bad argument #3 to 'std.package.insert' (string expected, got boolean)" - it appends by default: expect (M.insert (path, "new")). to_be (M.normalize ("begin", "middle", "end", "new")) @@ -84,9 +104,16 @@ specify std.package: - describe mappath: - before: expected = require "std.string".split (path, M.pathsep) - - it diagnoses bad arguments: | - expect (M.mappath ()).to_error "bad argument #1 to mappath" - expect (M.mappath ("")).to_error "bad argument #2 to mappath" + - it diagnoses missing arguments: | + expect (M.mappath ()). + to_error "bad argument #1 to 'std.package.mappath' (string expected, got no value)" + expect (M.mappath ("")). + to_error "bad argument #2 to 'std.package.mappath' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.mappath (false)). + to_error "bad argument #1 to 'std.package.mappath' (string expected, got boolean)" + expect (M.mappath ("", false)). + to_error "bad argument #2 to 'std.package.mappath' (function expected, got boolean)" - it calls a function with each path element: t = {} M.mappath (path, function (e) t[#t + 1] = e end) @@ -102,8 +129,14 @@ specify std.package: - describe normalize: - - it diagnoses bad arguments: - expect (M.normalize ()).to_error "wrong number of arguments" + - it diagnoses missing arguments: | + expect (M.normalize ()). + to_error "bad argument #1 to 'std.package.normalize' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.normalize (false)). + to_error "bad argument #1 to 'std.package.normalize' (string expected, got boolean)" + expect (M.normalize ("", false)). + to_error "bad argument #2 to 'std.package.normalize' (string expected, got boolean)" - context with a single element: - it strips redundant . directories: @@ -150,8 +183,14 @@ specify std.package: - describe remove: - - it diagnoses bad arguments: | - expect (M.remove ()).to_error "bad argument #1 to remove" + - it diagnoses missing arguments: | + expect (M.remove ()). + to_error "bad argument #1 to 'std.package.remove' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.remove (false)). + to_error "bad argument #1 to 'std.package.remove' (string expected, got boolean)" + expect (M.remove ("", false)). + to_error "bad argument #2 to 'std.package.remove' (int or nil expected, got boolean)" - it removes the last item by default: expect (M.remove (path)).to_be (M.normalize ("begin", "middle")) - it pops the first item with pos set to 1: From 76257e6f4bf8fd69e49b7f180310a549dc248c53 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 4 Jun 2014 09:43:28 +0700 Subject: [PATCH 199/703] doc: consolidate LDoc headers for core extension modules. * lib/std/debug.lua, lib/std/io.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua: Consolidate LDoc headers for consistency. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 8 +++++++- lib/std/io.lua | 9 ++++++++- lib/std/math.lua | 6 ++++++ lib/std/package.lua | 6 ++++++ lib/std/string.lua | 31 +++++-------------------------- lib/std/table.lua | 9 ++++++++- 6 files changed, 40 insertions(+), 29 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index a291887..8a5a01e 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -1,8 +1,14 @@ --[[-- 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" + The behaviour of the functions in this module are controlled by the value - of the global `_DEBUG`. Not setting `_DEBUG` prior to requiring any of + of the global `_DEBUG`. Not setting `_DEBUG` prior to requiring **any** of stdlib's modules is equivalent to having `_DEBUG = true`. The first line of Lua code in production quality projects that use stdlib diff --git a/lib/std/io.lua b/lib/std/io.lua index 88c9cff..e4f9b47 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -1,5 +1,12 @@ --[[-- - Additions to the io module. + Additions to the core io module. + + The module table returned by `std.io` also contains all of the entries from + the core io table. An hygienic way to import this module, then, is simply + to override the core `io` locally: + + local io = require "std.io" + @module std.io ]] diff --git a/lib/std/math.lua b/lib/std/math.lua index 9d8ffaf..9f498f5 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -1,6 +1,12 @@ --[[-- Additions to the core math module. + The module table returned by `std.io` 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" + @module std.math ]] diff --git a/lib/std/package.lua b/lib/std/package.lua index 5f0f2d7..094a116 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -1,6 +1,12 @@ --[[-- 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 the core `package` locally: + + local package = require "std.package" + @module std.package ]] diff --git a/lib/std/string.lua b/lib/std/string.lua index 0caf0e3..e966e2e 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -1,32 +1,11 @@ --[[-- - Additions to the string module. + Additions to the core string module. - If you `require "std"`, the contents of this module are all available - in the `std.string` table. + 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: - However, this module also contains references to the Lua core string - table entries, so it's safe to load it like this: - - local string = require "std.string" - - Of course, if you do that you'll lose references to any core string - functions overwritten by `std.string`, so you might want to save any - that you want access to before you overwrite them. - - If your code does not `require "std"` anywhere, then you'll also need - to manually overwrite string functions in the global namespace if you - want to use them from there: - - local assert, tostring = string.assert, string.tostring - - And finally, to use the string metatable improvements with all core - strings, you'll need to merge this module's metatable into the core - string metatable (again, `require "std"` does this automatically): - - local string_metatable = getmetatable "" - string_metatable.__append = string.__append - string_metatable.__concat = string.__concat - string_metatable.__index = string.__index + local string = require "std.string" @module std.string ]] diff --git a/lib/std/table.lua b/lib/std/table.lua index b2ddd4b..c16cee3 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -1,5 +1,12 @@ --[[-- - Extensions to the table module. + Extensions to the core table module. + + The module table returned by `std.io` 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" + @module std.table ]] From 749e7fd75b5b704ec79238fc31fffd274ba07692 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 4 Jun 2014 16:34:15 +0700 Subject: [PATCH 200/703] maint: remove workaround for legacy LuaRocks bug. * local.mk (dist_luastddebug_DATA): Move lib/std/debug_init/init.lua from here... (dist_luastd_DATA): ...to lib/std/debug_init.lua. (luastddebugdir, dit_luastddebug_DATA): Remove. Signed-off-by: Gary V. Vaughan --- lib/std/{debug_init/init.lua => debug_init.lua} | 0 local.mk | 13 +------------ 2 files changed, 1 insertion(+), 12 deletions(-) rename lib/std/{debug_init/init.lua => debug_init.lua} (100%) diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init.lua similarity index 100% rename from lib/std/debug_init/init.lua rename to lib/std/debug_init.lua diff --git a/local.mk b/local.mk index a2a909a..d58f83a 100644 --- a/local.mk +++ b/local.mk @@ -66,6 +66,7 @@ dist_luastd_DATA = \ lib/std/base.lua \ lib/std/container.lua \ lib/std/debug.lua \ + lib/std/debug_init.lua \ lib/std/functional.lua \ lib/std/io.lua \ lib/std/list.lua \ @@ -82,18 +83,6 @@ dist_luastd_DATA = \ $(NOTHING_ELSE) -# For bugwards compatibility with LuaRocks 2.1, while ensuring that -# `require "std.debug_init"` continues to work, we have to install -# the former `$(luadir)/std/debug_init.lua` to `debug_init/init.lua`. -# When everyone has upgraded to a LuaRocks that works, move this -# file back to dist_luastd_DATA above and rename to debug_init.lua. - -luastddebugdir = $(luastddir)/debug_init - -dist_luastddebug_DATA = \ - lib/std/debug_init/init.lua \ - $(NOTHING_ELSE) - # In order to avoid regenerating std.lua at configure time, which # causes the documentation to be rebuilt and hence requires users to # have ldoc installed, put std/std.lua in as a Makefile dependency. From 5c57e7167ff14cb8c5a8a2d1e3be6a0c730c59dc Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 4 Jun 2014 16:52:26 +0700 Subject: [PATCH 201/703] refactor: move _ARGCHECK calculation into std.debug_init. * lib/std/base.lua (_ARGCHECK): Move from here... * lib/std/debug_init.lua (M._ARGCHECK): ...to here. Adjust clients. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 8 +------- lib/std/debug_init.lua | 12 +++++++++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index c89e17a..ee913de 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,15 +24,9 @@ end local init = require "std.debug_init" -local _ARGCHECK = init._DEBUG -if type (init._DEBUG) == "table" then - _ARGCHECK = init._DEBUG.argcheck - if _ARGCHECK == nil then _ARGCHECK= true end -end - local argcheck, argerror, argscheck -if not _ARGCHECK then +if not init._ARGCHECK then local function nop () end diff --git a/lib/std/debug_init.lua b/lib/std/debug_init.lua index f008777..186967f 100644 --- a/lib/std/debug_init.lua +++ b/lib/std/debug_init.lua @@ -1,10 +1,20 @@ -- Debugging is on by default local M = { - _DEBUG = true, + _DEBUG = true, + _ARGCHECK = true, } if _G._DEBUG ~= nil then M._DEBUG = _G._DEBUG end + +-- Argument checking is on by default +M._ARGCHECK = M._DEBUG +if type (M._DEBUG) == "table" then + M._ARGCHECK = M._DEBUG.argcheck + if M._ARGCHECK == nil then M._ARGCHECK= true end +end + + return M From e2a5a3697024905879bcfb08af9e473f767b4a1c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 2 Jun 2014 09:24:39 +0700 Subject: [PATCH 202/703] table: complete specs and improve LDocs. * lib/std/table.lua (clone, clone_select, empty, invert, keys) (merge, merge_select, monkey_patch, new, pack, ripairs, size) (sort, totable, values): Add argcheck calls, and improve LDocs with usage examples & cross-references. (clone, clone_select, merge, merge_select): Minimise overhead when argchecking is disabled by wrapping complex argument checking in `if init._ARGCHECK`. * specs/table_spec.yaml (pack, ripairs, totable): Add missing specifications. (clone, clone_select, empty, invert, keys, merge, merge_select) (monkey_patch, new, size, sort, values): Improve specs to match argcheck behaviours. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 154 ++++++++++++++++++++++---------- specs/table_spec.yaml | 200 +++++++++++++++++++++++++++++++----------- 2 files changed, 261 insertions(+), 93 deletions(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index c16cee3..a02a867 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,12 +11,13 @@ ]] local base = require "std.base" +local init = require "std.debug_init" local M -- forward declaration -- No need to pull all of std.list into memory. -local elems = base.elems +local argcheck, argscheck, elems = base.argcheck, base.argscheck, base.elems --- Merge one table's fields into another. @@ -24,7 +25,7 @@ local elems = base.elems -- @tparam table u table with fields to merge -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` -- @tparam boolean nometa if non-nil don't copy metatable --- @return table `t` with fields from `u` merged in +-- @return table *t* with fields from *u* merged in local function merge_allfields (t, u, map, nometa) map = map or {} if type (map) ~= "table" then @@ -69,12 +70,22 @@ end -- To make deep copies, use @{std.tree.clone}. -- @tparam table t source table -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` --- @tparam boolean nometa if non-nil don't copy metatable +-- @tparam[opt] boolean 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 std.table.merge +-- @see std.table.clone_select +-- @usage +-- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") local function clone (t, map, nometa) - assert (type (t) == "table", - "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") + if init._ARGCHECK then + local types = {"table", "table", {"boolean", ":nometa", "nil"}} + if type (map) ~= "table" then + types = {"table", {"table", "boolean", ":nometa", "nil"}} + end + argscheck ("std.table.clone", types, {t, map, nometa}) + end + return merge_allfields ({}, t, map, nometa) end @@ -102,27 +113,44 @@ local clone_rename = base.deprecate (function (map, t) -- @function clone_select -- @tparam table t source table -- @tparam[opt={}] table keys list of keys to copy +-- @tparam[opt] boolean nometa if non-nil don't copy metatable -- @return copy of fields in *selection* from *t*, also sharing *t*'s -- metatable unless *nometa* +-- @see std.table.clone +-- @see std.table.merge_select +-- @usage +-- partialcopy = clone_select (original, {"this", "and_this"}, true) local function clone_select (t, keys, nometa) - assert (type (t) == "table", - "bad argument #1 to 'clone_select' (table expected, got " .. type (t) .. ")") + if init._ARGCHECK then + local types = {"table", "table", {"boolean", ":nometa", "nil"}} + if type (keys) ~= "table" then + types = {"table", {"table", "boolean", ":nometa", "nil"}} + end + argscheck ("std.table.clone_select", types, {t, keys, nometa}) + end + return merge_namedfields ({}, t, keys, nometa) end --- Return whether table is empty. -- @tparam table t any table --- @return `true` if `t` is empty, otherwise `false` +-- @return `true` if *t* is empty, otherwise `false` +-- @usage if empty (t) then error "ohnoes" end local function empty (t) + argcheck ("std.table.empty", 1, "table", t) + return not next (t) end --- Invert a table. --- @tparam table t a table with `{k=v, ...}` --- @treturn table inverted table `{v=k, ...}` +-- @tparam table t a table with `{k=v, ...}` +-- @treturn table inverted table `{v=k, ...}` +-- @usage values = invert (t) local function invert (t) + argcheck ("std.table.invert", 1, "table", t) + local i = {} for k, v in pairs (t) do i[v] = k @@ -132,9 +160,13 @@ end --- Make the list of keys in table. --- @tparam table t any table --- @treturn table list of keys +-- @tparam table t any table +-- @treturn table list of keys +-- @see std.table.values +-- @usage globals = keys (_G) local function keys (t) + argcheck ("std.table.keys", 1, "table", t) + local l = {} for k, _ in pairs (t) do l[#l + 1] = k @@ -147,13 +179,20 @@ end -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` --- @tparam boolean nometa if non-nil don't copy metatable --- @return table `t` with fields from `u` merged in +-- @tparam[opt] boolean nometa if non-nil don't copy metatable +-- @return table *t* with fields from *u* merged in +-- @see std.table.clone +-- @see std.table.merge_select +-- @usage merge (_G, require "std.debug", {say = "log"}, ":nometa") local function merge (t, u, map, nometa) - assert (type (t) == "table", - "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") - assert (type (u) == "table", - "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") + if init._ARGCHECK then + local types = {"table", "table", "table", {"boolean", ":nometa", "nil"}} + if type (map) ~= "table" then + types = {"table", "table", {"table", "boolean", ":nometa", "nil"}} + end + argscheck ("std.table.merge", types, {t, u, map, nometa}) + end + return merge_allfields (t, u, map, nometa) end @@ -164,32 +203,43 @@ end -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table keys list of keys to copy --- @tparam boolean nometa if non-nil don't copy metatable +-- @tparam[opt] boolean nometa if non-nil don't copy metatable -- @return copy of fields in *selection* from *t*, also sharing *t*'s -- metatable unless *nometa* +-- @see std.table.merge +-- @see std.table.clone_select +-- @usage merge_select (_G, require "std.debug", {"say"}, false) local function merge_select (t, u, keys, nometa) - assert (type (t) == "table", - "bad argument #1 to 'merge_select' (table expected, got " .. type (t) .. ")") - assert (type (u) == "table", - "bad argument #2 to 'merge_select' (table expected, got " .. type (u) .. ")") + if init._ARGCHECK then + local types = {"table", "table", "table", {"boolean", ":nometa", "nil"}} + if type (keys) ~= "table" then + types = {"table", "table", {"table", "boolean", ":nometa", "nil"}} + end + argscheck ("std.table.merge_select", types, {t, u, keys, nometa}) + end + return merge_namedfields (t, u, keys, nometa) end --- Return given metamethod, if any, or nil. -- @function metamethod --- @param x object to get metamethod of --- @param n name of metamethod to get --- @return metamethod function or nil if no metamethod or not a --- function +-- @tparam std.object x object to get metamethod of +-- @string n name of metamethod to get +-- @treturn function|nil metamethod function or `nil` if no metamethod or +-- not a function +-- @usage lookup = metamethod (require "std.object", "__index") local metamethod = base.metamethod --- Make a table with a default value for unset keys. --- @param x default entry value (default: `nil`) --- @tparam table t initial table (default: `{}`) --- @treturn table table whose unset elements are x +-- @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) local function new (x, t) + argcheck ("std.table.new", 2, {"table", "nil"}, t) + return setmetatable (t or {}, {__index = function (t, i) return x @@ -206,11 +256,14 @@ end --- An iterator like ipairs, but in reverse. --- @tparam table t any table --- @treturn function iterator function --- @treturn table the table, `t` --- @treturn number `#t + 1` +-- @tparam table t any table +-- @treturn function iterator function +-- @treturn table the table, *t* +-- @treturn number `#t + 1` +-- @usage for i, v = ripairs (t) do ... end local function ripairs (t) + argcheck ("std.table.ripairs", 1, "table", t) + return function (t, n) n = n - 1 if n > 0 then @@ -223,8 +276,11 @@ end --- Find the number of elements in a table. -- @tparam table t any table --- @return number of non-nil values in `t` +-- @return number of non-nil values in *t* +-- @usage count = size {foo = true, bar = true, baz = false} local function size (t) + argcheck ("std.table.size", 1, "table", t) + local n = 0 for _ in pairs (t) do n = n + 1 @@ -237,10 +293,14 @@ end local _sort = table.sort --- Make table.sort return its result. --- @tparam table t unsorted table --- @tparam function c comparator function --- @return `t` with keys sorted accordind to `c` +-- @tparam table t unsorted table +-- @tparam[opt] function c comparator function if passed, otherwise standard +-- lua `<` operator +-- @return *t* with keys sorted accordind to *c* +-- @usage table.concat (sort (object)) local function sort (t, c) + argscheck ("std.table.sort", {"table", "function"}, {t, c}) + _sort (t, c) return t end @@ -251,20 +311,23 @@ end -- Replaces core `table.sort` with `std.table` version. -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table +-- @usage local table = require "std.table".monkey_patch () local function monkey_patch (namespace) + argcheck ("std.table.monkey_patch", 1, {"table", "nil"}, namespace) namespace = namespace or _G - assert (type (namespace) == "table", - "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") namespace.table.sort = sort return M end ---- Turn an object into a table according to __totable metamethod. --- @tparam std.object x object to turn into a table +--- Turn an object into a table according to `__totable` metamethod. +-- @tparam std.object x object to turn into a table -- @treturn table resulting table or `nil` +-- @usage print (table.concat (totable (object))) local function totable (x) + argcheck ("std.table.totable", 1, {"object", "table", "string"}, x) + local m = metamethod (x, "__totable") if m then return m (x) @@ -281,9 +344,12 @@ end --- Make the list of values of a table. --- @tparam table t any table --- @treturn table list of values +-- @tparam table t any table +-- @treturn table list of values +-- @see std.table.keys local function values (t) + argcheck ("std.table.values", 1, "table", t) + local l = {} for _, v in pairs (t) do l[#l + 1] = v diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index a179bcb..14c7483 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -35,6 +35,16 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) f = M.clone + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.clone' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.clone' (table expected, got boolean)" + expect (f ({}, "nometa")). + to_error "bad argument #2 to 'std.table.clone' (table, boolean, :nometa or nil expected, got string)" + expect (f ({}, {}, "nometa")). + to_error "bad argument #3 to 'std.table.clone' (boolean, :nometa or nil expected, got string)" - it does not just return the subject: expect (f (subject)).not_to_be (subject) - it does copy the subject: @@ -51,11 +61,11 @@ specify std.table: - 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) + 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) + expect (getmetatable (f (withmt, {}, ":nometa"))).to_be (nil) - context when renaming some keys: - it renames during cloning: @@ -64,10 +74,6 @@ specify std.table: - it does not perturb the value in the renamed key field: expect (f (subject, {k1 = "newkey"}).newkey).to_be (subject.k1) - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") - - describe clone_rename: - before: @@ -105,6 +111,16 @@ specify std.table: withmt = setmetatable (M.clone (subject), {"meta!"}) f = M.clone_select + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.clone_select' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.clone_select' (table expected, got boolean)" + expect (f ({}, "nometa")). + to_error "bad argument #2 to 'std.table.clone_select' (table, boolean, :nometa or nil expected, got string)" + expect (f ({}, {}, "nometa")). + to_error "bad argument #3 to 'std.table.clone_select' (boolean, :nometa or nil expected, got string)" - it does not just return the subject: expect (f (subject)).not_to_be (subject) - it copies the keys selected: @@ -121,22 +137,24 @@ specify std.table: - context with metatables: - it treats non-table arg2 as nometa parameter: - expect (getmetatable (f (withmt, "nometa"))).to_be (nil) + 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) - - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") + expect (getmetatable (f (withmt, {}, ":nometa"))).to_be (nil) + expect (getmetatable (f (withmt, {"k1"}, ":nometa"))).to_be (nil) - describe empty: - before: f = M.empty + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.empty' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.empty' (table expected, got boolean)" - it returns true for an empty table: expect (f {}).to_be (true) expect (f {nil}).to_be (true) @@ -144,15 +162,18 @@ specify std.table: expect (f {"stuff"}).to_be (false) expect (f {{}}).to_be (false) expect (f {false}).to_be (false) - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") - describe invert: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } f = M.invert + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.invert' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.invert' (table expected, got boolean)" - it returns a new table: expect (f (subject)).not_to_be (subject) - it inverts keys and values in the returned table: @@ -162,15 +183,18 @@ specify std.table: - it seems to copy a list of 1..n numbers: subject = { 1, 2, 3 } expect (f (subject)).to_copy (subject) - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") - describe keys: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } f = M.keys + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.keys' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.keys' (table expected, got boolean)" - it returns an empty list when subject is empty: expect (f {}).to_equal {} - it makes a list of table keys: @@ -182,9 +206,6 @@ specify std.table: -- 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) - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") - describe merge: @@ -198,6 +219,20 @@ specify std.table: target = {} for k, v in pairs (t1) do target[k] = v end for k, v in pairs (t2) do target[k] = v end + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.merge' (table expected, got no value)" + expect (f ({})). + to_error "bad argument #2 to 'std.table.merge' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.merge' (table expected, got boolean)" + expect (f ({}, false)). + to_error "bad argument #2 to 'std.table.merge' (table expected, got boolean)" + expect (f ({}, {}, "nometa")). + to_error "bad argument #3 to 'std.table.merge' (table, boolean, :nometa or nil expected, got string)" + expect (f ({}, {}, {}, "nometa")). + to_error "bad argument #4 to 'std.table.merge' (boolean, :nometa or nil expected, got string)" - it does not create a whole new table: expect (f (t1, t2)).to_be (t1) - it does not change t1 when t2 is empty: @@ -219,13 +254,13 @@ specify std.table: 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) + 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) + 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: @@ -234,10 +269,6 @@ specify std.table: - it does not perturb the value in the renamed key field: expect (f ({}, t1, {k1 = "newkey"}).newkey).to_be (t1.k1) - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f ("foo", "bar")).to_error ("table expected") - - describe merge_select: - before: | @@ -252,6 +283,20 @@ specify std.table: target = {} for k, v in pairs (t1) do target[k] = v end for k, v in pairs (t2) do target[k] = v end + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.merge_select' (table expected, got no value)" + expect (f ({})). + to_error "bad argument #2 to 'std.table.merge_select' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.merge_select' (table expected, got boolean)" + expect (f ({}, false)). + to_error "bad argument #2 to 'std.table.merge_select' (table expected, got boolean)" + expect (f ({}, {}, "nometa")). + to_error "bad argument #3 to 'std.table.merge_select' (table, boolean, :nometa or nil expected, got string)" + expect (f ({}, {}, {}, "nometa")). + to_error "bad argument #4 to 'std.table.merge_select' (boolean, :nometa or nil expected, got string)" - it does not create a whole new table: expect (f (t1, t2)).to_be (t1) - it does not change t1 when t2 is empty: @@ -277,17 +322,13 @@ specify std.table: 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) + 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) - - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f ("foo", "bar")).to_error ("table expected") + expect (getmetatable (f ({}, t1mt, {}, ":nometa"))).to_be (nil) + expect (getmetatable (f ({}, t1mt, {"k1"}, ":nometa"))).to_be (nil) - describe metamethod: @@ -324,16 +365,21 @@ specify std.table: table = {}, } f (t) + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.monkey_patch' (table or nil expected, got boolean)" - it installs table.sort function: expect (t.table.sort).to_be (M.sort) - - it diagnoses non-table argument: - expect (f "bad").to_error "table expected" - describe new: - before: f = M.new + - it diagnoses wrong argument types: | + expect (f (nil, false)). + to_error "bad argument #2 to 'std.table.new' (table or nil expected, got boolean)" + - context when not setting a default: - before: default = nil - it returns a new table when nil is passed: @@ -363,14 +409,41 @@ specify std.table: default = { "unique object" } t = f (default) expect (t[1]).to_be (default) - - it diagnoses non-tables/non-nil in the second argument: - expect (f (nil, "foo")).to_error ("table expected") - describe pack: + - before: + unpack = unpack or table.unpack + t = {"one", "two", "five"} + f = M.pack + - it creates an empty table with no arguments: + expect (f ()).to_equal {} + - 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) - describe ripairs: + - before: + f = M.ripairs + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.ripairs' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.ripairs' (table expected, got boolean)" + - 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 a 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"} - describe size: @@ -378,13 +451,16 @@ specify std.table: -- - 1 - --------- 2 ---------- -- 3 -- subject = { "one", { { "two" }, "three" }, four = 5 } f = M.size + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.size' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.size' (table expected, got boolean)" - 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) - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") - describe sort: @@ -393,23 +469,52 @@ specify std.table: target = { 0, 1, 2, 3, 4, 5 } cmp = function (a, b) return a < b end f = M.sort + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.sort' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.sort' (table expected, got boolean)" + expect (f ({}, false)). + to_error "bad argument #2 to 'std.table.sort' (function expected, got boolean)" - 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) - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") - describe totable: + - before: + t = {"one", "two", "five"} + mt = { _type = "MockObject", + __totable = function (self) return self.content end } + f = M.totable + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.totable' (object, table or string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.totable' (object, table or string expected, got boolean)" + - it calls object's __totable metamethod: + object = setmetatable ({content = t}, mt) + expect (f (object)).to_be (t) + - it returns a table with no __totable metamethod unchanged: + t = {content = t} + object = setmetatable (t, { _type = "Thing" }) + expect (f (object)).to_be (t) - describe values: - before: subject = { k1 = {1}, k2 = {2}, k3 = {3} } f = M.values + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.values' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.values' (table expected, got boolean)" - it returns an empty list when subject is empty: expect (f {}).to_equal {} - it makes a list of table values: @@ -420,6 +525,3 @@ specify std.table: -- 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) - - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") From eaff77eea939aae8fd057983d4d1a0d45d7b2b61 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 5 Jun 2014 11:18:46 +0700 Subject: [PATCH 203/703] maint: minimize overhead with argchecks disabled. * lib/std/array.lua (__call): Wrap argchecking block in `if debug._ARGCHECK`. * lib/std/functional.lua (compose): Likewise. * lib/std/io.lua (catdir, catfile): Likewise. * lib/std/package.lua (normalize, insert): Likewise. * lib/std/base.lua (split): Use argscheck instead of assert. * lib/std/math.lua (monkey_patch): Likewise. * specs/math_spec.yaml (monkey_patch): Adjust bad argument behaviours. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 18 +++++++++++------- lib/std/base.lua | 8 ++++---- lib/std/functional.lua | 14 +++++++++----- lib/std/io.lua | 21 +++++++++++++-------- lib/std/math.lua | 5 ++--- lib/std/package.lua | 25 +++++++++++++++---------- specs/math_spec.yaml | 5 +++-- 7 files changed, 57 insertions(+), 39 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index b7041b2..54f820a 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -36,6 +36,8 @@ local argcheck, argscheck = base.argcheck, base.argscheck local Container = require "std.container" local prototype = Container.prototype +local debug = require "std.debug_init" + local have_alien, alien = pcall (require, "alien") local buffer, memmove, memset if have_alien then @@ -187,13 +189,15 @@ core_metatable = { -- @tparam[opt] int|table init initial size or list of initial elements -- @treturn Array a new Array object __call = function (self, type, init) - if init ~= nil then - -- When called with 2 arguments: - argcheck ("Array", 1, "string", type) - argcheck ("Array", 2, {"int", "table"}, init) - elseif type ~= nil then - -- When called with 1 argument: - argcheck ("Array", 1, {"int", "string", "table"}, type) + if debug._ARGCHECK then + if init ~= nil then + -- When called with 2 arguments: + argcheck ("Array", 1, "string", type) + argcheck ("Array", 2, {"int", "table"}, init) + elseif type ~= nil then + -- When called with 1 argument: + argcheck ("Array", 1, {"int", "string", "table"}, type) + end end -- Non-string argument 1 is really an init argument. diff --git a/lib/std/base.lua b/lib/std/base.lua index ee913de..373bb8e 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -22,11 +22,11 @@ local function prototype (o) end -local init = require "std.debug_init" +local debug = require "std.debug_init" local argcheck, argerror, argscheck -if not init._ARGCHECK then +if not debug._ARGCHECK then local function nop () end @@ -235,8 +235,8 @@ end -- Doc-commented in string.lua... local function split (s, sep) - assert (type (s) == "string", - "bad argument #1 to 'split' (string expected, got " .. type (s) .. ")") + argscheck ("std.string.split", {"string", {"string", "nil"}}, {s, sep}) + local b, len, t, patt = 0, #s, {}, "(.-)" .. sep if sep == "" then patt = "(.)"; t[#t + 1] = "" end while b <= len do diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 809270c..5d3077c 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -6,6 +6,8 @@ local base = require "std.base" local argcheck, argscheck = base.argcheck, base.argscheck +local debug = require "std.debug_init" + local functional -- forward declaration @@ -107,11 +109,13 @@ end -- a local function compose (...) local arg = {...} - if #arg < 1 then - argcheck ("std.functional.compose", 1, "function", nil) - end - for i in ipairs (arg) do - argcheck ("std.functional.compose", i, "function", arg[i]) + if debug._ARGCHECK then + if #arg < 1 then + argcheck ("std.functional.compose", 1, "function", nil) + end + for i in ipairs (arg) do + argcheck ("std.functional.compose", i, "function", arg[i]) + end end local fns, n = arg, #arg diff --git a/lib/std/io.lua b/lib/std/io.lua index e4f9b47..3e3f5ed 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,6 +11,7 @@ ]] local base = require "std.base" +local debug = require "std.debug_init" local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), @@ -136,11 +137,13 @@ end -- @usage filepath = catfile ("relative", "path", "filename") local function catfile (...) local t = {...} - if #t == 0 then - argcheck ("std.io.catfile", 1, "string", nil) - end - for i, v in ipairs (t) do - argcheck ("std.io.catfile", i, "string", v) + if debug._ARGCHECK then + if #t == 0 then + argcheck ("std.io.catfile", 1, "string", nil) + end + for i, v in ipairs (t) do + argcheck ("std.io.catfile", i, "string", v) + end end return table.concat (t, package.dirsep) @@ -153,9 +156,11 @@ end -- @see std.io.catfile -- @usage dirpath = catdir ("", "absolute", "directory") local function catdir (...) - t = {...} - for i, v in ipairs (t) do - argcheck ("std.io.catdir", i, "string", v) + local t = {...} + if debug._ARGCHECK then + for i, v in ipairs (t) do + argcheck ("std.io.catdir", i, "string", v) + end end return (string.gsub (table.concat (t, package.dirsep), "^$", package.dirsep)) diff --git a/lib/std/math.lua b/lib/std/math.lua index 9f498f5..24b0104 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -14,7 +14,7 @@ local base = require "std.base" local _floor = math.floor -local argscheck = base.argscheck +local argcheck, argscheck = base.argcheck, base.argscheck local M -- forward declaration @@ -44,9 +44,8 @@ end -- @treturn table the module table -- @usage require "std.math".monkey_patch () local function monkey_patch (namespace) + argcheck ("std.math.monkey_patch", 1, {"table", "nil"}, namespace) namespace = namespace or _G - assert (type (namespace) == "table", - "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") namespace.math.floor = floor return M diff --git a/lib/std/package.lua b/lib/std/package.lua index 094a116..de967d4 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -15,6 +15,7 @@ local M -- forward declaration local base = require "std.base" +local debug = require "std.debug_init" local case = require "std.functional".case local catfile = require "std.io".catfile local invert = require "std.table".invert @@ -78,9 +79,11 @@ end -- @usage package.path = normalize (user_paths, sys_paths, package.path) local function normalize (...) local t = {...} - if #t < 1 then argcheck ("std.package.normalize", 1, "string") end - for i, v in ipairs (t) do - argcheck ("std.package.normalize", i, "string", v) + if debug._ARGCHECK then + if #t < 1 then argcheck ("std.package.normalize", 1, "string") end + for i, v in ipairs (t) do + argcheck ("std.package.normalize", i, "string", v) + end end local i, paths, pathstrings = 1, {}, table.concat (t, M.pathsep) @@ -119,14 +122,16 @@ local unpack = unpack or table.unpack local function insert (pathstrings, ...) local args, types = {pathstrings, ...} - if #args == 1 then - types = {"string", {"int", "string"}} - elseif #args == 2 then - types = {"string", "string"} - else - types = {"string", "int", "string"} + if debug._ARGCHECK then + if #args == 1 then + types = {"string", {"int", "string"}} + elseif #args == 2 then + types = {"string", "string"} + else + types = {"string", "int", "string"} + end + argscheck ("std.package.insert", types, args) end - argscheck ("std.package.insert", types, args) local paths = split (pathstrings, M.pathsep) table.insert (paths, ...) diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index c889cee..9275267 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -62,10 +62,11 @@ specify std.math: math = {}, } f (t) + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.math.monkey_patch' (table or nil expected, got boolean)" - it installs math.floor function: expect (t.math.floor).to_be (M.floor) - - it diagnoses non-table argument: - expect (f "bad").to_error "table expected" - describe round: From 49255b58480cd05e30a9c7e8929a9da94cbaf693 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 5 Jun 2014 11:51:37 +0700 Subject: [PATCH 204/703] array: remove duplicate nested argcheck. * lib/std/array.lua (dispatch): Remove duplicate nested argcheck. The dispatched to functions already run a full argscheck, so no need to check again here. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index 54f820a..d658c88 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -512,7 +512,6 @@ alien_metatable = { -- as appropriate to the element manager of array local function dispatch (name) return function (array, ...) - argcheck (name, 1, "Array", array) local vfns = array.size > 0 and alien_functions or core_functions return vfns[name] (array, ...) end From 31b4f991813af657bcdd8f9eafa6de72dc71aa64 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 5 Jun 2014 12:44:01 +0700 Subject: [PATCH 205/703] array: remove duplicate nested argcheck, correctly this time. * lib/std/array.lua: Where a function or metamethod is called via `dispatch`, which already checks that the first argument is an Array object, don't recheck type of self. If that leaves only arguments of type "any" then don't spend any time checking argument types at all. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index d658c88..6bf5ce0 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -59,8 +59,6 @@ local core_functions = { -- @function pop -- @return the right-most element pop = function (self) - argcheck ("pop", 1, "Array", self) - self.length = math.max (self.length - 1, 0) return table.remove (self.buffer) end, @@ -71,8 +69,6 @@ local core_functions = { -- @param elem new element to be pushed -- @return elem push = function (self, elem) - argscheck ("push", {"Array", "any"}, {self, elem}) - local length = self.length + 1 self.buffer[length] = elem self.length = length @@ -85,7 +81,7 @@ local core_functions = { -- @int n the number of elements required -- @treturn Array the array realloc = function (self, n) - argscheck ("realloc", {"Array", "int"}, {self, n}) + argcheck ("realloc", 2, "int", n) -- Zero padding for uninitialised elements. for i = self.length + 1, n do @@ -124,8 +120,6 @@ local core_functions = { -- @function shift -- @return the removed element. shift = function (self) - argcheck ("shift", 1, "Array", self) - self.length = math.max (self.length - 1, 0) return table.remove (self.buffer, 1) end, @@ -136,8 +130,6 @@ local core_functions = { -- @param elem new element to be pushed -- @treturn elem unshift = function (self, elem) - argscheck ("unshift", {"Array", "any"}, {self, elem}) - self.length = self.length + 1 table.insert (self.buffer, 1, elem) return elem @@ -298,7 +290,7 @@ core_metatable = { -- @int n 1-based index, or negative to index starting from the right -- @treturn string the element at index `n` __index = function (self, n) - argscheck ("__index", {"Array", {"int", "string"}}, {self, n}) + argcheck ("__index", 2, {"int", "string"}, n) if typeof (n) == "number" then if n < 0 then n = n + self.length + 1 end @@ -317,7 +309,7 @@ core_metatable = { -- @param elem value to store at index n -- @treturn Array the array __newindex = function (self, n, elem) - argscheck ("__newindex", {"Array", "int", "any"}, {self, n, elem}) + argcheck ("__newindex", 2, "int", n) if typeof (n) == "number" then local used = self.length @@ -339,6 +331,7 @@ core_metatable = { -- @treturn int number of elements __len = function (self) argcheck ("__len", 1, "Array", self) + return self.length end, @@ -369,8 +362,6 @@ local element_chunk_size = 16 local alien_functions = { pop = function (self) - argscheck ("pop", {"Array"}, {self}) - local used = self.length if used > 0 then local elem = self[used] @@ -382,7 +373,7 @@ local alien_functions = { push = function (self, elem) - argscheck ("push", {"Array", "number"}, {self, elem}) + argcheck ("push", 2, "number", elem) local used = self.length + 1 self:realloc (used) @@ -392,7 +383,7 @@ local alien_functions = { realloc = function (self, n) - argscheck ("realloc", {"Array", "int"}, {self, n}) + argcheck ("realloc", 2, "int", n) if n > self.allocated or n < self.allocated / 2 then self.allocated = n + element_chunk_size @@ -426,8 +417,6 @@ local alien_functions = { shift = function (self) - argscheck ("shift", {"Array"}, {self}) - local n = self.length - 1 if n >= 0 then local elem = self[1] @@ -440,7 +429,7 @@ local alien_functions = { unshift = function (self, elem) - argscheck ("unshift", {"Array", "number"}, {self, elem}) + argcheck ("unshift", 2, "number", elem) local n = self.length self:realloc (n + 1) @@ -465,7 +454,7 @@ alien_metatable = { end, __index = function (self, n) - argscheck ("__index", {"Array", {"int", "string"}}, {self, n}) + argcheck ("__index", 2, {"int", "string"}, n) if typeof (n) == "number" then if n < 0 then n = n + self.length + 1 end @@ -478,7 +467,8 @@ alien_metatable = { end, __newindex = function (self, n, elem) - argscheck ("__newindex", {"Array", "int", "number"}, {self, n, elem}) + argcheck ("__newindex", 2, "int", n) + argcheck ("__newindex", 3, "number", elem) if typeof (n) == "number" then local used = self.length @@ -512,6 +502,7 @@ alien_metatable = { -- as appropriate to the element manager of array local function dispatch (name) return function (array, ...) + argcheck (name, 1, "Array", array) local vfns = array.size > 0 and alien_functions or core_functions return vfns[name] (array, ...) end From e950ee5473ee604e7ed053162adfcf591d00f67d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 5 Jun 2014 14:10:17 +0700 Subject: [PATCH 206/703] refactor: put api and helper functions in sections. * lib/std/array.lua, lib/std/container.lua, lib/std/io.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/set.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua: To make it clear that user-facing code ("api functions") need argument checking, but internal code ("helper functions") does not, explicitly separate those kinds of functions, and add some block headers. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 96 ++++++++++++++++++++++++++----------------- lib/std/container.lua | 12 ++++++ lib/std/io.lua | 12 ++++++ lib/std/optparse.lua | 4 +- lib/std/package.lua | 51 ++++++++++++++--------- lib/std/set.lua | 24 ++++++++++- lib/std/string.lua | 12 ++++++ lib/std/table.lua | 13 +++++- lib/std/tree.lua | 68 +++++++++++++++++++----------- 9 files changed, 205 insertions(+), 87 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index 6bf5ce0..39b8b0d 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -45,13 +45,55 @@ if have_alien then else buffer = function () return {} end end + local typeof = type --- Initial Array prototype object, plus any derived object containing -- --- elements that don't fit in alien buffers use `core_functions` to -- --- find object methods and `core_metatable` for metamethods. -- +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + +--- Number of bytes needed in an alien.buffer for each `type` element. +-- @string type name of an element type +-- @treturn int bytes per `type`, or 0 if alien.buffer cannot store `type`s +local function sizeof (type) + local ok, size = pcall ((alien or {}).sizeof, type) + return ok and size or 0 +end + + +--- Convert an array element index into a pointer. +-- @tparam std.array self an array +-- @int i[opt=1] an index into array +-- @treturn alien.buffer.pointer suitable for memmove or memset +local function topointer (self, i) + i = i or 1 + return self.buffer:topointer ((i - 1) * self.size + 1) +end + + +--- Fast zeroing of a contiguous block of array elements for `alien.buffer`s. +-- @tparam std.array self an array +-- @int from index of first element to zero out +-- @int n number of elements to zero out +local function setzero (self, from, n) + if n > 0 then memset (topointer (self, from), 0, n * self.size) end +end + + + +--[[ ================== ]]-- +--[[ Lua Table Manager. ]]-- +--[[ ================== ]]-- + + +-- Initial Array prototype object, plus any derived object containing +-- elements that don't fit in alien buffers use `core_functions` to +-- find object methods and `core_metatable` for metamethods. + +local core_metatable, alien_metatable -- forward declarations local core_functions = { @@ -137,36 +179,6 @@ local core_functions = { } ---- Number of bytes needed in an alien.buffer for each `type` element. --- @string type name of an element type --- @treturn int bytes per `type`, or 0 if alien.buffer cannot store `type`s -local function sizeof (type) - local ok, size = pcall ((alien or {}).sizeof, type) - return ok and size or 0 -end - - ---- Convert an array element index into a pointer. --- @tparam std.array self an array --- @int i[opt=1] an index into array --- @treturn alien.buffer.pointer suitable for memmove or memset -local function topointer (self, i) - i = i or 1 - return self.buffer:topointer ((i - 1) * self.size + 1) -end - - ---- Fast zeroing of a contiguous block of array elements for `alien.buffer`s. --- @tparam std.array self an array --- @int from index of first element to zero out --- @int n number of elements to zero out -local function setzero (self, from, n) - if n > 0 then memset (topointer (self, from), 0, n * self.size) end -end - - -local core_metatable, alien_metatable -- forward declarations - core_metatable = { _type = "Array", @@ -352,9 +364,15 @@ core_metatable = { } --- Cloned Array objects with elements managed by an alien buffer use -- --- `alien_functions` to find object methods and `alien_metatable` -- --- for metamethods. -- + +--[[ ===================== ]]-- +--[[ Alien Buffer Manager. ]]-- +--[[ ===================== ]]-- + + +-- Cloned Array objects with elements managed by an alien buffer use +-- `alien_functions` to find object methods and `alien_metatable` +-- for metamethods. local element_chunk_size = 16 @@ -490,6 +508,11 @@ alien_metatable = { +--[[ ========================= ]]-- +--[[ Public Dispatcher Object. ]]-- +--[[ ========================= ]]-- + + --- Return a function that dispatches to a virtual function table. -- The __call metamethod ensures that cloned Array objects are assigned -- a metatable and method table optimised for the element storage method @@ -509,7 +532,6 @@ local function dispatch (name) end - ------ -- An efficient array of homogenous objects. -- @table Array diff --git a/lib/std/container.lua b/lib/std/container.lua index 86cdb86..b4b199d 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -61,6 +61,12 @@ local base = require "std.base" + +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + -- Instantiate a new object based on *proto*. -- -- This is equivalent to: @@ -104,6 +110,12 @@ local function modulefunction (fn) end + +--[[ ================= ]]-- +--[[ Container Object. ]]-- +--[[ ================= ]]-- + + --- Return `obj` with references to the fields of `src` merged in. -- @static -- @tparam table obj destination object diff --git a/lib/std/io.lua b/lib/std/io.lua index 3e3f5ed..17ec24e 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -23,6 +23,12 @@ local argcheck, argerror, leaves, split = local M -- forward declaration + +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + --- Get an input file handle. -- @tparam[opt=io.input()] file|string h file handle or name -- @return file handle, or nil on error @@ -36,6 +42,12 @@ local function input_handle (h) end + +--[[ ============== ]]-- +--[[ API Functions. ]]-- +--[[ ============== ]]-- + + --- Slurp a file handle. -- @tparam[opt=io.input()] file|string file file handle or name -- if file is a file handle, that file is closed after reading diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 8e750da..5b43ece 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -97,9 +97,9 @@ local OptionParser -- forward declaration -- @func on see @{on} ---[[ ----------------- ]]-- +--[[ ================= ]]-- --[[ Helper Functions. ]]-- ---[[ ----------------- ]]-- +--[[ ================= ]]-- local optional, required diff --git a/lib/std/package.lua b/lib/std/package.lua index de967d4..ca2aed6 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -11,9 +11,6 @@ ]] -local M -- forward declaration - - local base = require "std.base" local debug = require "std.debug_init" local case = require "std.functional".case @@ -24,6 +21,37 @@ local escape_pattern = require "std.string".escape_pattern local argcheck, argscheck, split = base.argcheck, base.argscheck, base.split +local M -- forward declaration + + + +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + +--- Substitute special characters in a path string. +-- Characters prefixed with `%` have the `%` stripped, but are not +-- subject to further substitution. +-- @string path a path element with explicit `/` and `?` as necessary +-- @treturn string `path` with `dirsep` and `path_mark` substituted +-- for `/` and `?` +local function pathsub (path) + return path:gsub ("%%?.", function (capture) + return case (capture, { + ["?"] = function () return M.path_mark end, + ["/"] = function () return M.dirsep end, + function (s) return s:gsub ("^%%", "", 1) end, + }) + end) +end + + + +--[[ ============== ]]-- +--[[ API Functions. ]]-- +--[[ ============== ]]-- + --- Look for a path segment match of `patt` in `pathstrings`. -- @string pathstrings `pathsep` delimited path elements @@ -51,23 +79,6 @@ local function find (pathstrings, patt, init, plain) end ---- Substitute special characters in a path string. --- Characters prefixed with `%` have the `%` stripped, but are not --- subject to further substitution. --- @string path a path element with explicit `/` and `?` as necessary --- @treturn string `path` with `dirsep` and `path_mark` substituted --- for `/` and `?` -local function pathsub (path) - return path:gsub ("%%?.", function (capture) - return case (capture, { - ["?"] = function () return M.path_mark end, - ["/"] = function () return M.dirsep end, - function (s) return s:gsub ("^%%", "", 1) end, - }) - end) -end - - --- Normalize a path list. -- Removing redundant `.` and `..` directories, and keep only the first -- instance of duplicate elements. Each argument can contain any number diff --git a/lib/std/set.lua b/lib/std/set.lua index ec50f65..49fcb8c 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -18,7 +18,14 @@ local prototype = require "std.object".prototype local Set -- forward declaration --- Primitive methods (know about representation) + + +--[[ ==================== ]]-- +--[[ Primitive Functions. ]]-- +--[[ ==================== ]]-- + + +-- These functions know about internal implementatation. -- The representation is a table whose tags are the elements, and -- whose values are true. @@ -61,7 +68,14 @@ local function elems (set) end --- High level methods (representation-independent) + +--[[ ===================== ]]-- +--[[ High Level Functions. ]]-- +--[[ ===================== ]]-- + + +-- These functions are independent of the internal implementation. + local difference, symmetric_difference, intersection, union, subset, proper_subset, equal @@ -172,6 +186,12 @@ function equal (set1, set2) end + +--[[ =========== ]]-- +--[[ Set Object. ]]-- +--[[ =========== ]]-- + + --- Set prototype object. -- @table std.set -- @string[opt="Set"] _type type of Set, returned by diff --git a/lib/std/string.lua b/lib/std/string.lua index e966e2e..cc66f96 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -23,6 +23,12 @@ local _tostring = _G.tostring local M = {} + +--[[ ============ ]]-- +--[[ Metamethods. ]]-- +--[[ ============ ]]-- + + --- String append operation. -- @param s string -- @param c character (1-character string) @@ -56,6 +62,12 @@ local function __index (s, i) end + +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- + + --- Extend to work better with one argument. -- If only one argument is passed, no formatting is attempted. -- @param f format diff --git a/lib/std/table.lua b/lib/std/table.lua index a02a867..a5b1b42 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -16,10 +16,15 @@ local init = require "std.debug_init" local M -- forward declaration --- No need to pull all of std.list into memory. local argcheck, argscheck, elems = base.argcheck, base.argscheck, base.elems + +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + --- Merge one table's fields into another. -- @tparam table t destination table -- @tparam table u table with fields to merge @@ -65,6 +70,12 @@ local function merge_namedfields (t, u, keys, nometa) end + +--[[ ============== ]]-- +--[[ API Functions. ]]-- +--[[ ============== ]]-- + + --- Make a shallow copy of a table, including any metatable. -- -- To make deep copies, use @{std.tree.clone}. diff --git a/lib/std/tree.lua b/lib/std/tree.lua index be6be26..30f8cda 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -21,6 +21,43 @@ local prototype = require "std.object".prototype local Tree -- forward declaration + +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + +--- Tree iterator. +-- @tparam function it iterator function +-- @tparam tree|table tr tree or tree-like table +-- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order)) +-- @treturn table path to node ({i\_1...i\_k}) +-- @return node +local function _nodes (it, tr) + local p = {} + local function visit (n) + if type (n) == "table" then + coroutine.yield ("branch", p, n) + for i, v in it (n) do + p[#p + 1] = i + visit (v) + table.remove (p) + end + coroutine.yield ("join", p, n) + else + coroutine.yield ("leaf", p, n) + end + end + return coroutine.wrap (visit), tr +end + + + +--[[ ============== ]]-- +--[[ API Functions. ]]-- +--[[ ============== ]]-- + + --- Tree iterator which returns just numbered leaves, in order. -- @function ileaves -- @static @@ -83,31 +120,6 @@ local function clone (t, nometa) end ---- Tree iterator. --- @tparam function it iterator function --- @tparam tree|table tr tree or tree-like table --- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order)) --- @treturn table path to node ({i\_1...i\_k}) --- @return node -local function _nodes (it, tr) - local p = {} - local function visit (n) - if type (n) == "table" then - coroutine.yield ("branch", p, n) - for i, v in it (n) do - p[#p + 1] = i - visit (v) - table.remove (p) - end - coroutine.yield ("join", p, n) - else - coroutine.yield ("leaf", p, n) - end - end - return coroutine.wrap (visit), tr -end - - --- Tree iterator over all nodes. -- -- The returned iterator function performs a depth-first traversal of @@ -185,6 +197,12 @@ local function merge (t, u) end + +--[[ ============ ]]-- +--[[ Tree Object. ]]-- +--[[ ============ ]]-- + + --- Tree prototype object. -- @table std.tree -- @string[opt="Tree"] _type type of Tree, returned by From b5b5efc28d9aed605c42cab66dfc9897cf463ee8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 5 Jun 2014 14:24:52 +0700 Subject: [PATCH 207/703] string: remove unused string.__append metamethod. The __append metamethod was added in commit 7a548ba, but only ever had one client in the defunct std.lcs module which was removed in commet 5f0a8af. * lib/std/string.lua (__append): Remove. No longer required. * specs/string_spec.yaml: Remove __append examples and references. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 11 ----------- specs/string_spec.yaml | 7 +------ 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index cc66f96..a5c54ea 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -29,15 +29,6 @@ local M = {} --[[ ============ ]]-- ---- String append operation. --- @param s string --- @param c character (1-character string) --- @return `s .. c` -local function __append (s, c) - return s .. c -end - - --- String concatenation operation. -- @param s string -- @param o object @@ -378,7 +369,6 @@ local function monkey_patch (namespace) namespace.assert, namespace.tostring = assert, tostring local string_metatable = getmetatable "" - string_metatable.__append = __append string_metatable.__concat = __concat string_metatable.__index = __index @@ -576,7 +566,6 @@ end --- @export M = { - __append = __append, __concat = __concat, __index = __index, assert = assert, diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 205ff4f..acf3bf2 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -3,7 +3,7 @@ before: | this_module = "std.string" global_table = "_G" - extend_base = { "__append", "__concat", "__index", + extend_base = { "__concat", "__index", "assert", "caps", "chomp", "escape_pattern", "escape_shell", "finds", "format", "ltrim", "monkey_patch", "numbertosi", "ordinal_suffix", @@ -12,7 +12,6 @@ before: | "tostring", "trim", "wrap" } M = require (this_module) - getmetatable ("").__append = M.__append getmetatable ("").__concat = M.__concat getmetatable ("").__index = M.__index @@ -231,10 +230,6 @@ specify std.string: f = M.monkey_patch t = {} f (t) - - it installs append metamethod: - # FIXME: string metatable monkey-patches leak out! - mt = getmetatable "" - expect (mt.__append).to_be (M.__append) - it installs concat metamethod: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" From c845cf20ff74063c9d0b0c9eaede677fb81bbe8d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 5 Jun 2014 14:58:13 +0700 Subject: [PATCH 208/703] tree: remove std.list dependency requirement. No need to pull in all of std.list when loading std.tree, just for the foldl function. * lib/std/tree.lua (__index): Use func.fold and base.elems from existing required modules instead of list.foldl. Signed-off-by: Gary V. Vaughan --- lib/std/tree.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 30f8cda..1e219bb 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -13,7 +13,6 @@ local base = require "std.base" local Container = require "std.container" -local list = require "std.list" local func = require "std.functional" local prototype = require "std.object".prototype @@ -223,7 +222,7 @@ Tree = Container { -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? __index = function (self, i) if prototype (i) == "table" then - return list.foldl (func.op["[]"], self, i) + return func.fold (func.op["[]"], self, base.elems, i) else return rawget (self, i) end From c3b3245667b280bcbb97e2c2570d52d56c770769 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 5 Jun 2014 15:08:26 +0700 Subject: [PATCH 209/703] tree: remove std.object dependency. No need to pull in all of std.object when loading std.tree. * lib/std/tree.lua: Use base.prototype directly instead of via re-exported object.prototype, which allows complete removal of std.object dependency. While we're here, use imported functions from locals to speed up access a tiny bit. Signed-off-by: Gary V. Vaughan --- lib/std/tree.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 1e219bb..ace57e4 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -15,7 +15,8 @@ local base = require "std.base" local Container = require "std.container" local func = require "std.functional" -local prototype = require "std.object".prototype +local elems, base_leaves, prototype = base.elems, base.leaves, base.prototype +local fold, op = func.fold, func.op local Tree -- forward declaration @@ -66,7 +67,7 @@ end local function ileaves (tr) assert (type (tr) == "table", "bad argument #1 to 'ileaves' (table expected, got " .. type (tr) .. ")") - return base.leaves (ipairs, tr) + return base_leaves (ipairs, tr) end @@ -79,7 +80,7 @@ end local function leaves (tr) assert (type (tr) == "table", "bad argument #1 to 'leaves' (table expected, got " .. type (tr) .. ")") - return base.leaves (pairs, tr) + return base_leaves (pairs, tr) end @@ -222,7 +223,7 @@ Tree = Container { -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? __index = function (self, i) if prototype (i) == "table" then - return func.fold (func.op["[]"], self, base.elems, i) + return fold (op["[]"], self, elems, i) else return rawget (self, i) end From 69eb96ebb67d87da41f47babb2290ee4e81525e9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 5 Jun 2014 19:06:21 +0700 Subject: [PATCH 210/703] string: fix number extraction in require_version. * lib/std/string.lua (require_version): Change the match pattern to actually extract the numeric part of the version string. Also update LDocs to cite correct module._VERSION (with an under- score). * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 11 +++++++++++ lib/std/string.lua | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 8416e23..93ed323 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,17 @@ Stdlib NEWS - User visible changes - `optparse.on` now works with `std.strict` enabled. + - `string.require_version` now extracts the first substring made + entirely of digits and periods from the required module's version + string before splitting on period. That means, for version strings + like stdlib's "General Lua Libraries / 41" 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 `string.require_version` + looks first in `module.version` and then `module._VERSION` to match + the long-standing implementation. + * Noteworthy changes in release 40 (2014-05-01) [stable] diff --git a/lib/std/string.lua b/lib/std/string.lua index a5c54ea..2a86a2a 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -144,14 +144,14 @@ end -- @param min lowest acceptable version (default: any) -- @param too_big lowest version that is too big (default: none) -- @param pattern to match version in `module.version` or --- `module.VERSION` (default: `".*[%.%d]+"`) +-- `module._VERSION` (default: `"%D*([%.%d]+)"`) local function require_version (module, min, too_big, pattern) local function version_to_list (v) return List (split (v, "%.")) end local function module_version (module, pattern) return version_to_list (string.match (module.version or module._VERSION, - pattern or ".*[%.%d]+")) + pattern or "%D*([%.%d]+)")) end local m = require (module) if min then From 7140f2ab8dc57e0598f3d0def963791c3dfc3ee1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 6 Jun 2014 11:07:10 +0700 Subject: [PATCH 211/703] specs: add missing std.string specs. * specs/string_spec.yaml (assert, pickle, render, require_version) (tostring): Specify behaviour of these calls. (require_version): Add pending examples for newly discovered issue with non-numeric ordering. Signed-off-by: Gary V. Vaughan --- specs/string_spec.yaml | 136 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index acf3bf2..15dba3b 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -58,6 +58,29 @@ specify std.string: - describe assert: + - before: + f = M.assert + - context when it does not trigger: + - it has a truthy initial argument: + expect (f (1)).not_to_error () + expect (f (true)).not_to_error () + expect (f "yes").not_to_error () + expect (f (false == false)).not_to_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_error () + expect (f (false)).to_error () + expect (f (1 == 0)).to_error () + - it throws an optional error string: + expect (f (false, "ah boo")).to_error "ah boo" + - it plugs specifiers with string.format: | + expect (f (nil, "%s %d: %q", "here", 42, "a string")). + to_error (string.format ("%s %d: %q", "here", 42, "a string")) - describe caps: @@ -334,6 +357,30 @@ specify std.string: - describe pickle: + - before: + loadstring = loadstring or load + function unpickle (s) return loadstring ("return " .. s) () end + t = {1, {{2, 3}, 4, {5}}} + f = M.pickle + - it converts a primitive to a representative string: + expect (f (nil)).to_be "nil" + expect (f (false)).to_be "false" + expect (f (42)).to_be "42" + expect (f "string").to_be '"string"' + - it returns a loadable string that results in the original value: + expect (unpickle (f (nil))).to_be (nil) + expect (unpickle (f (false))).to_be (false) + expect (unpickle (f (42))).to_be (42) + expect (unpickle (f "string")).to_be "string" + - it converts a table to a representative string: + expect (f {"table", 42}).to_be '{[1]="table",[2]=42}' + - it returns a loadable string that results in the original table: + expect (unpickle (f {"table", 42})).to_equal {"table", 42} + - it converts a nested table to a representative string: + expect (f (t)). + to_be "{[1]=1,[2]={[1]={[1]=2,[2]=3},[2]=4,[3]={[1]=5}}}" + - it returns a loadable string that results in the original nested table: + expect (unpickle (f (t))).to_equal (t) - describe prettytostring: @@ -373,9 +420,76 @@ specify std.string: - describe render: + - before: + term = function (s) return function () return s end end + pair = function (_, _, _, i, v) return i .. "=" .. v end + sep = function (_, i, _, j) return (i and j) and "," or "" end + t = {1, {{2, 3}, 4, {5}}} + f = function (x) + return M.render (x, term "{", term "}", tostring, pair, sep) + end + - it converts a primitive to a representative string: + expect (f (nil)).to_be "nil" + expect (f (false)).to_be "false" + expect (f (42)).to_be "42" + expect (f ("string")).to_be "string" + - it converts a table to a representative string: + expect (f ({"table", 42})). + to_be '{1=table,2=42}' + - it converts a nested table to a representative string: + expect (f (t)). + to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}" + - it converts a recursive table to a representative string: + t[1] = t + expect (f (t)). + to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") + - it converts an Array to a representative string: + a = require "std.array" {"a", "b", {{"c"},"d"},"e"} + expect (f (a)). + to_be ('Array ("any", {a, b, ' .. tostring (a[3]) .. ', e})') - describe require_version: + - before: + f = M.require_version + - it diagnoses non-existent module: + expect (f ("module-not-exists", "", "")).to_error "module-not-exists" + - it diagnoses module too old: + expect (f ("std", "9999", "9999")).to_error () + - it diagnoses module too new: + expect (f ("std", "0", "0")).to_error () + - 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", "41")).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: + std = require "std" + std._VERSION, std.version = std.version, std._VERSION + expect (f ("std", "41", "9999")).to_be (require "std") + std._VERSION, std.version = std.version, std._VERSION + - context with semantic versioning: + - it diagnoses module too old: | + std = require "std" + std.version = "1.2.3" + expect (f ("std", "1.2.4")).to_error () + expect (f ("std", "1.3")).to_error () + expect (f ("std", "2.1.2")).to_error () + expect (f ("std", "2")).to_error () + pending "issue #60" + expect (f ("std", "1.2.10")).to_error () + - it diagnoses module too new: | + std = require "std" + std.version = "1.2.3" + expect (f ("std", nil, "1.2.2")).to_error () + expect (f ("std", nil, "1.1")).to_error () + expect (f ("std", nil, "1.1.2")).to_error () + expect (f ("std", nil, "1")).to_error () + - it returns modules with version in range: | + pending "issue #60" + expect (f ("std", nil, "1.2.10")).not_to_error () - describe rtrim: @@ -468,6 +582,28 @@ specify std.string: - describe tostring: + - before: + f = M.tostring + - it renders primitives exactly like system tostring: + expect (f (nil)).to_be (tostring (nil)) + expect (f (false)).to_be (tostring (false)) + expect (f (42)).to_be (tostring (42)) + expect (f (f)).to_be (tostring (f)) + expect (f "a string").to_be "a string" + - it renders empty tables as a pair of braces: + expect (f {}).to_be ("{}") + - it renders table array part compactly: + expect (f {"one", "two", "five"}). + to_be '{1=one,2=two,3=five}' + - it renders a table dictionary part compactly: + expect (f { one = true, two = 2, three = {3}}). + to_be '{one=true,three={1=3},two=2}' + - it renders table keys in table.sort order: + expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }). + to_be '{five=1,four=2,one=3,three=4,two=5}' + - it renders keys with invalid symbol names compactly: + expect (f { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }). + to_be '{?=1,[]=1,_=0,a-key=1,word=0}' - describe trim: From 6ca4550ed88240052479d98f6be456080b70940a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 6 Jun 2014 18:13:51 +0700 Subject: [PATCH 212/703] string: add argchecks and improve LDocs. * specs/string_spec.yaml (assert, caps, chomp, escape_pattern) (escape_shell, finds, format, ltrim, monkey_patch, numbertosi) (ordinal_suffix, pad, prettytostring, render, require_version) (rtrim, split, tfind, trim, wrap): Specify behaviours with bad or missing arguments. * lib/std/string.lua (assert, caps, chomp, escape_pattern) (escape_shell, finds, format, ltrim, monkey_patch, numbertosi) (ordinal_suffix, pad, prettytostring, render, require_version) (rtrim, split, tfind, trim, wrap): Use argcheck or argscheck to ensure argument types are validated when _DEBUG is not `false`. Improve LDocs with @usage examples and parameter types. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 6 + lib/std/string.lua | 343 ++++++++++++++++++++++++++--------------- specs/string_spec.yaml | 222 ++++++++++++++++++++------ 3 files changed, 397 insertions(+), 174 deletions(-) diff --git a/NEWS b/NEWS index 93ed323..f9ee58e 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,12 @@ Stdlib NEWS - User visible changes need to remove the previously ignored arguments that correspond to the fixed argument positions in the `bind` invocation. + - `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. + ** Bug fixes: diff --git a/lib/std/string.lua b/lib/std/string.lua index 2a86a2a..15a04f2 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -15,7 +15,8 @@ local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" -local metamethod, split = base.metamethod, base.split +local argcheck, argscheck, metamethod, split = + base.argcheck, base.argscheck, base.metamethod, base.split local _format = string.format local _tostring = _G.tostring @@ -30,19 +31,25 @@ local M = {} --- String concatenation operation. --- @param s string --- @param o object +-- @string s initial string +-- @param o object to stringify and concatenate -- @return s .. tostring (o) +-- @usage +-- local string = require "std.string".monkey_patch () +-- concatenated = "foo" .. {"bar"} local function __concat (s, o) return M.tostring (s) .. M.tostring (o) end --- String subscript operation. --- @param s string --- @param i index +-- @string s string +-- @tparam int|string i index or method name -- @return `s:sub (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] local function __index (s, i) if type (i) == "number" then return s:sub (i, i) @@ -61,11 +68,14 @@ end --- Extend to work better with one argument. -- If only one argument is passed, no formatting is attempted. --- @param f format --- @param arg1 first argument to format --- @param ... arguments to format +-- @function format +-- @string f format string +-- @param[opt] ... arguments to format -- @return formatted string +-- @usage print (format "100% stdlib!") local function format (f, arg1, ...) + argcheck ("std.string.format", 1, "string", f) + if arg1 == nil then return f else @@ -76,10 +86,13 @@ end --- Extend to allow formatted arguments. -- @param v value to assert --- @param f format --- @param ... arguments to format +-- @string[opt=""] f format string +-- @param[opt] ... arguments to format -- @return value +-- @usage assert (expected == actual, "100% unexpected!") local function assert (v, f, ...) + argcheck ("std.string.assert", 2, {"string", "nil"}, f) + if not v then if f == nil then f = "" @@ -90,36 +103,49 @@ local function assert (v, f, ...) end ---- Do find, returning captures as a list. --- @param s target string --- @param p pattern --- @param init start position (default: 1) --- @param plain inhibit magic characters (default: nil) --- @return start of match, end of match, table of captures -local function tfind (s, p, init, plain) - assert (type (s) == "string", - "bad argument #1 to 'tfind' (string expected, got " .. type (s) .. ")") - assert (type (p) == "string", - "bad argument #2 to 'tfind' (string expected, got " .. type (p) .. ")") +--- Do `string.find`, returning a table of captures. +-- @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) +local function tfind (s, pattern, init, plain) + argscheck ("std.string.tfind", + {"string", "string", {"int", "nil"}, {"boolean", ":plain", "nil"}}, + {s, pattern, init, plain}) + local function pack (from, to, ...) return from, to, {...} end - return pack (p.find (s, p, init, plain)) + return pack (pattern.find (s, pattern, init, plain)) end ---- Do multiple `find`s on a string. --- @param s target string --- @param p pattern --- @param init start position (default: 1) --- @param plain inhibit magic characters (default: nil) +--- Repeatedly `string.find` until target string is exhausted. +-- @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}}` -local function finds (s, p, init, plain) +-- @see std.string.tfind +-- @usage +-- for t in list.elems (finds ("the target string", "%S+")) do +-- print (tostring (t.capt)) +-- end +local function finds (s, pattern, init, plain) + argscheck ("std.string.finds", + {"string", "string", {"int", "nil"}, {"boolean", ":plain", "nil"}}, + {s, pattern, init, plain}) + init = init or 1 local l = {} local from, to, r repeat - from, to, r = tfind (s, p, init, plain) + from, to, r = tfind (s, pattern, init, plain) if from ~= nil then l[#l + 1] = {from, to, capt = r} init = to + 1 @@ -136,16 +162,21 @@ end -- @string s to split -- @string[opt="%s*"] sep separator pattern -- @return list of strings --- @return list of strings +-- @usage words = split "a very short sentence" --- Require a module with a particular version. --- @param module module to require --- @param min lowest acceptable version (default: any) --- @param too_big lowest version that is too big (default: none) --- @param pattern to match version in `module.version` or --- `module._VERSION` (default: `"%D*([%.%d]+)"`) +-- @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 std = require ("std", "41") local function require_version (module, min, too_big, pattern) + argscheck ("std.string.require_version", + {"string", {"string", "nil"}, {"string", "nil"}, {"string", "nil"}}, + {module, min, too_big, pattern}) + local function version_to_list (v) return List (split (v, "%.")) end @@ -192,18 +223,25 @@ end --- Turn tables into strings with recursion detection. -- N.B. Functions calling render should not recurse, or recursion -- detection will not work. --- @see render_OpenRenderer, render_CloseRenderer --- @see render_ElementRenderer, render_PairRenderer --- @see render_SeparatorRenderer -- @param x object to convert to string --- @param open open table renderer --- @param close close table renderer --- @param elem element renderer --- @param pair pair renderer --- @param sep separator renderer --- @param roots accumulates table references to detect recursion --- @return string representation +-- @tparam render_open_table open open table rendering function +-- @tparam render_close_table close close table rendering function +-- @tparam render_element elem element rendering function +-- @tparam render_pair pair pair rendering function +-- @tparam render_separator sep separator rendering function +-- @tparam[opt] table roots accumulates table references to detect recursion +-- @return string representation of *x* +-- @usage +-- function tostring (x) +-- return render (x, mkterminal "{", mkterminal "}", string.tostring, +-- function (_, _, _, i, v) return i .. "=" .. v end, +-- mkterminal ",") +-- end local function render (x, open, close, elem, pair, sep, roots) + argscheck ("std.string.render", + {{"any", "nil"}, "function", "function", "function", "function", "function", {"table", "nil"}}, + {x, open, close, elem, pair, sep, roots}) + local function stop_roots (x) return roots[x] or render (x, open, close, elem, pair, sep, table.clone (roots)) end @@ -233,48 +271,58 @@ local function render (x, open, close, elem, pair, sep, roots) end ---- --- @function render_OpenRenderer --- @param t table --- @return open table string +--- Signature of render open table callback. +-- @function render_open_table +-- @tparam table t table about to be rendered +-- @treturn string open table rendering +-- @usage function open (t) return "{" end ---- --- @function render_CloseRenderer --- @param t table --- @return close table string +--- Signature of render close table callback. +-- @function render_close_table +-- @tparam table t table just rendered +-- @treturn string close table rendering +-- @usage function close (t) return "}" end ---- --- @function render_ElementRenderer --- @param e element --- @return element string +--- Signature of render element callback. +-- @function render_element +-- @param x element to render +-- @treturn string element rendering +-- @usage function element (e) return require "string".tostring (e) end ---- NB. the function should not try to render i and v, or treat them recursively. --- @function render_PairRenderer --- @param t table --- @param i index --- @param v value --- @param is index string --- @param vs value string --- @return element string +--- Signature of render pair callback. +-- Trying to re-render *key* or *value* here will break recursion +-- detection, use *strkey* and *strvalue* pre-rendered values instead. +-- @function render_pair +-- @tparam table t table containing pair being rendered +-- @param key key part of key being rendered +-- @param value value part of key being rendered +-- @string keystr prerendered *key* +-- @string valuestr prerendered *value* +-- @treturn string pair rendering +-- @usage +-- function pair (_, _, _, key, value) return key .. "=" .. value end ---- --- @function render_SeparatorRenderer --- @param t table --- @param i preceding index (nil on first call) --- @param v preceding value (nil on first call) --- @param j following index (nil on last call) --- @param w following value (nil on last call) --- @return separator string +--- Signature of render separator callback. +-- @function render_separator +-- @tparam table t table currently being renedered +-- @param pk *t* key preceding separator, or `nil` for first key +-- @param pv *t* value preceding separator, or `nil` for first value +-- @param fk *t* key following separator, or `nil` for last key +-- @param fv *t* value following separator, or `nil` for last value +-- @treturn string separator rendering +-- @usage function separator (t) return fk and "," or "" end ---- Extend `tostring` to work better on tables. --- @function tostring +--- Extend `tostring` to render table contents as a string. -- @param x object to convert to string --- @return string representation +-- @treturn string compact string rendering of *x* +-- @usage +-- local tostring = require "std.string".tostring +-- print {foo="bar","baz"} --> {1=baz,foo=bar} local function tostring (x) return render (x, function () return "{" end, @@ -292,15 +340,20 @@ local function tostring (x) end ---- Pretty-print a table. --- @param t table to print --- @param indent indent between levels ["\t"] --- @param spacing space before every line --- @return pretty-printed string -local function prettytostring (t, indent, spacing) +--- Pretty-print a table, or other object. +-- @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, " ")) +local function prettytostring (x, indent, spacing) + argscheck ("std.string.prettytostring", + {{"any", "nil"}, {"string", "nil"}, {"string", "nil"}}, + {x, indent, spacing}) + indent = indent or "\t" spacing = spacing or "" - return render (t, + return render (x, function () local s = spacing .. "{" spacing = spacing .. indent @@ -360,12 +413,11 @@ end -- `std.string` versions. -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table +-- @usage local string = require "std.string".monkey_patch () local function monkey_patch (namespace) - namespace = namespace or _G - - assert (type (namespace) == "table", - "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") + argcheck ("std.string.monkey_patch", 1, {"table", "nil"}, namespace) + namespace = namespace or _G namespace.assert, namespace.tostring = assert, tostring local string_metatable = getmetatable "" @@ -377,10 +429,13 @@ end --- Convert a value to a string. --- The string can be passed to dostring to retrieve the value. +-- The string can be passed to `functional.eval` to retrieve the value. -- @todo Make it work for recursive tables. -- @param x object to pickle --- @return string such that eval (s) is the same value as x +-- @treturn string reversible string rendering of *x* +-- @see functional.eval +-- @usage +-- function slow_identity (x) return functional.eval (pickle (x)) end local function pickle (x) if type (x) == "string" then return format ("%q", x) @@ -405,9 +460,12 @@ end --- Capitalise each word in a string. --- @param s string --- @return capitalised string +-- @string s any string +-- @treturn string *s* with each word capitalized +-- @usage userfullname = caps (input_string) local function caps (s) + argcheck ("std.string.caps", 1, "string", s) + return (string.gsub (s, "(%w)([%w]*)", function (l, ls) return string.upper (l) .. ls @@ -416,17 +474,23 @@ end --- Remove any final newline from a string. --- @param s string to process --- @return processed string +-- @string s any string +-- @treturn string *s* with any single trailing newline removed +-- @usage line = chomp (line) local function chomp (s) + argcheck ("std.string.chomp", 1, "string", s) + return (string.gsub (s, "\n$", "")) end --- Escape a string to be used as a pattern. --- @param s string to process --- @return processed string +-- @string s any string +-- @treturn string *s* with active pattern characters escaped +-- @usage substr = inputstr:match (escape_pattern (literal)) local function escape_pattern (s) + argcheck ("std.string.escape_pattern", 1, "string", s) + return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) end @@ -434,17 +498,25 @@ end --- Escape a string to be used as a shell token. -- Quotes spaces, parentheses, brackets, quotes, apostrophes and -- whitespace. --- @param s string to process --- @return processed string +-- @string s any string +-- @treturn string *s* with active shell characters escaped +-- @usage os.execute ("echo " .. escape_shell (outputstr)) local function escape_shell (s) + argcheck ("std.string.escape_shell", 1, "string", s) + return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) end --- Return the English suffix for an ordinal. --- @param n number of the day --- @return 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)) local function ordinal_suffix (n) + argcheck ("std.string.ordinal_suffix", 1, {"int", "string"}, n) + n = math.abs (n) % 100 local d = n % 10 if d == 1 and n ~= 11 then @@ -462,12 +534,16 @@ end --- Justify a string. -- When the string is longer than w, it is truncated (left or right -- according to the sign of w). --- @param s string to justify --- @param w width to justify to (-ve means right-justify; +ve means --- left-justify) --- @param p string to pad with (default: `" "`) --- @return justified string +-- @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") local function pad (s, w, p) + argscheck ("std.string.pad", {"string", "int", {"string", "nil"}}, + {s, w, p}) + p = string.rep (p or " ", math.abs (w)) if w < 0 then return string.sub (p .. s, w) @@ -477,19 +553,23 @@ end --- Wrap a string into a paragraph. --- @param s string to wrap --- @param w width to wrap to (default: 78) --- @param ind indent (default: 0) --- @param ind1 indent of first line (default: ind) --- @return wrapped paragraph +-- @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)) local function wrap (s, w, ind, ind1) + argscheck ("std.string.wrap", + {"string", {"int", "nil"}, {"int", "nil"}, {"int", "nil"}}, + {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") - assert (type (s) == "string", - "bad argument #1 to 'wrap' (string expected, got " .. type (s) .. ")") local r = StrBuf { string.rep (" ", ind1) } local i, lstart, len = 1, ind1, #s while i <= #s do @@ -514,9 +594,12 @@ end --- Write a number using SI suffixes. -- The number is always written to 3 s.f. --- @param n number --- @return string +-- @tparam number|string n any numeric value +-- @treturn string *n* simplifed using largest available SI suffix. +-- @usage print (numbertosi (bitspersecond) .. "bps") local function numbertosi (n) + argcheck ("std.string.numbertosi", 1, {"number", "string"}, n) + local SIprefix = { [-8] = "y", [-7] = "z", [-6] = "a", [-5] = "f", [-4] = "p", [-3] = "n", [-2] = "mu", [-1] = "m", @@ -536,31 +619,41 @@ end --- Remove leading matter from a string. --- @param s string --- @param r leading pattern (default: `"%s+"`) --- @return string without leading r +-- @string s any string +-- @string[opt="%s+"] r leading pattern +-- @treturn string *s* with leading *r* stripped +-- @usage print ("got: " .. ltrim (userinput)) local function ltrim (s, r) + argscheck ("std.string.ltrim", {"string", {"string", "nil"}}, {s, r}) + r = r or "%s+" - return (string.gsub (s, "^" .. r, "")) + return s:gsub ("^" .. r, "") end --- Remove trailing matter from a string. --- @param s string --- @param r trailing pattern (default: `"%s+"`) --- @return string without trailing r +-- @string s any string +-- @string[opt="%s+"] r trailing pattern +-- @treturn string *s* with trailing *r* stripped +-- @usage print ("got: " .. rtrim (userinput)) local function rtrim (s, r) + argscheck ("std.string.rtrim", {"string", {"string", "nil"}}, {s, r}) + r = r or "%s+" - return (string.gsub (s, r .. "$", "")) + return s:gsub (r .. "$", "") end --- Remove leading and trailing matter from a string. --- @param s string --- @param r leading/trailing pattern (default: `"%s+"`) --- @return string without leading/trailing r +-- @string s any string +-- @string[opt="%s+"] r trailing pattern +-- @treturn string *s* with leading and trailing *r* stripped +-- @usage print ("got: " .. rtrim (userinput)) local function trim (s, r) - return rtrim (ltrim (s, r), r) + argscheck ("std.string.trim", {"string", {"string", "nil"}}, {s, r}) + + r = r or "%s+" + return s:gsub ("^" .. r, ""):gsub (r .. "$", "") end diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 15dba3b..b063bd2 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -60,6 +60,9 @@ specify std.string: - describe assert: - before: f = M.assert + - it diagnoses wrong argument types: | + expect (f (false, false)). + to_error "bad argument #2 to 'std.string.assert' (string or nil expected, got boolean)" - context when it does not trigger: - it has a truthy initial argument: expect (f (1)).not_to_error () @@ -86,6 +89,12 @@ specify std.string: - describe caps: - before: f = M.caps + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.caps' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.caps' (string expected, got boolean)" - it capitalises words of a string: target = "A String \n\n" expect (f (subject)).to_be (target) @@ -97,15 +106,18 @@ specify std.string: original = subject newstring = f (subject) expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") - describe chomp: - before: f = M.chomp target = "a string \n" + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.chomp' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.chomp' (string expected, got boolean)" - 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: @@ -117,9 +129,6 @@ specify std.string: original = subject newstring = f (subject) expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") - describe escape_pattern: @@ -132,6 +141,12 @@ specify std.string: magic[meta:sub (i, i)] = true end + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.escape_pattern' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.escape_pattern' (string expected, got boolean)" - context with each printable ASCII char: - before: subject, target = "", "" @@ -149,14 +164,17 @@ specify std.string: original = subject newstring = f (subject) expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") - describe escape_shell: - before: f = M.escape_shell + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.escape_shell' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.escape_shell' (string expected, got boolean)" - context with each printable ASCII char: - before: subject, target = "", "" @@ -183,6 +201,20 @@ specify std.string: - before: subject = "abcd" f = M.finds + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.finds' (string expected, got no value)" + expect (f ("string")). + to_error "bad argument #2 to 'std.string.finds' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.finds' (string expected, got boolean)" + expect (f ("string", false)). + to_error "bad argument #2 to 'std.string.finds' (string expected, got boolean)" + expect (f ("string", "pattern", false)). + to_error "bad argument #3 to 'std.string.finds' (int or nil expected, got boolean)" + expect (f ("string", "pattern", nil, "plain")). + to_error "bad argument #4 to 'std.string.finds' (boolean, :plain or nil expected, got string)" - context given a complex nested list: - before: target = { { 1, 2; capt = { "a", "b" } }, { 3, 4; capt = { "c", "d" } } } @@ -203,9 +235,6 @@ specify std.string: original = subject newstring = f (subject, "...") expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") # FIXME: This looks like a misfeature to me, let's remove it! @@ -213,6 +242,12 @@ specify std.string: - before: | subject = "string: %s, number: %d" f = M.format + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.format' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.format' (string expected, got boolean)" - it returns a single argument without attempting formatting: expect (f (subject)).to_be (subject) - it is available as a string metamethod: @@ -221,15 +256,18 @@ specify std.string: original = subject newstring = f (subject) expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f (nil, "arg")).to_error ("string expected") - expect (f ({"a table"}, "arg")).to_error ("string expected") - describe ltrim: - before: subject = " \t\r\n a short string \t\r\n " f = M.ltrim + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.ltrim' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.ltrim' (string expected, got boolean)" - it removes whitespace from the start of a string: target = "a short string \t\r\n " expect (f (subject)).to_equal (target) @@ -243,9 +281,6 @@ specify std.string: original = subject newstring = f (subject, "%W") expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") - describe monkey_patch: @@ -253,6 +288,9 @@ specify std.string: f = M.monkey_patch t = {} f (t) + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.monkey_patch' (table or nil expected, got boolean)" - it installs concat metamethod: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" @@ -265,13 +303,17 @@ specify std.string: expect (t.assert).to_be (M.assert) - it installs the tostring function: expect (t.tostring).to_be (M.tostring) - - it diagnoses non-table argument: - expect (f "bad").to_error "table expected" - describe numbertosi: - before: f = M.numbertosi + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.numbertosi' (number or string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.numbertosi' (number or string expected, got boolean)" - 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"} @@ -283,14 +325,17 @@ specify std.string: expect (subject).to_equal (target) - it coerces string arguments to a number: expect (f "1000").to_be "1k" - - "it diagnoses non-numeric arguments": - expect (f ()).to_error ("attempt to perform arithmetic") - expect (f {"a table"}).to_error ("number expected") - describe ordinal_suffix: - before: f = M.ordinal_suffix + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.ordinal_suffix' (int or string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.ordinal_suffix' (int or string expected, got boolean)" - it returns the English suffix for a number: subject, target = {}, {} for n = -120, 120 do @@ -306,9 +351,6 @@ specify std.string: expect (subject).to_equal (target) - it coerces string arguments to a number: expect (f "-91").to_be "st" - - "it diagnoses non-numeric arguments": - expect (f ()).to_error ("number expected") - expect (f {"a table"}).to_error ("number expected") - describe pad: @@ -316,6 +358,19 @@ specify std.string: width = 20 f = M.pad + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.pad' (string expected, got no value)" + expect (f ("string")). + to_error "bad argument #2 to 'std.string.pad' (int expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.pad' (string expected, got boolean)" + expect (f ("string", false)). + to_error "bad argument #2 to 'std.string.pad' (int expected, got boolean)" + expect (f ("string", 42, false)). + to_error "bad argument #3 to 'std.string.pad' (string or nil expected, got boolean)" + - context when string is shorter than given width: - before: subject = "short string" @@ -348,12 +403,6 @@ specify std.string: original = subject newstring = f (subject, width) expect (subject).to_be (original) - - "it coerces non-string arguments to a string": - argument = { "a table " } - expect (f (argument, width)).to_contain (M.tostring (argument)) - - "it diagnoses non-numeric width arguments": - expect (f (subject, nil)).to_error ("number expected") - expect (f (subject, {"a table"})).to_error ("number expected") - describe pickle: @@ -386,6 +435,11 @@ specify std.string: - describe prettytostring: - before: f = M.prettytostring + - it diagnoses wrong argument types: | + expect (f (false, false)). + to_error "bad argument #2 to 'std.string.prettytostring' (string or nil expected, got boolean)" + expect (f (false, "string", false)). + to_error "bad argument #3 to 'std.string.prettytostring' (string or nil expected, got boolean)" - it renders nil exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) - it renders booleans exactly like system tostring: @@ -428,6 +482,30 @@ specify std.string: f = function (x) return M.render (x, term "{", term "}", tostring, pair, sep) end + - it diagnoses missing arguments: | + expect (M.render (nil)). + to_error "bad argument #2 to 'std.string.render' (function expected, got no value)" + expect (M.render (nil, f)). + to_error "bad argument #3 to 'std.string.render' (function expected, got no value)" + expect (M.render (nil, f, f)). + to_error "bad argument #4 to 'std.string.render' (function expected, got no value)" + expect (M.render (nil, f, f, f)). + to_error "bad argument #5 to 'std.string.render' (function expected, got no value)" + expect (M.render (nil, f, f, f, f)). + to_error "bad argument #6 to 'std.string.render' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (M.render (nil, false)). + to_error "bad argument #2 to 'std.string.render' (function expected, got boolean)" + expect (M.render (nil, f, false)). + to_error "bad argument #3 to 'std.string.render' (function expected, got boolean)" + expect (M.render (nil, f, f, false)). + to_error "bad argument #4 to 'std.string.render' (function expected, got boolean)" + expect (M.render (nil, f, f, f, false)). + to_error "bad argument #5 to 'std.string.render' (function expected, got boolean)" + expect (M.render (nil, f, f, f, f, false)). + to_error "bad argument #6 to 'std.string.render' (function expected, got boolean)" + expect (M.render (nil, f, f, f, f, f, false)). + to_error "bad argument #7 to 'std.string.render' (table or nil expected, got boolean)" - it converts a primitive to a representative string: expect (f (nil)).to_be "nil" expect (f (false)).to_be "false" @@ -452,6 +530,18 @@ specify std.string: - describe require_version: - before: f = M.require_version + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.require_version' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.require_version' (string expected, got boolean)" + expect (f ("string", false)). + to_error "bad argument #2 to 'std.string.require_version' (string or nil expected, got boolean)" + expect (f ("string", "string", false)). + to_error "bad argument #3 to 'std.string.require_version' (string or nil expected, got boolean)" + expect (f ("string", "string", "string", false)). + to_error "bad argument #4 to 'std.string.require_version' (string or nil expected, got boolean)" - it diagnoses non-existent module: expect (f ("module-not-exists", "", "")).to_error "module-not-exists" - it diagnoses module too old: @@ -496,6 +586,12 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " f = M.rtrim + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.rtrim' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.rtrim' (string expected, got boolean)" - it removes whitespace from the end of a string: target = " \t\r\n a short string" expect (f (subject)).to_equal (target) @@ -509,9 +605,6 @@ specify std.string: original = subject newstring = f (subject, "%W") expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") - describe split: @@ -519,6 +612,14 @@ specify std.string: target = { "first", "the second one", "final entry" } subject = table.concat (target, ", ") f = M.split + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.split' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.split' (string expected, got boolean)" + expect (f ("string", false)). + to_error "bad argument #2 to 'std.string.split' (string or nil expected, got boolean)" - it returns a one-element list for an empty string: expect (f ("", ", ")).to_equal {""} - it makes a table of substrings delimited by a separator: @@ -548,15 +649,26 @@ specify std.string: - it takes a Lua pattern as a separator: expect (f (subject, "%s+")). to_equal {"first,", "the", "second", "one,", "final", "entry"} - - it diagnoses non-string arguments: - expect (f (nil, ",")).to_error ("string expected") - expect (f ({"a table"}, ",")).to_error ("string expected") - describe tfind: - before: subject = "abc" f = M.tfind + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.tfind' (string expected, got no value)" + expect (f ("string")). + to_error "bad argument #2 to 'std.string.tfind' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.tfind' (string expected, got boolean)" + expect (f ("string", false)). + to_error "bad argument #2 to 'std.string.tfind' (string expected, got boolean)" + expect (f ("string", "pattern", false)). + to_error "bad argument #3 to 'std.string.tfind' (int or nil expected, got boolean)" + expect (f ("string", "pattern", 1, "plain")). + to_error "bad argument #4 to 'std.string.tfind' (boolean, :plain or nil expected, got string)" - it creates a list of pattern captures: target = { 1, 3, { "a", "b", "c" } } expect ({f (subject, "(.)(.)(.)")}).to_equal (target) @@ -576,9 +688,6 @@ specify std.string: original = subject newstring = f (subject, "...") expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") - describe tostring: @@ -610,6 +719,12 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " f = M.trim + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.trim' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.trim' (string expected, got boolean)" - it removes whitespace from each end of a string: target = "a short string" expect (f (subject)).to_equal (target) @@ -623,37 +738,46 @@ specify std.string: original = subject newstring = f (subject, "%W") expect (subject).to_be (original) - - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") - 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" .. - "-2013 (see the AUTHORS file for details), and released und" .. + "-2014 (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 + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.wrap' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.wrap' (string expected, got boolean)" + expect (f ("string", false)). + to_error "bad argument #2 to 'std.string.wrap' (int or nil expected, got boolean)" + expect (f ("string", 72, false)). + to_error "bad argument #3 to 'std.string.wrap' (int or nil expected, got boolean)" + expect (f ("string", 72, 4, false)). + to_error "bad argument #4 to 'std.string.wrap' (int or nil expected, got boolean)" - 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" .. - "-2013 (see the AUTHORS file for details), and\nreleased un" .. + "-2014 (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" .. - "-2013 (see the AUTHORS file for\ndetails), and released un" .. + "-2014 (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-2013 (see the\n AUTHORS file for d" .. + "eir authors 2000-2014 (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) @@ -661,7 +785,7 @@ specify std.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-2013 (see\n the AUTHORS file for details), and rel" .. + "s 2000-2014 (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: From d172e3062f67de553450d7c6a0578151c6da87f3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 6 Jun 2014 19:51:25 +0700 Subject: [PATCH 213/703] debug: support trailing "?" in place of separate "nil" in argcheck. * specs/debug_spec.yaml (argcheck): Specify behaviour of trailing "?" in type specifiers. * lib/std/base.lua (argcheck): Strip trailing "?" from acceptable argument types, appending a "nil" entry to the list if any "?" is stripped. * lib/std/base.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua: Adjust all callers to use trailing "?" instead of separate explicit "nil" in type list. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 22 +++++++++++++++++++--- lib/std/debug.lua | 10 +++++++++- lib/std/functional.lua | 2 +- lib/std/math.lua | 6 +++--- lib/std/package.lua | 5 ++--- lib/std/string.lua | 27 ++++++++++++--------------- lib/std/table.lua | 20 ++++++++++---------- specs/debug_spec.yaml | 34 ++++++++++++++++++++++++++++++++++ 8 files changed, 90 insertions(+), 36 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 373bb8e..356ff22 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -62,6 +62,22 @@ else level = level or 2 if prototype (expected) ~= "table" then expected = {expected} end + -- Strip trailing "?" but add "nil" to expected when a "?" is found. + local add_nil = nil + for i, v in ipairs (expected) do + local m, q = v:match "^(.*)(%?)$" + if m then + expected[i] = m + if add_nil == nil and q == "?" then + add_nil = true + end + end + if m == "nil" then add_nil = false end + end + if add_nil then + expected[#expected + 1] = "nil" + end + -- Check actual has one of the types from expected local ok, actualtype = false, prototype (actual) for i, check in ipairs (expected) do @@ -163,9 +179,9 @@ end -- @return a function to show the warning on first call, and hand off to *fn* -- @usage funcname = deprecate (function (...) ... end, "funcname") local function deprecate (fn, name, warnmsg) - argscheck ("std.base.deprecate", - {"function", {"string", "nil"}, {"string", "nil"}}, + argscheck ("std.base.deprecate", {"function", "string?", "string?"}, {fn, name, warnmsg}) + if not (name or warnmsg) then error ("missing argument to 'std.base.deprecate' (2 or 3 arguments expected)", 2) end @@ -235,7 +251,7 @@ end -- Doc-commented in string.lua... local function split (s, sep) - argscheck ("std.string.split", {"string", {"string", "nil"}}, {s, sep}) + argscheck ("std.string.split", {"string", "string?"}, {s, sep}) local b, len, t, patt = 0, #s, {}, "(.-)" .. sep if sep == "" then patt = "(.)"; t[#t + 1] = "" end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 8a5a01e..5724542 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -163,7 +163,15 @@ local argerror = base.argerror -- `:option` instead of `false` versus `true`. Or you could support -- both: -- --- argcheck ("table.copy", 2, {"boolean", ":nometa"}, nometa) +-- argcheck ("table.copy", 2, {"boolean", ":nometa", "nil"}, nometa) +-- +-- A very common pattern is to have a list of possible types including +-- "nil" when the argument is optional. Rather than writing long-hand +-- as above, append a question mark to at least one of the list types +-- and omit the explicit "nil" entry. This is particularly effective +-- when there is only one acceptable type for an optional argument: +-- +-- argcheck ("string.assert", 1, "string?", predicate) -- -- Call `argerror` if there is a type mismatch. -- diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 5d3077c..65d7d94 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -145,7 +145,7 @@ end -- @param normalize[opt] function to normalize arguments -- @return memoized function local function memoize (fn, normalize) - argscheck ("std.functional.memoize", {"function", {"function", "nil"}}, + argscheck ("std.functional.memoize", {"function", "function?"}, {fn, normalize}) if normalize == nil then diff --git a/lib/std/math.lua b/lib/std/math.lua index 24b0104..2213252 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -26,7 +26,7 @@ local M -- forward declaration -- @treturn number `n` truncated to `p` decimal places -- @usage tenths = floor (magnitude, 1) local function floor (n, p) - argscheck ("std.math.floor", {"number", {"int", "nil"}}, {n, p}) + argscheck ("std.math.floor", {"number", "int?"}, {n, p}) if p and p ~= 0 then local e = 10 ^ p @@ -44,7 +44,7 @@ end -- @treturn table the module table -- @usage require "std.math".monkey_patch () local function monkey_patch (namespace) - argcheck ("std.math.monkey_patch", 1, {"table", "nil"}, namespace) + argcheck ("std.math.monkey_patch", 1, "table?", namespace) namespace = namespace or _G namespace.math.floor = floor @@ -58,7 +58,7 @@ end -- @treturn number `n` rounded to `p` decimal places -- @usage roughly = round (exactly, 2) local function round (n, p) - argscheck ("std.math.floor", {"number", {"int", "nil"}}, {n, p}) + argscheck ("std.math.floor", {"number", "int?"}, {n, p}) local e = 10 ^ (p or 0) return _floor (n * e + 0.5) / e diff --git a/lib/std/package.lua b/lib/std/package.lua index ca2aed6..edff40b 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -66,7 +66,7 @@ end -- @usage i, s = find (package.path, "^[^" .. package.dirsep .. "/]") local function find (pathstrings, patt, init, plain) argscheck ("std.package.find", - {"string", "string", {"int", "nil"}, {"boolean", ":plain", "nil"}}, + {"string", "string", "int?", {"boolean?", ":plain"}}, {pathstrings, patt, init, plain}) local paths = split (pathstrings, M.pathsep) @@ -183,8 +183,7 @@ end -- @treturn string a new string with given element removed -- @usage package.path = remove (package.path) local function remove (pathstrings, pos) - argscheck ("std.package.remove", - {"string", {"int", "nil"}}, {pathstrings, pos}) + argscheck ("std.package.remove", {"string", "int?"}, {pathstrings, pos}) local paths = split (pathstrings, M.pathsep) table.remove (paths, pos) diff --git a/lib/std/string.lua b/lib/std/string.lua index 15a04f2..0a7db1d 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -91,7 +91,7 @@ end -- @return value -- @usage assert (expected == actual, "100% unexpected!") local function assert (v, f, ...) - argcheck ("std.string.assert", 2, {"string", "nil"}, f) + argcheck ("std.string.assert", 2, "string?", f) if not v then if f == nil then @@ -115,7 +115,7 @@ end -- @usage b, e, captures = tfind ("the target string", "%s", 10) local function tfind (s, pattern, init, plain) argscheck ("std.string.tfind", - {"string", "string", {"int", "nil"}, {"boolean", ":plain", "nil"}}, + {"string", "string", "int?", {"boolean?", ":plain"}}, {s, pattern, init, plain}) local function pack (from, to, ...) @@ -138,7 +138,7 @@ end -- end local function finds (s, pattern, init, plain) argscheck ("std.string.finds", - {"string", "string", {"int", "nil"}, {"boolean", ":plain", "nil"}}, + {"string", "string", "int?", {"boolean?", ":plain"}}, {s, pattern, init, plain}) init = init or 1 @@ -174,7 +174,7 @@ end -- @usage std = require ("std", "41") local function require_version (module, min, too_big, pattern) argscheck ("std.string.require_version", - {"string", {"string", "nil"}, {"string", "nil"}, {"string", "nil"}}, + {"string", "string?", "string?", "string?"}, {module, min, too_big, pattern}) local function version_to_list (v) @@ -239,7 +239,7 @@ end -- end local function render (x, open, close, elem, pair, sep, roots) argscheck ("std.string.render", - {{"any", "nil"}, "function", "function", "function", "function", "function", {"table", "nil"}}, + {"any?", "function", "function", "function", "function", "function", "table?"}, {x, open, close, elem, pair, sep, roots}) local function stop_roots (x) @@ -347,8 +347,7 @@ end -- @treturn string pretty string rendering of *x* -- @usage print (prettytostring (std, " ")) local function prettytostring (x, indent, spacing) - argscheck ("std.string.prettytostring", - {{"any", "nil"}, {"string", "nil"}, {"string", "nil"}}, + argscheck ("std.string.prettytostring", {"any?", "string?", "string?"}, {x, indent, spacing}) indent = indent or "\t" @@ -415,7 +414,7 @@ end -- @treturn table the module table -- @usage local string = require "std.string".monkey_patch () local function monkey_patch (namespace) - argcheck ("std.string.monkey_patch", 1, {"table", "nil"}, namespace) + argcheck ("std.string.monkey_patch", 1, "table?", namespace) namespace = namespace or _G namespace.assert, namespace.tostring = assert, tostring @@ -541,8 +540,7 @@ end -- @treturn string *s* justified to *w* characters wide -- @usage print (pad (trim (outputstr, 78)) .. "\n") local function pad (s, w, p) - argscheck ("std.string.pad", {"string", "int", {"string", "nil"}}, - {s, w, p}) + argscheck ("std.string.pad", {"string", "int", "string?"}, {s, w, p}) p = string.rep (p or " ", math.abs (w)) if w < 0 then @@ -561,8 +559,7 @@ end -- @usage -- print (wrap (copyright, 72, 4)) local function wrap (s, w, ind, ind1) - argscheck ("std.string.wrap", - {"string", {"int", "nil"}, {"int", "nil"}, {"int", "nil"}}, + argscheck ("std.string.wrap", {"string", "int?", "int?", "int?"}, {s, w, ind, ind1}) w = w or 78 @@ -624,7 +621,7 @@ end -- @treturn string *s* with leading *r* stripped -- @usage print ("got: " .. ltrim (userinput)) local function ltrim (s, r) - argscheck ("std.string.ltrim", {"string", {"string", "nil"}}, {s, r}) + argscheck ("std.string.ltrim", {"string", "string?"}, {s, r}) r = r or "%s+" return s:gsub ("^" .. r, "") @@ -637,7 +634,7 @@ end -- @treturn string *s* with trailing *r* stripped -- @usage print ("got: " .. rtrim (userinput)) local function rtrim (s, r) - argscheck ("std.string.rtrim", {"string", {"string", "nil"}}, {s, r}) + argscheck ("std.string.rtrim", {"string", "string?"}, {s, r}) r = r or "%s+" return s:gsub (r .. "$", "") @@ -650,7 +647,7 @@ end -- @treturn string *s* with leading and trailing *r* stripped -- @usage print ("got: " .. rtrim (userinput)) local function trim (s, r) - argscheck ("std.string.trim", {"string", {"string", "nil"}}, {s, r}) + argscheck ("std.string.trim", {"string", "string?"}, {s, r}) r = r or "%s+" return s:gsub ("^" .. r, ""):gsub (r .. "$", "") diff --git a/lib/std/table.lua b/lib/std/table.lua index a5b1b42..85abb3e 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -90,9 +90,9 @@ end -- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") local function clone (t, map, nometa) if init._ARGCHECK then - local types = {"table", "table", {"boolean", ":nometa", "nil"}} + local types = {"table", "table", {"boolean?", ":nometa"}} if type (map) ~= "table" then - types = {"table", {"table", "boolean", ":nometa", "nil"}} + types = {"table", {"table?", "boolean?", ":nometa"}} end argscheck ("std.table.clone", types, {t, map, nometa}) end @@ -133,9 +133,9 @@ local clone_rename = base.deprecate (function (map, t) -- partialcopy = clone_select (original, {"this", "and_this"}, true) local function clone_select (t, keys, nometa) if init._ARGCHECK then - local types = {"table", "table", {"boolean", ":nometa", "nil"}} + local types = {"table", "table", {"boolean?", ":nometa"}} if type (keys) ~= "table" then - types = {"table", {"table", "boolean", ":nometa", "nil"}} + types = {"table", {"table?", "boolean?", ":nometa"}} end argscheck ("std.table.clone_select", types, {t, keys, nometa}) end @@ -197,9 +197,9 @@ end -- @usage merge (_G, require "std.debug", {say = "log"}, ":nometa") local function merge (t, u, map, nometa) if init._ARGCHECK then - local types = {"table", "table", "table", {"boolean", ":nometa", "nil"}} + local types = {"table", "table", "table", {"boolean?", ":nometa"}} if type (map) ~= "table" then - types = {"table", "table", {"table", "boolean", ":nometa", "nil"}} + types = {"table", "table", {"table?", "boolean?", ":nometa"}} end argscheck ("std.table.merge", types, {t, u, map, nometa}) end @@ -222,9 +222,9 @@ end -- @usage merge_select (_G, require "std.debug", {"say"}, false) local function merge_select (t, u, keys, nometa) if init._ARGCHECK then - local types = {"table", "table", "table", {"boolean", ":nometa", "nil"}} + local types = {"table", "table", "table", {"boolean?", ":nometa"}} if type (keys) ~= "table" then - types = {"table", "table", {"table", "boolean", ":nometa", "nil"}} + types = {"table", "table", {"table?", "boolean?", ":nometa"}} end argscheck ("std.table.merge_select", types, {t, u, keys, nometa}) end @@ -249,7 +249,7 @@ local metamethod = base.metamethod -- @treturn table table whose unset elements are x -- @usage t = new (0) local function new (x, t) - argcheck ("std.table.new", 2, {"table", "nil"}, t) + argcheck ("std.table.new", 2, "table?", t) return setmetatable (t or {}, {__index = function (t, i) @@ -324,7 +324,7 @@ end -- @treturn table the module table -- @usage local table = require "std.table".monkey_patch () local function monkey_patch (namespace) - argcheck ("std.table.monkey_patch", 1, {"table", "nil"}, namespace) + argcheck ("std.table.monkey_patch", 1, "table?", namespace) namespace = namespace or _G namespace.table.sort = sort diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 61a57b2..ebd1ea2 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -220,6 +220,40 @@ specify std.debug: expect (fn ({"string", "table"}, {0})).not_to_error () expect (fn ({"table", "#table"}, {})).not_to_error () expect (fn ({"#table", "table"}, {})).not_to_error () + - context with an optional type element: + - it diagnoses mismatched elements: + expect (fn ("boolean?", "string")). + to_error "boolean or nil expected, got string" + expect (fn ({"boolean?", ":symbol"}, {})). + to_error "boolean, :symbol or nil expected, got empty table" + - it matches nil against a single type: + expect (fn ("any?", nil)).not_to_error () + expect (fn ("boolean?", nil)).not_to_error () + expect (fn ("string?", nil)).not_to_error () + - it matches nil against a list of types: + expect (fn ({"boolean?", "table"}, nil)).not_to_error () + expect (fn ({"string?", "table"}, nil)).not_to_error () + expect (fn ({"table?", "#table"}, nil)).not_to_error () + expect (fn ({"#table?", "table"}, nil)).not_to_error () + - it matches nil against a list of optional types: + expect (fn ({"boolean?", "table?"}, nil)).not_to_error () + expect (fn ({"string?", "table?"}, nil)).not_to_error () + expect (fn ({"table?", "#table?"}, nil)).not_to_error () + expect (fn ({"#table?", "table?"}, nil)).not_to_error () + - it matches any named type: + expect (fn ("any?", false)).not_to_error () + expect (fn ("boolean?", false)).not_to_error () + expect (fn ("string?", "string")).not_to_error () + - it matches any type from a list: + expect (fn ({"boolean?", "table"}, {})).not_to_error () + expect (fn ({"string?", "table"}, {0})).not_to_error () + expect (fn ({"table?", "#table"}, {})).not_to_error () + expect (fn ({"#table?", "table"}, {})).not_to_error () + - it matches any type from a list with several optional specifiers: + expect (fn ({"boolean?", "table?"}, {})).not_to_error () + expect (fn ({"string?", "table?"}, {0})).not_to_error () + expect (fn ({"table?", "#table?"}, {})).not_to_error () + expect (fn ({"#table?", "table?"}, {})).not_to_error () - describe argscheck: From e42f92ef3ded1d5ccd08b2469b7fa91b8492a3b7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 6 Jun 2014 23:15:17 +0700 Subject: [PATCH 214/703] std: use argcheck instead of assert for type checking. * specs/std_spec.yaml (barrel, monkey_patch): Make bad argument examples more specific. * lib/std.lua.in (barrel, monkey_patch): Use argcheck calls for type checking insntead of assert. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 10 ++++------ specs/std_spec.yaml | 10 ++++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index eda66fe..5c99938 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -34,10 +34,9 @@ local M -- forward declaration -- @treturn table the module table -- @usage local std = require "std".monkey_patch () local function monkey_patch (namespace) - namespace = namespace or _G + require "std.base".argcheck ("std.monkey_patch", 1, "table?", namespace) - assert (type (namespace) == "table", - "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") + namespace = namespace or _G require "std.io".monkey_patch (namespace) require "std.math".monkey_patch (namespace) @@ -57,10 +56,9 @@ end -- @treturn table module table -- @usage local std = require "std".barrel () local function barrel (namespace) - namespace = namespace or _G + require "std.base".argcheck ("std.barrel", 1, "table?", namespace) - assert (type (namespace) == "table", - "bad argument #1 to 'barrel' (table expected, got " .. type (namespace) .. ")") + namespace = namespace or _G -- Older releases installed the following into _G by default. for _, v in pairs { diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index f6477f5..e3aa45b 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -30,6 +30,9 @@ specify std: table = {}, } f (t) + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.barrel' (table or nil expected, got boolean)" - it installs std.io monkey patches: expect (io_mt.readlines).to_be (std.io.readlines) expect (io_mt.writelines).to_be (std.io.writelines) @@ -78,8 +81,6 @@ specify std: totable = std.table.totable, warn = std.io.warn, } - - it diagnoses non-table argument: - expect (f "bad").to_error "table expected" - describe monkey_patch: - before: @@ -95,6 +96,9 @@ specify std: table = {}, } f (t) + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.monkey_patch' (table or nil expected, got boolean)" - it installs std.io monkey patches: expect (io_mt.readlines).to_be (std.io.readlines) expect (io_mt.writelines).to_be (std.io.writelines) @@ -110,5 +114,3 @@ specify std: expect (t.tostring).to_be (std.string.tostring) - it installs std.table monkey patches: expect (t.table.sort).to_be (std.table.sort) - - it diagnoses non-table argument: - expect (f "bad").to_error "table expected" From 1d3541c704988229b08d40697714615eb6219ca5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 7 Jun 2014 10:31:24 +0700 Subject: [PATCH 215/703] string: split on whitespace by default. * specs/string_spec.yaml (split): Add an example with no explicit split-pattern argument. * lib/std/base.lua (split): Default split-pattern to `%s+` when no argument provided. * lib/std/string.lua (split): LDocs cite `%s+` as the default pattern instead of `%s*`. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 5 +++++ lib/std/base.lua | 1 + lib/std/string.lua | 4 ++-- specs/string_spec.yaml | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index f9ee58e..e154e8c 100644 --- a/NEWS +++ b/NEWS @@ -63,6 +63,11 @@ Stdlib NEWS - User visible changes 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 on every non-whitespace character). + * Noteworthy changes in release 40 (2014-05-01) [stable] diff --git a/lib/std/base.lua b/lib/std/base.lua index 356ff22..a2802a2 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -253,6 +253,7 @@ end local function split (s, sep) argscheck ("std.string.split", {"string", "string?"}, {s, sep}) + sep = sep or "%s+" local b, len, t, patt = 0, #s, {}, "(.-)" .. sep if sep == "" then patt = "(.)"; t[#t + 1] = "" end while b <= len do diff --git a/lib/std/string.lua b/lib/std/string.lua index 0a7db1d..a7f9934 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -157,10 +157,10 @@ 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`. +-- `^$()%.[]*+-?` with a `%` prefix to match a literal character in *s*. -- @function split -- @string s to split --- @string[opt="%s*"] sep separator pattern +-- @string[opt="%s+"] sep separator pattern -- @return list of strings -- @usage words = split "a very short sentence" diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index b063bd2..cbfd24b 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -620,6 +620,9 @@ specify std.string: to_error "bad argument #1 to 'std.string.split' (string expected, got boolean)" expect (f ("string", false)). to_error "bad argument #2 to 'std.string.split' (string or nil expected, got boolean)" + - 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: From 048bbc3261cf545b563f26af3c195b0f3346c2c3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 7 Jun 2014 12:04:53 +0700 Subject: [PATCH 216/703] doc: move stringification functions to their own section. * lib/std/string.lua (render, tostring, prettytostring, pickle): Move to a new 'Stringification Functions' section. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 443 +++++++++++++++++++++++---------------------- 1 file changed, 223 insertions(+), 220 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index a7f9934..004dd43 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -195,6 +195,229 @@ local function require_version (module, min, too_big, pattern) end +--- Overwrite core methods and metamethods with `std` enhanced versions. +-- +-- Adds auto-stringification to `..` operator on core strings, and +-- integer indexing of strings with `[]` dereferencing. +-- +-- Also replaces core `assert` and `tostring` functions with +-- `std.string` versions. +-- @tparam[opt=_G] table namespace where to install global functions +-- @treturn table the module table +-- @usage local string = require "std.string".monkey_patch () +local function monkey_patch (namespace) + argcheck ("std.string.monkey_patch", 1, "table?", namespace) + + namespace = namespace or _G + namespace.assert, namespace.tostring = assert, M.tostring + + local string_metatable = getmetatable "" + string_metatable.__concat = __concat + string_metatable.__index = __index + + return M +end + + +--- Capitalise each word in a string. +-- @string s any string +-- @treturn string *s* with each word capitalized +-- @usage userfullname = caps (input_string) +local function caps (s) + argcheck ("std.string.caps", 1, "string", s) + + return (string.gsub (s, "(%w)([%w]*)", + function (l, ls) + return string.upper (l) .. ls + end)) +end + + +--- Remove any final newline from a string. +-- @string s any string +-- @treturn string *s* with any single trailing newline removed +-- @usage line = chomp (line) +local function chomp (s) + argcheck ("std.string.chomp", 1, "string", s) + + return (string.gsub (s, "\n$", "")) +end + + +--- Escape a string to be used as a pattern. +-- @string s any string +-- @treturn string *s* with active pattern characters escaped +-- @usage substr = inputstr:match (escape_pattern (literal)) +local function escape_pattern (s) + argcheck ("std.string.escape_pattern", 1, "string", s) + + return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) +end + + +--- Escape a string to be used as a shell token. +-- Quotes spaces, parentheses, brackets, quotes, apostrophes and +-- whitespace. +-- @string s any string +-- @treturn string *s* with active shell characters escaped +-- @usage os.execute ("echo " .. escape_shell (outputstr)) +local function escape_shell (s) + argcheck ("std.string.escape_shell", 1, "string", s) + + return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) +end + + +--- Return the English suffix for an ordinal. +-- @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)) +local function ordinal_suffix (n) + argcheck ("std.string.ordinal_suffix", 1, {"int", "string"}, n) + + n = math.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 + + +--- Justify a string. +-- When the string is longer than w, it is truncated (left or right +-- according to the sign of w). +-- @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") +local function pad (s, w, p) + argscheck ("std.string.pad", {"string", "int", "string?"}, {s, w, p}) + + p = string.rep (p or " ", math.abs (w)) + if w < 0 then + return string.sub (p .. s, w) + end + return string.sub (s .. p, 1, w) +end + + +--- Wrap a string into a paragraph. +-- @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)) +local function wrap (s, w, ind, ind1) + argscheck ("std.string.wrap", {"string", "int?", "int?", "int?"}, + {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 = StrBuf { string.rep (" ", ind1) } + local i, lstart, len = 1, ind1, #s + while i <= #s do + local j = i + w - lstart + while #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 + r:concat (s:sub (i, j)) + i = ni + if i < #s then + r:concat ("\n" .. string.rep (" ", ind)) + lstart = ind + end + end + return r:tostring () +end + + +--- Write a number using SI suffixes. +-- The number is always written to 3 s.f. +-- @tparam number|string n any numeric value +-- @treturn string *n* simplifed using largest available SI suffix. +-- @usage print (numbertosi (bitspersecond) .. "bps") +local function numbertosi (n) + argcheck ("std.string.numbertosi", 1, {"number", "string"}, 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 = t:find(".(.%...)e(.+)") + local man, exp = tonumber (m), tonumber (e) + local siexp = math.floor (exp / 3) + local shift = exp - siexp * 3 + local s = SIprefix[siexp] or "e" .. tostring (siexp) + man = man * (10 ^ shift) + return tostring (man) .. s +end + + +--- Remove leading matter from a string. +-- @string s any string +-- @string[opt="%s+"] r leading pattern +-- @treturn string *s* with leading *r* stripped +-- @usage print ("got: " .. ltrim (userinput)) +local function ltrim (s, r) + argscheck ("std.string.ltrim", {"string", "string?"}, {s, r}) + + r = r or "%s+" + return s:gsub ("^" .. r, "") +end + + +--- Remove trailing matter from a string. +-- @string s any string +-- @string[opt="%s+"] r trailing pattern +-- @treturn string *s* with trailing *r* stripped +-- @usage print ("got: " .. rtrim (userinput)) +local function rtrim (s, r) + argscheck ("std.string.rtrim", {"string", "string?"}, {s, r}) + + r = r or "%s+" + return s:gsub (r .. "$", "") +end + + +--- Remove leading and trailing matter from a string. +-- @string s any string +-- @string[opt="%s+"] r trailing pattern +-- @treturn string *s* with leading and trailing *r* stripped +-- @usage print ("got: " .. rtrim (userinput)) +local function trim (s, r) + argscheck ("std.string.trim", {"string", "string?"}, {s, r}) + + r = r or "%s+" + return s:gsub ("^" .. r, ""):gsub (r .. "$", "") +end + + +--- Stringification Functions +-- @section Stringification + -- Write pretty-printing based on: -- -- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators @@ -403,30 +626,6 @@ local function prettytostring (x, indent, spacing) end ---- Overwrite core methods and metamethods with `std` enhanced versions. --- --- Adds auto-stringification to `..` operator on core strings, and --- integer indexing of strings with `[]` dereferencing. --- --- Also replaces core `assert` and `tostring` functions with --- `std.string` versions. --- @tparam[opt=_G] table namespace where to install global functions --- @treturn table the module table --- @usage local string = require "std.string".monkey_patch () -local function monkey_patch (namespace) - argcheck ("std.string.monkey_patch", 1, "table?", namespace) - - namespace = namespace or _G - namespace.assert, namespace.tostring = assert, tostring - - local string_metatable = getmetatable "" - string_metatable.__concat = __concat - string_metatable.__index = __index - - return M -end - - --- Convert a value to a string. -- The string can be passed to `functional.eval` to retrieve the value. -- @todo Make it work for recursive tables. @@ -458,202 +657,6 @@ local function pickle (x) end ---- Capitalise each word in a string. --- @string s any string --- @treturn string *s* with each word capitalized --- @usage userfullname = caps (input_string) -local function caps (s) - argcheck ("std.string.caps", 1, "string", s) - - return (string.gsub (s, "(%w)([%w]*)", - function (l, ls) - return string.upper (l) .. ls - end)) -end - - ---- Remove any final newline from a string. --- @string s any string --- @treturn string *s* with any single trailing newline removed --- @usage line = chomp (line) -local function chomp (s) - argcheck ("std.string.chomp", 1, "string", s) - - return (string.gsub (s, "\n$", "")) -end - - ---- Escape a string to be used as a pattern. --- @string s any string --- @treturn string *s* with active pattern characters escaped --- @usage substr = inputstr:match (escape_pattern (literal)) -local function escape_pattern (s) - argcheck ("std.string.escape_pattern", 1, "string", s) - - return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) -end - - ---- Escape a string to be used as a shell token. --- Quotes spaces, parentheses, brackets, quotes, apostrophes and --- whitespace. --- @string s any string --- @treturn string *s* with active shell characters escaped --- @usage os.execute ("echo " .. escape_shell (outputstr)) -local function escape_shell (s) - argcheck ("std.string.escape_shell", 1, "string", s) - - return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) -end - - ---- Return the English suffix for an ordinal. --- @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)) -local function ordinal_suffix (n) - argcheck ("std.string.ordinal_suffix", 1, {"int", "string"}, n) - - n = math.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 - - ---- Justify a string. --- When the string is longer than w, it is truncated (left or right --- according to the sign of w). --- @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") -local function pad (s, w, p) - argscheck ("std.string.pad", {"string", "int", "string?"}, {s, w, p}) - - p = string.rep (p or " ", math.abs (w)) - if w < 0 then - return string.sub (p .. s, w) - end - return string.sub (s .. p, 1, w) -end - - ---- Wrap a string into a paragraph. --- @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)) -local function wrap (s, w, ind, ind1) - argscheck ("std.string.wrap", {"string", "int?", "int?", "int?"}, - {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 = StrBuf { string.rep (" ", ind1) } - local i, lstart, len = 1, ind1, #s - while i <= #s do - local j = i + w - lstart - while #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 - r:concat (s:sub (i, j)) - i = ni - if i < #s then - r:concat ("\n" .. string.rep (" ", ind)) - lstart = ind - end - end - return r:tostring () -end - - ---- Write a number using SI suffixes. --- The number is always written to 3 s.f. --- @tparam number|string n any numeric value --- @treturn string *n* simplifed using largest available SI suffix. --- @usage print (numbertosi (bitspersecond) .. "bps") -local function numbertosi (n) - argcheck ("std.string.numbertosi", 1, {"number", "string"}, 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 = t:find(".(.%...)e(.+)") - local man, exp = tonumber (m), tonumber (e) - local siexp = math.floor (exp / 3) - local shift = exp - siexp * 3 - local s = SIprefix[siexp] or "e" .. tostring (siexp) - man = man * (10 ^ shift) - return tostring (man) .. s -end - - ---- Remove leading matter from a string. --- @string s any string --- @string[opt="%s+"] r leading pattern --- @treturn string *s* with leading *r* stripped --- @usage print ("got: " .. ltrim (userinput)) -local function ltrim (s, r) - argscheck ("std.string.ltrim", {"string", "string?"}, {s, r}) - - r = r or "%s+" - return s:gsub ("^" .. r, "") -end - - ---- Remove trailing matter from a string. --- @string s any string --- @string[opt="%s+"] r trailing pattern --- @treturn string *s* with trailing *r* stripped --- @usage print ("got: " .. rtrim (userinput)) -local function rtrim (s, r) - argscheck ("std.string.rtrim", {"string", "string?"}, {s, r}) - - r = r or "%s+" - return s:gsub (r .. "$", "") -end - - ---- Remove leading and trailing matter from a string. --- @string s any string --- @string[opt="%s+"] r trailing pattern --- @treturn string *s* with leading and trailing *r* stripped --- @usage print ("got: " .. rtrim (userinput)) -local function trim (s, r) - argscheck ("std.string.trim", {"string", "string?"}, {s, r}) - - r = r or "%s+" - return s:gsub ("^" .. r, ""):gsub (r .. "$", "") -end - - --- @export M = { __concat = __concat, From 2f2dccd6e0676f2485d756d5de003d92ea1f0d1c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 7 Jun 2014 15:13:58 +0700 Subject: [PATCH 217/703] doc: add usage examples to array LDocs. * lib/std.array.lua: Add usage examples to LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 59 ++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index 39b8b0d..dcdcfd7 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -1,20 +1,20 @@ --[[-- Array of homogenous objects. - An Array is usually a block of contiguous memory, divided into equal + An array is usually a block of contiguous memory, divided into equal sized elements that can be indexed quickly. - Create a new Array with: + Create a new array with: > Array = require "std.array" > array = Array ("int", {0xdead, 0xbeef, 0xfeed}) > =array[1], array[2], array[3], array[-3], array[-4] 57005 48879 65261 57005 nil - All the indices passed to Array methods use 1-based counting. + All the indices passed to array methods use 1-based counting. If the Lua alien module is installed, and the `type` argument passed - when cloning a new Array object is suitable (i.e. the name of a numeric + when cloning a new array object is suitable (i.e. the name of a numeric C type that `alien.sizeof` understands), then the array contents are managed in an `alien.buffer`. @@ -89,7 +89,7 @@ end --[[ ================== ]]-- --- Initial Array prototype object, plus any derived object containing +-- Initial array prototype object, plus any derived object containing -- elements that don't fit in alien buffers use `core_functions` to -- find object methods and `core_metatable` for metamethods. @@ -100,6 +100,7 @@ local core_functions = { --- Remove the right-most element. -- @function pop -- @return the right-most element + -- @usage removed = anarray:pop () pop = function (self) self.length = math.max (self.length - 1, 0) return table.remove (self.buffer) @@ -110,6 +111,7 @@ local core_functions = { -- @function push -- @param elem new element to be pushed -- @return elem + -- @usage added = anarray:push (anelement) push = function (self, elem) local length = self.length + 1 self.buffer[length] = elem @@ -121,7 +123,8 @@ local core_functions = { --- Change the number of elements allocated to be at least `n`. -- @function realloc -- @int n the number of elements required - -- @treturn Array the array + -- @treturn std.array the array + -- @usage anarray = anarray:realloc (anarray.length) realloc = function (self, n) argcheck ("realloc", 2, "int", n) @@ -140,7 +143,8 @@ local core_functions = { -- @int from index of first element to set -- @param v value to store -- @int n number of elements to set - -- @treturn Array the array + -- @treturn std.array the array + -- @usage anarray:realloc (anarray.length):set (1, -1, anarray.length) set = function (self, from, v, n) argscheck ("set", {"Array", "int", "any", "int"}, {self, from, v, n}) @@ -161,6 +165,7 @@ local core_functions = { -- This makes the array 1 element shorter than it was before the shift. -- @function shift -- @return the removed element. + -- @usage removed = anarray:shift () shift = function (self) self.length = math.max (self.length - 1, 0) return table.remove (self.buffer, 1) @@ -171,6 +176,7 @@ local core_functions = { -- @function unshift -- @param elem new element to be pushed -- @treturn elem + -- @usage added = anarray:unshift (anelement) unshift = function (self, elem) self.length = self.length + 1 table.insert (self.buffer, 1, elem) @@ -183,15 +189,18 @@ core_metatable = { _type = "Array", - --- Instantiate a newly cloned Array. + --- Instantiate a newly cloned array. -- If not specified, `type` will be the same as the prototype array being -- cloned; otherwise, it can be any string. Only a type name accepted by -- `alien.sizeof` will use the fast `alien.buffer` managed memory buffer - -- for Array contents; otherwise, a much slower Lua emulation is used. + -- for array contents; otherwise, a much slower Lua emulation is used. -- @function __call -- @string type element type name -- @tparam[opt] int|table init initial size or list of initial elements - -- @treturn Array a new Array object + -- @treturn std.array a new array object + -- @usage + -- local Array = require "std.array" + -- local new = Array ("int", {1, 2, 3}) __call = function (self, type, init) if debug._ARGCHECK then if init ~= nil then @@ -210,7 +219,7 @@ core_metatable = { type = type or self.type init = init or self.length - -- This will become the cloned Array object. + -- This will become the cloned array object. local obj = {} for k, v in pairs (self) do @@ -286,6 +295,7 @@ core_metatable = { --- Iterate consecutively over all elements with `ipairs (array)`. -- @function __ipairs -- @treturn function iterator function + -- @usage for index, anelement in ipairs (anarray) do ... end __ipairs = function (self) local i, n = 0, self.length return function () @@ -297,10 +307,11 @@ core_metatable = { end, - --- Return the `n`th character in this Array. + --- Return the `n`th character in this array. -- @function __index -- @int n 1-based index, or negative to index starting from the right -- @treturn string the element at index `n` + -- @usage rightmost = anarray[anarray.length] __index = function (self, n) argcheck ("__index", 2, {"int", "string"}, n) @@ -315,11 +326,12 @@ core_metatable = { end, - --- Set the `n`th element of this Array to `elem`. + --- Set the `n`th element of this array to `elem`. -- @function __newindex -- @int n 1-based index -- @param elem value to store at index n - -- @treturn Array the array + -- @treturn std.array the array + -- @usage anarray[1] = newvalue __newindex = function (self, n, elem) argcheck ("__newindex", 2, "int", n) @@ -338,9 +350,13 @@ core_metatable = { end, - --- Return the number of elements in this Array. + --- Return the number of elements in this array. + -- + -- Beware that Lua 5.1 does not respect this metamethod; use + -- `array.length` if you care about portability. -- @function __len -- @treturn int number of elements + -- @usage length = #anarray __len = function (self) argcheck ("__len", 1, "Array", self) @@ -348,9 +364,10 @@ core_metatable = { end, - --- Return a string representation of the contents of this Array. + --- Return a string representation of the contents of this array. -- @function __tostring -- @treturn string string representation + -- @usage print (anarray) __tostring = function (self) argcheck ("__tostring", 1, "Array", self) @@ -370,7 +387,7 @@ core_metatable = { --[[ ===================== ]]-- --- Cloned Array objects with elements managed by an alien buffer use +-- Cloned array objects with elements managed by an alien buffer use -- `alien_functions` to find object methods and `alien_metatable` -- for metamethods. @@ -514,9 +531,9 @@ alien_metatable = { --- Return a function that dispatches to a virtual function table. --- The __call metamethod ensures that cloned Array objects are assigned +-- The __call metamethod ensures that cloned array objects are assigned -- a metatable and method table optimised for the element storage method --- (either alien buffer, or Lua table element containers), but the Array +-- (either alien buffer, or Lua table element containers), but the array -- prototype returned by this module needs to dispatch to the correct -- function according to the element type at run-time, because we want -- to support passing either object as an argument to a module function. @@ -534,13 +551,13 @@ end ------ -- An efficient array of homogenous objects. --- @table Array +-- @table std.array -- @int allocated number of allocated element slots, for `alien.buffer` -- managed elements -- @tfield alien.buffer|table buffer a block of indexable memory -- @int length number of elements currently stored -- @int size length of each stored element, or 0 when `alien.buffer` is --- not managing this Array +-- not managing this array -- @string type type name for elements local Array = Container { _type = "Array", From 4a25af897849c439031349fcda4d3ae271810de3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 7 Jun 2014 17:20:56 +0700 Subject: [PATCH 218/703] container: argcheck apis. * lib/std/container.lua (mapfields, __call, __tostring) (__totable): While it would be extremely convoluted to dig out the functions behind these apis to call them with non-object initial arguments, add type checking for completeness. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/std/container.lua b/lib/std/container.lua index b4b199d..afd008e 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -60,6 +60,8 @@ local base = require "std.base" +local argcheck, argscheck = base.argcheck, base.argscheck + --[[ ================= ]]-- @@ -126,6 +128,9 @@ end -- renamed according to `map` -- @see std.object.mapfields local function mapfields (obj, src, map) + argscheck ("std.container.mapfields", {"table", {"table", "object"}, "table?"}, + {obj, src, map}) + local mt = getmetatable (obj) or {} -- Map key pairs. @@ -191,6 +196,8 @@ local metatable = { -- @treturn std.container a clone of the called container. -- @see std.object:__call __call = function (self, x, ...) + argcheck ("std.container.__call", 1, "object", self) + local mt = getmetatable (self) local obj_mt = mt local obj = {} @@ -232,6 +239,8 @@ local metatable = { -- @treturn string stringified container representation -- @see std.object.__tostring __tostring = function (self) + argcheck ("std.container.__tostring", 1, "object", self) + local totable = getmetatable (self).__totable local array = instantiate (totable (self)) local other = instantiate (array) @@ -266,6 +275,8 @@ local metatable = { -- @treturn table a shallow copy of non-private container fields -- @see std.object:__totable __totable = function (self) + argcheck ("std.container.__totable", 1, "object", self) + local t = {} for k, v in pairs (self) do if type (k) ~= "string" or k:sub (1, 1) ~= "_" then From 617798facccc2767db1c70945c83452f0ffbe422 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 7 Jun 2014 16:19:13 +0700 Subject: [PATCH 219/703] doc: add usage examples to container LDocs. * lib/std/container.lua (__call, __tostring, __totable): Add usage examples to LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/container.lua b/lib/std/container.lua index afd008e..67ce7e5 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -195,6 +195,9 @@ local metatable = { -- @param ... any additional arguments for `_init` -- @treturn std.container a clone of the called container. -- @see std.object:__call + -- @usage + -- local Container = require "std.container" + -- local new = Container {"init", {"elements"}, 2, "insert"} __call = function (self, x, ...) argcheck ("std.container.__call", 1, "object", self) @@ -238,6 +241,7 @@ local metatable = { -- @function __tostring -- @treturn string stringified container representation -- @see std.object.__tostring + -- @usage print (acontainer) __tostring = function (self) argcheck ("std.container.__tostring", 1, "object", self) @@ -274,6 +278,9 @@ local metatable = { -- @function __totable -- @treturn table a shallow copy of non-private container fields -- @see std.object:__totable + -- @usage + -- local tostring = require "std.string".tostring + -- print (totable (acontainer)) __totable = function (self) argcheck ("std.container.__totable", 1, "object", self) From bff6efec705b92e23fa0986e777ab76a1c796e56 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 7 Jun 2014 16:57:38 +0700 Subject: [PATCH 220/703] doc: improve object LDocs, and add usage examples. * lib/std/object.lua: Add usage examples to apis. (clone): Normally we'd use the __call metamethod to clone from a given object, so mark the LDocs for clone as @static because it is primarily a module function. (prototype): Mark the function documentation as @static, and add equivalent method documentation. Signed-off-by: Gary V. Vaughan --- lib/std/object.lua | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/std/object.lua b/lib/std/object.lua index 6678cae..f71f2b9 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -86,13 +86,17 @@ return Container { -- `\_\_index`. __index = { - --- Clone this Object. + --- Clone an Object. + -- @static -- @function clone -- @tparam std.object obj an object - -- @param ... a list of arguments if `o._init` is a function, or a - -- single table if `o._init` is a table. - -- @treturn std.object a clone of `o` + -- @param ... a list of arguments if `obj._init` is a function, or a + -- single table if `obj._init` is a table. + -- @treturn std.object a clone of *obj* -- @see __call + -- @usage + -- local object = require "std.object" + -- new = object.clone (object, {"foo", "bar"}) clone = metamethod (Container, "__call"), @@ -105,9 +109,11 @@ return Container { -- Additionally, this function returns the results of `io.type` for -- file objects, or `type` otherwise. -- + -- @static -- @function prototype -- @param x anything - -- @treturn string type of `x` + -- @treturn string type of *x* + -- @see std.object:prototype -- @usage -- local Stack = Object { -- _type = "Stack", @@ -129,6 +135,15 @@ return Container { -- assert (prototype (h) == io.type (h)) -- -- assert (prototype {} == type {}) + + --- Type of this object. + -- + -- Additionally, this function returns the results of `io.type` for + -- file objects, or `type` otherwise. + -- @function prototype + -- @treturn string type of this object + -- @see std.object.prototype + -- @usage if object:prototype () ~= "table" then ... end prototype = Container.prototype.call, @@ -149,6 +164,7 @@ return Container { -- `_init` is safer than manually splitting `src` into `obj` and -- its metatable, because you'll pick up fixes and changes when you -- upgrade stdlib. + -- @static -- @function mapfields -- @tparam table obj destination object -- @tparam table src fields to copy int clone @@ -156,6 +172,11 @@ return Container { -- @treturn table `obj` with non-private fields from `src` merged, -- and a metatable with private fields (if any) merged, both sets -- of keys renamed according to `map` + -- @usage + -- myobject.mapfields = function (obj, src, map) + -- object.mapfields (obj, src, map) + -- ... + -- end mapfields = Container.mapfields.call, @@ -171,6 +192,9 @@ return Container { -- @param ... arguments for `\_init` -- @treturn std.object a clone of the this object. -- @see clone + -- @usage + -- local Object = require "std.object" + -- new = Object {"initialisation", "elements"} --- Return a string representation of this object. @@ -184,6 +208,7 @@ return Container { -- @function __tostring -- @treturn string stringified container representation -- @see tostring + -- @usage print (anobject) --- Return a shallow copy of non-private object fields. @@ -194,4 +219,7 @@ return Container { -- @function __totable -- @treturn table a shallow copy of non-private object fields -- @see std.table.totable + -- @usage + -- tostring = require "std.string".tostring + -- print (totable (anobject)) } From 5aa5a8fb42ede5658f326cbbc653811a1207eb24 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Jun 2014 15:52:13 +0700 Subject: [PATCH 221/703] list: index_value and index_key return raw tables. * specs/list_spec.yaml (index_key, index_value): Specify proper behaviours. * lib/std/list.lua (index_key, index_value): A non-contiguous set of valid results cannot be represented as a std.list object, so return a raw table. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 8 ++++---- specs/list_spec.yaml | 45 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 33caa98..d325f91 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -206,9 +206,9 @@ end --- Make an index of a list of tables on a given field -- @param f field -- @tparam List l list of tables `{t1, ..., tn}` --- @treturn List index `{t1[f]=1, ..., tn[f]=n}` +-- @treturn table index `{t1[f]=1, ..., tn[f]=n}` local function index_key (f, l) - local r = List {} + local r = {} for i, v in ipairs (l) do local k = v[f] if k then @@ -222,9 +222,9 @@ end --- Copy a list of tables, indexed on a given field -- @param f field whose value should be used as index -- @tparam List l list of tables `{i1=t1, ..., in=tn}` --- @treturn List index `{t1[f]=t1, ..., tn[f]=tn}` +-- @treturn table index `{t1[f]=t1, ..., tn[f]=tn}` local function index_value (f, l) - local r = List {} + local r = {} for i, v in ipairs (l) do local k = v[f] if k then diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 4f46216..179da8e 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1,6 +1,7 @@ before: Object = require "std.object" - List = require "std.list" + list = require "std.list" + List = list l = List {"foo", "bar", "baz"} @@ -280,9 +281,51 @@ specify std.list: - describe index_key: + - context when called as a module function: + - before: + f = list.index_key + - it makes a map of matched table field values to table list offsets: + l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} + t = f ("a", l) + expect (t).to_equal {b = 1, x = 3} + for k, v in pairs (t) do + expect (k).to_equal (l[v]["a"]) + end + - it captures only the last matching list offset: + l = List {{a = "b"}, {a = "x"}, {a = "b"}} + t = f ("a", l) + expect (t.b).not_to_be (1) + expect (t.x).to_be (2) + expect (t.b).to_be (3) + - it produces incomplete indices when faced with repeated matching table values: + l = List {{1, 2, 3}, {2}, {2, 1, 3, 2, 1}} + expect (f (1, l)).to_equal {1, 3} + expect (f (2, l)).to_equal {3, 1} + expect (f (3, l)).to_equal {nil, nil, 3} - describe index_value: + - context when called as a module function: + - before: + f = list.index_value + - it makes a table of matched table field values to table list references: + l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} + t = f ("a", l) + expect (t).to_equal {b = l[1], x = l[3]} + for k, v in pairs (t) do + expect (k).to_equal (v["a"]) + end + - it captures only the last matching list offset: + l = List {{a = "b"}, {a = "x"}, {a = "b"}} + t = f ("a", l) + expect (t.b).not_to_be (l[1]) + expect (t.x).to_be (l[2]) + expect (t.b).to_be (l[3]) + - it produces incomplete indices when faced with repeated matching table values: + l = List {{1, 2, 3}, {2}, {2, 1, 3, 2, 1}} + expect (f (1, l)).to_equal {l[1], l[3]} + expect (f (2, l)).to_equal {l[3], l[1]} + expect (f (3, l)).to_equal {nil, nil, l[3]} - describe map: From f7a5c7d15e21661043188cd72aebdd2558adcc32 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Jun 2014 15:59:42 +0700 Subject: [PATCH 222/703] refactor: use list copying constructor to simplify list.append. * lib/std/list.lua (append): Use implicit copy of argument object constructor rather than slower __call constructor and explicit unpack of argument elements. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index d325f91..84cddc1 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -43,7 +43,7 @@ local List -- forward declaration -- @param x item -- @treturn List new list containing `{l[1], ..., l[#l], x}` local function append (l, x) - local r = List {unpack (l)} + local r = l {} r[#r + 1] = x return r end From c93d3f2c9735a675d52d9d37b2969aed69b45044 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Jun 2014 11:26:01 +0700 Subject: [PATCH 223/703] list: add argchecks. * specs/list_spec.yaml (append, compare, concat, cons, depair) (elems, enpair, filter, flatten, foldl, foldr, index_key) (index_value, map, map_with, project, relems, rep, reverse) (shape, sub, tail, traspose, zip_with): Specify behaviours for missing or wrong type arguments. 8 specs/object_spec.yaml: Decouple from list implementation details. * lib/std/list.lua (append, compare, concat, cons, depair) (elems, enpair, filter, flatten, foldl, foldr, index_key) (index_value, map, map_with, project, relems, rep, reverse) (shape, sub, tail, traspose, zip_with): Check argument types when not disabled by _DEBUG. * build-aux/sanity-cfg.mk: Disable bogus failures when rejecting uppercase error messages with lib/std/list.lua. Signed-off-by: Gary V. Vaughan --- build-aux/sanity-cfg.mk | 2 +- lib/std/list.lua | 122 ++++++++++++++- specs/list_spec.yaml | 329 ++++++++++++++++++++++++++++++++++++---- specs/object_spec.yaml | 5 +- 4 files changed, 418 insertions(+), 40 deletions(-) diff --git a/build-aux/sanity-cfg.mk b/build-aux/sanity-cfg.mk index 693103f..6bbf697 100644 --- a/build-aux/sanity-cfg.mk +++ b/build-aux/sanity-cfg.mk @@ -1,3 +1,3 @@ -exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/optparse.lua$$ +exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua$$ EXTRA_DIST += build-aux/sanity-cfg.mk diff --git a/lib/std/list.lua b/lib/std/list.lua index 84cddc1..2a038d1 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -28,9 +28,12 @@ ]] local base = require "std.base" +local debug = require "std.debug_init" local func = require "std.functional" local Object = require "std.object" +local argcheck, argerror, argscheck, prototype = + base.argcheck, base.argerror, base.argscheck, base.prototype local List -- forward declaration @@ -43,6 +46,8 @@ local List -- forward declaration -- @param x item -- @treturn List new list containing `{l[1], ..., l[#l], x}` local function append (l, x) + argscheck ("std.list.append", {"List", "any"}, {l, x}) + local r = l {} r[#r + 1] = x return r @@ -59,6 +64,8 @@ end -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 -- if `l` is greater than `m` local function compare (l, m) + argscheck ("std.list.compare", {"List", {"List", "table"}}, {l, m}) + for i = 1, math.min (#l, #m) do if l[i] < m[i] then return -1 @@ -92,6 +99,14 @@ local elems = base.elems -- @treturn List new list containing -- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` local function concat (l, ...) + if debug._ARGCHECK then + argcheck ("std.list.concat", 1, "List", l) + argcheck ("std.list.concat", 2, {"List", "table"}, select (1, ...)) + for i, v in ipairs {...} do + argcheck ("std.list.concat", i + 1, {"List", "table"}, v) + end + end + local r = List {} for e in elems ({l, ...}) do for v in elems (e) do @@ -107,6 +122,8 @@ end -- @param x item -- @treturn List new list containing `{x, unpack (l)}` local function cons (l, x) + argscheck ("std.list.cons", {"List", "any"}, {l, x}) + return List {x, unpack (l)} end @@ -117,6 +134,22 @@ end -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` -- @see enpair local function depair (ls) + if debug._ARGCHECK then + local fname = "std.list.depair" + argcheck (fname, 1, {"List", "table"}, ls) + + for i, v in ipairs (ls) do + local actual = prototype (v) + if actual ~= "List" and actual ~= "table" then + argerror (fname, 1, "List or table of pairs expected, got " .. + actual .. " at index " .. i, 2) + elseif #v ~= 2 then + argerror (fname, 1, "List or table of pairs expected, got " .. + #v .. "-tuple at index " .. i, 2) + end + end + end + local t = {} for v in elems (ls) do t[v[1]] = v[2] @@ -131,6 +164,8 @@ end -- @treturn List a new list containing `{{i1, v1}, ..., {in, vn}}` -- @see depair local function enpair (t) + argcheck ("std.list.enpair", 1, "table", t) + local ls = List {} for i, v in pairs (t) do ls[#ls + 1] = List {i, v} @@ -146,6 +181,8 @@ end -- `p (e)` is true -- @see std.list:filter local function filter (p, l) + argscheck ("std.list.filter", {"function", "List"}, {p, l}) + return List (func.filter (p, elems, l)) end @@ -154,6 +191,8 @@ end -- @tparam List l a list -- @treturn List flattened list local function flatten (l) + argcheck ("std.list.flatten", 1, "List", l) + local r = List {} for v in base.leaves (ipairs, l) do r[#r + 1] = v @@ -169,6 +208,8 @@ end -- @return result -- @see std.list:foldl local function foldl (fn, e, l) + argscheck ("std.list.foldl", {"function", "any?", "List"}, {fn, e, l}) + return func.fold (fn, e, elems, l) end @@ -180,6 +221,8 @@ end -- @treturn List `l` -- @return `true` local function relems (l) + argcheck ("std.list.relems", 1, {"List", "table"}, l) + local n = #l + 1 return function (l) n = n - 1 @@ -198,6 +241,8 @@ end -- @return result -- @see std.list:foldr local function foldr (fn, e, l) + argscheck ("std.list.foldr", {"function", "any?", "List"}, {fn, e, l}) + return List (func.fold (function (x, y) return fn (y, x) end, e, relems, l)) end @@ -208,6 +253,8 @@ end -- @tparam List l list of tables `{t1, ..., tn}` -- @treturn table index `{t1[f]=1, ..., tn[f]=n}` local function index_key (f, l) + argcheck ("std.list.index_key", 2, "List", l) + local r = {} for i, v in ipairs (l) do local k = v[f] @@ -224,6 +271,8 @@ end -- @tparam List l list of tables `{i1=t1, ..., in=tn}` -- @treturn table index `{t1[f]=t1, ..., tn[f]=tn}` local function index_value (f, l) + argcheck ("std.list.index_value", 2, "List", l) + local r = {} for i, v in ipairs (l) do local k = v[f] @@ -241,6 +290,8 @@ end -- @treturn List new list containing `{fn (l[1]), ..., fn (l[#l])}` -- @see std.list:map local function map (fn, l) + argscheck ("std.list.map", {"function", {"List", "table"}}, {fn, l}) + return List (func.map (fn, elems, l)) end @@ -250,16 +301,43 @@ end -- @tparam List ls a list of lists -- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` local function map_with (fn, ls) + if debug._ARGCHECK then + local fname = "std.list.map_with" + argscheck (fname, {"function", "List"}, {fn, ls}) + + for i, v in ipairs (ls) do + local actual = prototype (v) + if actual ~= "List" then + argerror (fname, 2, "List of Lists expected, got " .. + actual .. " at index " .. i, 2) + end + end + end + return List (func.map (func.compose (unpack, fn), elems, ls)) end --- Project a list of fields from a list of tables. -- @param f field to project --- @tparam List l a list +-- @tparam List l a list of tables -- @treturn List list of `f` fields -- @see std.list:project local function project (f, l) + if debug._ARGCHECK then + local fname = "std.list.project" + argcheck (fname, 2, "List", l) + + for i, v in ipairs (l) do + local actual = prototype (v) + if actual ~= "table" then + argerror (fname, 2, "List of tables expected, got " .. + actual .. " at index " .. i, 2) + end + end + end + + return map (function (t) return t[f] end, l) end @@ -269,6 +347,8 @@ end -- @int n number of times to repeat -- @treturn List `n` copies of `l` appended together local function rep (l, n) + argscheck ("std.list.rep", {"List", "int"}, {l, n}) + local r = List {} for i = 1, n do r = concat (r, l) @@ -281,6 +361,8 @@ end -- @tparam List l a list -- @treturn List new list containing `{l[#l], ..., l[1]}` local function reverse (l) + argcheck ("std.list.reverse", 1, "List", l) + local r = List {} for i = #l, 1, -1 do r[#r + 1] = l[i] @@ -310,6 +392,8 @@ end -- @return reshaped list -- @see std.list:shape local function shape (s, l) + argscheck ("std.list.shape", {"table", "List"}, {s, l}) + l = flatten (l) -- Check the shape and calculate the size of the zero, if any local size = 1 @@ -353,6 +437,8 @@ end -- @int to end of range (default: `#l`) -- @treturn List new list containing `{l[from], ..., l[to]}` local function sub (l, from, to) + argscheck ("std.list.sub", {"List", "int?", "int?"}, {l, from, to}) + local r = List {} local len = #l from = from or 1 @@ -374,6 +460,8 @@ end -- @tparam List l a list -- @treturn List new list containing `{l[2], ..., l[#l]}` local function tail (l) + argcheck ("std.list.tail", 1, "List", l) + return sub (l, 2) end @@ -386,6 +474,19 @@ end -- @treturn List new list containing -- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` local function transpose (ls) + if debug._ARGCHECK then + local fname = "std.list.transpose" + argcheck (fname, 1, {"table", "List"}, ls) + + for i, v in ipairs (ls) do + local actual = prototype (v) + if actual ~= "List" then + argerror (fname, 1, "List or table of Lists expected, got " .. + actual .. " at index " .. i, 2) + end + end + end + local rs, len = List {}, #ls for i = 1, math.max (unpack (map (ls, function (l) return #l end))) do rs[i] = List {} @@ -399,12 +500,25 @@ end --- Zip a list of lists together with a function. -- @tparam table ls list of lists --- @tparam function f function +-- @tparam function fn function -- @treturn List a new list containing -- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` -- where `N = max {map (function (l) return #l end, ls)}` -local function zip_with (ls, f) - return map_with (transpose (ls), f) +local function zip_with (ls, fn) + if debug._ARGCHECK then + local fname = "std.list.zip_with" + argscheck (fname, {"List", "function"}, {ls, fn}) + + for i, v in ipairs (ls) do + local actual = prototype (v) + if actual ~= "List" then + argerror (fname, 1, + "List of Lists expected, got " .. actual .. " at index " .. i, 2) + end + end + end + + return map_with (transpose (ls), fn) end diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 179da8e..e5b885d 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1,7 +1,7 @@ before: Object = require "std.object" list = require "std.list" - List = list + List = list {} l = List {"foo", "bar", "baz"} @@ -56,6 +56,16 @@ specify std.list: - describe append: + - before: + f = list.append + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.append' (List expected, got no value)" + expect (f (l)). + to_error "bad argument #2 to 'std.list.append' (any value expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.append' (List expected, got boolean)" - context when called as a list object method: - it returns a list object: l = l:append ("quux") @@ -81,6 +91,17 @@ specify std.list: - describe compare: - before: a, b = List {"foo", "bar"}, List {"foo", "baz"} + f = list.compare + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.compare' (List expected, got no value)" + expect (f (l)). + to_error "bad argument #2 to 'std.list.compare' (List or table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.compare' (List expected, got boolean)" + expect (f (a, false)). + to_error "bad argument #2 to 'std.list.compare' (List or table expected, got boolean)" - context when called as a list object method: - it returns -1 when the first list is less than the second: | expect (a:compare {"foo", "baz"}).to_be (-1) @@ -125,7 +146,21 @@ specify std.list: - describe concat: - - before: l = List {"foo", "bar"} + - before: + l = List {"foo", "bar"} + f = list.concat + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.concat' (List expected, got no value)" + expect (f (l)). + to_error "bad argument #2 to 'std.list.concat' (List or table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.concat' (List expected, got boolean)" + expect (f (l, false)). + to_error "bad argument #2 to 'std.list.concat' (List or table expected, got boolean)" + expect (f (l, l, false)). + to_error "bad argument #3 to 'std.list.concat' (List or table expected, got boolean)" - context when called as a list object method: - it returns a list object: @@ -154,6 +189,16 @@ specify std.list: - describe cons: + - before: + f = list.cons + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.cons' (List expected, got no value)" + expect (f (l)). + to_error "bad argument #2 to 'std.list.cons' (any value expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.cons' (List expected, got boolean)" - context when called as a list object method: - it returns a list object: l = l:cons "quux" @@ -169,9 +214,22 @@ specify std.list: - describe depair: - before: t = {"first", "second", third = 4} - l = List.enpair (t) - - - it diagnoses an argument that is not a list of lists: + l = list.enpair (t) + f = list.depair + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.depair' (List or table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.depair' (List or table expected, got boolean)" + expect (f (List {0})). + to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got number at index 1)" + expect (f (List {{}})). + to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got 0-tuple at index 1)" + expect (f (List {{"a"}})). + to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got 1-tuple at index 1)" + expect (f (List {{"a", "b"}, ""})). + to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got string at index 2)" - context when called as a list object method: - it returns a primitive table: expect (Object.type (l:depair ())).to_be "table" @@ -183,12 +241,16 @@ specify std.list: - describe elems: + - before: + f = list.elems + - it diagnoses missing arguments: | - expect (List.elems ()). + expect (f ()). to_error "bad argument #1 to 'std.list.elems' (List or table expected, got no value)" - it diagnoses wrong argument types: | - expect (List.elems (false)). + expect (f (false)). to_error "bad argument #1 to 'std.list.elems' (List or table expected, got boolean)" + - it is an iterator over list members: t = {} for e in List.elems (l) do table.insert (t, e) end @@ -210,15 +272,20 @@ specify std.list: - describe enpair: - before: t = {"first", "second", third = 4} + f = list.enpair - - it diagnoses a missing argument: - - it diagnoses a non-table argument: + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.enpair' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.enpair' (table expected, got boolean)" - it returns a list object: - expect (Object.type (List.enpair (t))).to_be "List" + expect (Object.type (f (t))).to_be "List" - it works for an empty table: - expect (List.enpair {}).to_equal (List {}) + expect (f {}).to_equal (List {}) - it turns a table into a list of pairs: - expect (List.enpair (t)). + expect (f (t)). to_equal (List {List {1, "first"}, List {2, "second"}, List {"third", 4}}) @@ -226,6 +293,18 @@ specify std.list: - before: l = List {"foo", "bar", "baz", "quux"} p = function (e) return (e:match "a" ~= nil) end + f = list.filter + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.filter' (function expected, got no value)" + expect (f (f)). + to_error "bad argument #2 to 'std.list.filter' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.filter' (function expected, got boolean)" + expect (f (f, false)). + to_error "bad argument #2 to 'std.list.filter' (List expected, got boolean)" - context when called as a list object method: - it returns a list object: @@ -241,6 +320,14 @@ specify std.list: - describe flatten: - before: l = List {List {List {"one"}}, "two", List {List {"three"}, "four"}} + f = list.flatten + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.flatten' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.flatten' (List expected, got boolean)" - context when called as a list object method: - it returns a list object: @@ -258,6 +345,18 @@ specify std.list: - before: op = require "std.functional".op l = List {1, 10, 100} + f = list.foldl + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.foldl' (function expected, got no value)" + expect (f (f, nil)). + to_error "bad argument #3 to 'std.list.foldl' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.foldl' (function expected, got boolean)" + expect (f (f, nil, false)). + to_error "bad argument #3 to 'std.list.foldl' (List expected, got boolean)" - context when called as a list object method: - it works with an empty list: @@ -271,6 +370,18 @@ specify std.list: - before: op = require "std.functional".op l = List {1, 10, 100} + f = list.foldr + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.foldr' (function expected, got no value)" + expect (f (f, nil)). + to_error "bad argument #3 to 'std.list.foldr' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.foldr' (function expected, got boolean)" + expect (f (f, nil, false)). + to_error "bad argument #3 to 'std.list.foldr' (List expected, got boolean)" - context when called as a list object method: - it works with an empty list: @@ -281,9 +392,17 @@ specify std.list: - describe index_key: + - before: + f = list.index_key + + - it diagnoses missing arguments: | + expect (f ()). + to_be "bad argument #2 to 'std.list.index_key' (List expected, got no value)" + - it diagnose wrong argument types: | + expect (f (false, false)). + to_be "bad argument #2 to 'std.list.index_key' (List expected, got boolean)" + - context when called as a module function: - - before: - f = list.index_key - it makes a map of matched table field values to table list offsets: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} t = f ("a", l) @@ -305,9 +424,17 @@ specify std.list: - describe index_value: + - before: + f = list.index_value + + - it diagnoses missing arguments: | + expect (f ()). + to_be "bad argument #2 to 'std.list.index_value' (List expected, got no value)" + - it diagnose wrong argument types: | + expect (f (false, false)). + to_be "bad argument #2 to 'std.list.index_value' (List expected, got boolean)" + - context when called as a module function: - - before: - f = list.index_value - it makes a table of matched table field values to table list references: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} t = f ("a", l) @@ -331,46 +458,73 @@ specify std.list: - describe map: - before: l = List {1, 2, 3, 4, 5} - f = function (n) return n * n end + sq = function (n) return n * n end + f = list.map + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.map' (function expected, got no value)" + expect (f (f)). + to_error "bad argument #2 to 'std.list.map' (List or table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.map' (function expected, got boolean)" + expect (f (f, false)). + to_error "bad argument #2 to 'std.list.map' (List or table expected, got boolean)" - context when called as a list object method: - it returns a list object: - m = l:map (f) + m = l:map (sq) expect (Object.type (m)).to_be "List" - it works for an empty list: l = List {} - expect (l:map (f)).to_equal (List {}) + expect (l:map (sq)).to_equal (List {}) - it creates a new list: o = l - m = l:map (f) + m = l:map (sq) expect (l).to_equal (o) expect (m).not_to_equal (o) expect (l).to_equal (List {1, 2, 3, 4, 5}) - it maps a function over a list: - expect (l:map (f)).to_equal (List {1, 4, 9, 16, 25}) + expect (l:map (sq)).to_equal (List {1, 4, 9, 16, 25}) - describe map_with: - before: l = List {List {1, 2, 3}, List {4, 5}} - f = function (...) return select ("#", ...) end + n = function (...) return select ("#", ...) end + f = list.map_with + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.map_with' (function expected, got no value)" + expect (f (f)). + to_error "bad argument #2 to 'std.list.map_with' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.map_with' (function expected, got boolean)" + expect (f (f, false)). + to_error "bad argument #2 to 'std.list.map_with' (List expected, got boolean)" + expect (f (f, List {{}})). + to_error "bad argument #2 to 'std.list.map_with' (List of Lists expected, got table at index 1)" + expect (f (f, List {List {}, false})). + to_error "bad argument #2 to 'std.list.map_with' (List of Lists expected, got boolean at index 2)" - - it diagnoses an argument that is not a list of lists: - context when called as a list object method: - it returns a list object: - m = l:map_with (f) + m = l:map_with (n) expect (Object.type (m)).to_be "List" - it works for an empty list: l = List {} - expect (l:map_with (f)).to_equal (List {}) + expect (l:map_with (n)).to_equal (List {}) - it creates a new list: o = l - m = l:map_with (f) + m = l:map_with (n) expect (l).to_equal (o) expect (m).not_to_equal (o) expect (l).to_equal (List {List {1, 2, 3}, List {4, 5}}) - it maps a function over a list: - expect (l:map_with (f)).to_equal (List {3, 2}) + expect (l:map_with (n)).to_equal (List {3, 2}) - describe project: @@ -380,8 +534,19 @@ specify std.list: {first = 1, second = 2, third = 3}, {first = "1st", second = "2nd", third = "3rd"}, } + f = list.project + + - it diagnoses missing arguments: | + expect (f (f)). + to_error "bad argument #2 to 'std.list.project' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (f, false)). + to_error "bad argument #2 to 'std.list.project' (List expected, got boolean)" + expect (f (f, List {false})). + to_error "bad argument #2 to 'std.list.project' (List of tables expected, got boolean at index 1)" + expect (f (f, List {{}, false})). + to_error "bad argument #2 to 'std.list.project' (List of tables expected, got boolean at index 2)" - - it diagnoses an argument that is not a list of tables: - context when called as a list object method: - it returns a list object: p = l:project ("third") @@ -398,6 +563,16 @@ specify std.list: - describe relems: + - before: + f = list.relems + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.relems' (List or table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.relems' (List or table expected, got boolean)" + - it is a reverse iterator over list members: t = {} for e in List.relems (l) do table.insert (t, e) end @@ -417,7 +592,20 @@ specify std.list: - describe rep: - - before: l = List {"foo", "bar"} + - before: + l = List {"foo", "bar"} + f = list.rep + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.rep' (List expected, got no value)" + expect (f (l)). + to_error "bad argument #2 to 'std.list.rep' (int expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.rep' (List expected, got boolean)" + expect (f (l, false)). + to_error "bad argument #2 to 'std.list.rep' (int expected, got boolean)" - context when called as a list object method: - it returns a list object: @@ -431,7 +619,16 @@ specify std.list: - describe reverse: - - before: l = List {"foo", "bar", "baz", "quux"} + - before: + l = List {"foo", "bar", "baz", "quux"} + f = list.reverse + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.reverse' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.reverse' (List expected, got boolean)" - context when called as a list object method: - it returns a list object: @@ -448,10 +645,38 @@ specify std.list: - describe shape: + - before: + f = list.shape + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.shape' (table expected, got no value)" + expect (f ({})). + to_error "bad argument #2 to 'std.list.shape' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.shape' (table expected, got boolean)" + expect (f ({}, false)). + to_error "bad argument #2 to 'std.list.shape' (List expected, got boolean)" + + - context when called as a list object method: - describe sub: - - before: l = List {1, 2, 3, 4, 5, 6, 7} + - before: + l = List {1, 2, 3, 4, 5, 6, 7} + f = list.sub + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.sub' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.sub' (List expected, got boolean)" + expect (f (l, false)). + to_error "bad argument #2 to 'std.list.sub' (int or nil expected, got boolean)" + expect (f (l, 1, false)). + to_error "bad argument #3 to 'std.list.sub' (int or nil expected, got boolean)" - context when called as a list object method: - it returns a list object: | @@ -473,7 +698,16 @@ specify std.list: - describe tail: - - before: l = List {1, 2, 3, 4, 5, 6, 7} + - before: + l = List {1, 2, 3, 4, 5, 6, 7} + f = list.tail + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.tail' (List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.tail' (List expected, got boolean)" - context when called as a list object method: - it returns a list object: | @@ -489,6 +723,37 @@ specify std.list: - describe transpose: + - before: + f = list.transpose + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.transpose' (table or List expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.transpose' (table or List expected, got boolean)" + + - context when called as a list object method: - describe zip_with: + - before: + l = List {} + f = list.zip_with + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.list.zip_with' (List expected, got no value)" + expect (f (l)). + to_error "bad argument #2 to 'std.list.zip_with' (function expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.list.zip_with' (List expected, got boolean)" + expect (f (List {{}}, f)). + to_error "bad argument #1 to 'std.list.zip_with' (List of Lists expected, got table at index 1)" + expect (f (List {List {}, false}, f)). + to_error "bad argument #1 to 'std.list.zip_with' (List of Lists expected, got boolean at index 2)" + expect (f (l, false)). + to_error "bad argument #2 to 'std.list.zip_with' (function expected, got boolean)" + + - context when called as a list object method: diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index e93baea..036fab2 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -260,17 +260,16 @@ specify std.object: expect (getmetatable (instance)).to_be (getmetatable (Derived)) - context with custom metamethods: - before: - compare = require "std.list".compare bag = Object { _type = "bag", - __lt = function (a, b) return compare (a, b) < 0 end, + __lt = function (a, b) return a[1] < b[1] end, } - it has it's own metatable: expect (getmetatable (bag)).not_to_be (root_mt) - it propagates prototype metatable to derived instances: instance = bag {} expect (getmetatable (instance)).to_be (getmetatable (bag)) - - it supports __lt calls: + - it supports __lt calls: | a, b = bag {"a"}, bag {"b"} expect (a < b).to_be (true) expect (a < a).to_be (false) From 0b8adfa902a1f4b4d37670c6a10cec4f390e37c1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Jun 2014 14:53:09 +0700 Subject: [PATCH 224/703] refactor: list.elems is an argument checking base.ielem wrapper. Functions in std.base are for internal use, and so all callers have already validated arguments, so we shouldn't waste time rechecking types on every call to base.elems. Also this means list.elems can be strict about only accepting List objects, and catch accidental table passing earlier. * lib/std/base.lua (elems): Move from here... (ielems): ...to here, and remove argument checking. Adjust export table. * lib/std/list.lua (elems): Re-export base.ielems when we are not argchecking, otherwise check types and call base.ielems when successful. * lib/std/debug.lua (tabify): Use non-argchecked base.ielems. * lib/std/list.lua (concat, depair, filter, foldl, map) (map_with): Likewise. * lib/std/set.lua (Set._init): Likewise. * lib/std/table.lua (merge_namedfields): Likewise. * lib/std/tree.lua (Tree.__index): Likewise. * specs/functional_spec.yaml (fold): Use List objects consistently. * specs/list_spec.yaml (elems): Adjust error message expectations. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 21 ++++++++++++--------- lib/std/debug.lua | 2 +- lib/std/list.lua | 34 ++++++++++++++++++++++++---------- lib/std/set.lua | 2 +- lib/std/table.lua | 4 ++-- lib/std/tree.lua | 4 ++-- specs/functional_spec.yaml | 7 ++++--- specs/list_spec.yaml | 4 ++-- 8 files changed, 48 insertions(+), 30 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index a2802a2..a72ef9c 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -200,18 +200,21 @@ local function deprecate (fn, name, warnmsg) end --- Doc-commented in list.lua... -local function elems (l) - argcheck ("std.list.elems", 1, {"List", "table"}, l) - +--- An iterator over the integer keyed elements of a table. +-- @function elems +-- @tparam table t a table +-- @treturn function iterator function +-- @treturn *t* +-- @return `true` +local function ielems (t) local n = 0 - return function (l) + return function (t) n = n + 1 - if n <= #l then - return l[n] + if n <= #t then + return t[n] end end, - l, true + t, true end @@ -270,7 +273,7 @@ local M = { argerror = argerror, argscheck = argscheck, deprecate = deprecate, - elems = elems, + ielems = ielems, leaves = leaves, metamethod = metamethod, prototype = prototype, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 5724542..ad1c972 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -51,7 +51,7 @@ local string = require "std.string" -- @usage s = tabify {...} local tabify = functional.compose ( -- map (elementfn, iterfn, unnbound_table_arg) - functional.bind (functional.map, {string.tostring, base.elems}), + functional.bind (functional.map, {string.tostring, base.ielems}), -- table.concat (unbound_strbuf_table, "\t") functional.bind (table.concat, {[2] = "\t"})) diff --git a/lib/std/list.lua b/lib/std/list.lua index 2a038d1..2e0b986 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -32,8 +32,8 @@ local debug = require "std.debug_init" local func = require "std.functional" local Object = require "std.object" -local argcheck, argerror, argscheck, prototype = - base.argcheck, base.argerror, base.argscheck, base.prototype +local argcheck, argerror, argscheck, ielems, prototype = + base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype local List -- forward declaration @@ -90,7 +90,21 @@ end -- of `l` -- @treturn List `l` -- @return `true` -local elems = base.elems +local elems + +if debug._ARGCHECK then + + elems = function (l) + argcheck ("std.list.elems", 1, "List", l) + return ielems (l) + end + +else + + -- Save a stack frame and a comparison on each call. + elems = ielems + +end --- Concatenate arguments into a list. @@ -108,8 +122,8 @@ local function concat (l, ...) end local r = List {} - for e in elems ({l, ...}) do - for v in elems (e) do + for e in ielems {l, ...} do + for v in ielems (e) do r[#r + 1] = v end end @@ -151,7 +165,7 @@ local function depair (ls) end local t = {} - for v in elems (ls) do + for v in ielems (ls) do t[v[1]] = v[2] end return t @@ -183,7 +197,7 @@ end local function filter (p, l) argscheck ("std.list.filter", {"function", "List"}, {p, l}) - return List (func.filter (p, elems, l)) + return List (func.filter (p, ielems, l)) end @@ -210,7 +224,7 @@ end local function foldl (fn, e, l) argscheck ("std.list.foldl", {"function", "any?", "List"}, {fn, e, l}) - return func.fold (fn, e, elems, l) + return func.fold (fn, e, ielems, l) end @@ -292,7 +306,7 @@ end local function map (fn, l) argscheck ("std.list.map", {"function", {"List", "table"}}, {fn, l}) - return List (func.map (fn, elems, l)) + return List (func.map (fn, ielems, l)) end @@ -314,7 +328,7 @@ local function map_with (fn, ls) end end - return List (func.map (func.compose (unpack, fn), elems, ls)) + return List (func.map (func.compose (unpack, fn), ielems, ls)) end diff --git a/lib/std/set.lua b/lib/std/set.lua index 49fcb8c..3c2c1ed 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -204,7 +204,7 @@ Set = Container { _type = "Set", _init = function (self, t) - for e in base.elems (t) do + for e in base.ielems (t) do insert (self, e) end return self diff --git a/lib/std/table.lua b/lib/std/table.lua index 85abb3e..2fff10c 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -16,7 +16,7 @@ local init = require "std.debug_init" local M -- forward declaration -local argcheck, argscheck, elems = base.argcheck, base.argscheck, base.elems +local argcheck, argscheck, ielems = base.argcheck, base.argscheck, base.ielems @@ -63,7 +63,7 @@ local function merge_namedfields (t, u, keys, nometa) if not nometa then setmetatable (t, getmetatable (u)) end - for k in elems (keys) do + for k in ielems (keys) do t[k] = u[k] end return t diff --git a/lib/std/tree.lua b/lib/std/tree.lua index ace57e4..1427b8a 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -15,7 +15,7 @@ local base = require "std.base" local Container = require "std.container" local func = require "std.functional" -local elems, base_leaves, prototype = base.elems, base.leaves, base.prototype +local ielems, base_leaves, prototype = base.ielems, base.leaves, base.prototype local fold, op = func.fold, func.op local Tree -- forward declaration @@ -223,7 +223,7 @@ Tree = Container { -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? __index = function (self, i) if prototype (i) == "table" then - return fold (op["[]"], self, elems, i) + return fold (op["[]"], self, ielems, i) else return rawget (self, i) end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 2f02a0a..7aabdcc 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -170,6 +170,7 @@ specify std.functional: - describe fold: - before: list = require "std.list" + List = list {} - it diagnoses missing arguments: | expect (M.fold ()). to_error "bad argument #1 to 'std.functional.fold' (function expected, got no value)" @@ -183,12 +184,12 @@ specify std.functional: expect (M.fold (M.id, 1, false)). to_error "bad argument #3 to 'std.functional.fold' (function expected, got boolean)" - it calls a binary function over element keys: - expect (M.fold (M.op["+"], 2, list.elems, {3})). + expect (M.fold (M.op["+"], 2, list.elems, List {3})). to_be (2 + 3) - expect (M.fold (M.op["*"], 2, list.elems, {3, 4})). + expect (M.fold (M.op["*"], 2, list.elems, List {3, 4})). to_be (2 * 3 * 4) - it folds elements from left to right: - expect (M.fold (math.pow, 2, list.elems, {3, 4})). + expect (M.fold (math.pow, 2, list.elems, List {3, 4})). to_be (math.pow (math.pow (2, 3), 4)) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index e5b885d..19ec47d 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -246,10 +246,10 @@ specify std.list: - it diagnoses missing arguments: | expect (f ()). - to_error "bad argument #1 to 'std.list.elems' (List or table expected, got no value)" + to_error "bad argument #1 to 'std.list.elems' (List expected, got no value)" - it diagnoses wrong argument types: | expect (f (false)). - to_error "bad argument #1 to 'std.list.elems' (List or table expected, got boolean)" + to_error "bad argument #1 to 'std.list.elems' (List expected, got boolean)" - it is an iterator over list members: t = {} From ed75bc7df816fe820175ce3325a843bb46bb640f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Jun 2014 15:32:44 +0700 Subject: [PATCH 225/703] refactor: table.metamethod is an argchecking base.getmetamethod. * lib/std/base.lua (metamethod): Rename from this... (getmetamethod): ...to this, and remove argument checking. Adjust export table. * lib/std/table.lua (metamethod): Re-export base.getmetamethod when we are not argchecking, otherwise check types and call base.getmetamethod when successful. * lib/std/object.lua (clone): Adjust. * lib/std/string.lua (render): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 29 ++++++++++++++++------------- lib/std/object.lua | 6 +++--- lib/std/string.lua | 6 +++--- lib/std/table.lua | 23 ++++++++++++++++++++--- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index a72ef9c..70cc47f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -237,10 +237,13 @@ local function leaves (it, tr) end --- Doc-commented in table.lua... -local function metamethod (x, n) - argscheck ("std.table.metamethod", {{"object", "table"}, "string"}, {x, n}) - +--- Return given metamethod, if any, or nil. +-- @tparam std.object x object to get metamethod of +-- @string n name of metamethod to get +-- @treturn function|nil metamethod function or `nil` if no metamethod or +-- not a function +-- @usage lookup = getmetamethod (require "std.object", "__index") +local function getmetamethod (x, n) local _, m = pcall (function (x) return getmetatable (x)[n] end, @@ -269,15 +272,15 @@ end local M = { - argcheck = argcheck, - argerror = argerror, - argscheck = argscheck, - deprecate = deprecate, - ielems = ielems, - leaves = leaves, - metamethod = metamethod, - prototype = prototype, - split = split, + argcheck = argcheck, + argerror = argerror, + argscheck = argscheck, + deprecate = deprecate, + getmetamethod = getmetamethod, + ielems = ielems, + leaves = leaves, + prototype = prototype, + split = split, } diff --git a/lib/std/object.lua b/lib/std/object.lua index f71f2b9..aa373ab 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -64,8 +64,8 @@ -- Container is derived from it. Confused? ;-) -local Container = require "std.container" -local metamethod = require "std.base".metamethod +local Container = require "std.container" +local getmetamethod = require "std.base".getmetamethod --- Root object. @@ -97,7 +97,7 @@ return Container { -- @usage -- local object = require "std.object" -- new = object.clone (object, {"foo", "bar"}) - clone = metamethod (Container, "__call"), + clone = getmetamethod (Container, "__call"), --- Type of an object, or primitive. diff --git a/lib/std/string.lua b/lib/std/string.lua index 004dd43..54915fb 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -15,8 +15,8 @@ local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" -local argcheck, argscheck, metamethod, split = - base.argcheck, base.argscheck, base.metamethod, base.split +local argcheck, argscheck, getmetamethod, split = + base.argcheck, base.argscheck, base.getmetamethod, base.split local _format = string.format local _tostring = _G.tostring @@ -469,7 +469,7 @@ local function render (x, open, close, elem, pair, sep, roots) return roots[x] or render (x, open, close, elem, pair, sep, table.clone (roots)) end roots = roots or {} - if type (x) ~= "table" or metamethod (x, "__tostring") then + if type (x) ~= "table" or getmetamethod (x, "__tostring") then return elem (x) else local s = StrBuf {} diff --git a/lib/std/table.lua b/lib/std/table.lua index 2fff10c..654e323 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -16,7 +16,8 @@ local init = require "std.debug_init" local M -- forward declaration -local argcheck, argscheck, ielems = base.argcheck, base.argscheck, base.ielems +local argcheck, argscheck, getmetamethod, ielems = + base.argcheck, base.argscheck, base.getmetamethod, base.ielems @@ -240,7 +241,23 @@ end -- @treturn function|nil metamethod function or `nil` if no metamethod or -- not a function -- @usage lookup = metamethod (require "std.object", "__index") -local metamethod = base.metamethod +local metamethod + +if init._ARGCHECK then + + metamethod = function (x, n) + argscheck ("std.table.metamethod", {{"object", "table"}, "string"}, {x, n}) + + return getmetamethod (x, n) + end + +else + + -- Save a stack frame and a comparison on each call when not checking + -- arguments. + metamethod = getmetamethod + +end --- Make a table with a default value for unset keys. @@ -339,7 +356,7 @@ end local function totable (x) argcheck ("std.table.totable", 1, {"object", "table", "string"}, x) - local m = metamethod (x, "__totable") + local m = getmetamethod (x, "__totable") if m then return m (x) elseif type (x) == "table" then From 2327ad2b8fa0571b9fc49a2c6a925f3d12abce79 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Jun 2014 15:43:45 +0700 Subject: [PATCH 226/703] refactor: string.split is an argchecking base.split. * lib/std/base.lua (split): Remove argument checking. * lib/std/string.lua (split): Re-export base.split when we are not argchecking, otherwise check types and call base.split when successful. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 20 ++++++++++++++++---- lib/std/string.lua | 20 ++++++++++++++++++-- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 70cc47f..feb4234 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -9,7 +9,15 @@ 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. + 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. @module std.base ]] @@ -255,10 +263,14 @@ local function getmetamethod (x, n) end --- Doc-commented in string.lua... +--- 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 local function split (s, sep) - argscheck ("std.string.split", {"string", "string?"}, {s, sep}) - sep = sep or "%s+" local b, len, t, patt = 0, #s, {}, "(.-)" .. sep if sep == "" then patt = "(.)"; t[#t + 1] = "" end diff --git a/lib/std/string.lua b/lib/std/string.lua index 54915fb..7f52e77 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -11,11 +11,12 @@ ]] local base = require "std.base" +local debug = require "std.debug_init" local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" -local argcheck, argscheck, getmetamethod, split = +local argcheck, argscheck, getmetamethod, base_split = base.argcheck, base.argscheck, base.getmetamethod, base.split local _format = string.format @@ -163,6 +164,21 @@ end -- @string[opt="%s+"] sep separator pattern -- @return list of strings -- @usage words = split "a very short sentence" +local split + +if debug._ARGCHECK then + + split = function (s, sep) + argscheck ("std.string.split", {"string", "string?"}, {s, sep}) + + return base_split (s, sep) + end + +else + + split = base_split + +end --- Require a module with a particular version. @@ -178,7 +194,7 @@ local function require_version (module, min, too_big, pattern) {module, min, too_big, pattern}) local function version_to_list (v) - return List (split (v, "%.")) + return List (base_split (v, "%.")) end local function module_version (module, pattern) return version_to_list (string.match (module.version or module._VERSION, From 422a42aae1eea8eee58a0c0780ecda0789edc674 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Jun 2014 16:07:15 +0700 Subject: [PATCH 227/703] refactor: set local _ARGCHECK instead of dereferencing debug_init. * lib/std/array.lua, lib/std/base.lua, lib/std/function.lua, lib/std/io.lua, lib/std/list.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua: Set local _ARGCHECK instead of importing std.debug_init and dereferencing it all the time. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 4 ++-- lib/std/base.lua | 4 ++-- lib/std/functional.lua | 4 ++-- lib/std/io.lua | 8 ++++---- lib/std/list.lua | 17 +++++++++-------- lib/std/package.lua | 7 ++++--- lib/std/string.lua | 5 +++-- lib/std/table.lua | 13 +++++++------ 8 files changed, 33 insertions(+), 29 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index dcdcfd7..5b7949e 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -36,7 +36,7 @@ local argcheck, argscheck = base.argcheck, base.argscheck local Container = require "std.container" local prototype = Container.prototype -local debug = require "std.debug_init" +local _ARGCHECK = require "std.debug_init"._ARGCHECK local have_alien, alien = pcall (require, "alien") local buffer, memmove, memset @@ -202,7 +202,7 @@ core_metatable = { -- local Array = require "std.array" -- local new = Array ("int", {1, 2, 3}) __call = function (self, type, init) - if debug._ARGCHECK then + if _ARGCHECK then if init ~= nil then -- When called with 2 arguments: argcheck ("Array", 1, "string", type) diff --git a/lib/std/base.lua b/lib/std/base.lua index feb4234..5f48c4f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -30,11 +30,11 @@ local function prototype (o) end -local debug = require "std.debug_init" +local _ARGCHECK = require "std.debug_init"._ARGCHECK local argcheck, argerror, argscheck -if not debug._ARGCHECK then +if not _ARGCHECK then local function nop () end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 65d7d94..6e2a877 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -6,7 +6,7 @@ local base = require "std.base" local argcheck, argscheck = base.argcheck, base.argscheck -local debug = require "std.debug_init" +local _ARGCHECK = require "std.debug_init"._ARGCHECK local functional -- forward declaration @@ -109,7 +109,7 @@ end -- a local function compose (...) local arg = {...} - if debug._ARGCHECK then + if _ARGCHECK then if #arg < 1 then argcheck ("std.functional.compose", 1, "function", nil) end diff --git a/lib/std/io.lua b/lib/std/io.lua index 17ec24e..5f2546f 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -10,8 +10,8 @@ @module std.io ]] -local base = require "std.base" -local debug = require "std.debug_init" +local base = require "std.base" +local _ARGCHECK = require "std.debug_init"._ARGCHECK local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), @@ -149,7 +149,7 @@ end -- @usage filepath = catfile ("relative", "path", "filename") local function catfile (...) local t = {...} - if debug._ARGCHECK then + if _ARGCHECK then if #t == 0 then argcheck ("std.io.catfile", 1, "string", nil) end @@ -169,7 +169,7 @@ end -- @usage dirpath = catdir ("", "absolute", "directory") local function catdir (...) local t = {...} - if debug._ARGCHECK then + if _ARGCHECK then for i, v in ipairs (t) do argcheck ("std.io.catdir", i, "string", v) end diff --git a/lib/std/list.lua b/lib/std/list.lua index 2e0b986..0c577e7 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -27,8 +27,9 @@ @classmod std.list ]] +local _ARGCHECK = require "std.debug_init"._ARGCHECK + local base = require "std.base" -local debug = require "std.debug_init" local func = require "std.functional" local Object = require "std.object" @@ -92,7 +93,7 @@ end -- @return `true` local elems -if debug._ARGCHECK then +if _ARGCHECK then elems = function (l) argcheck ("std.list.elems", 1, "List", l) @@ -113,7 +114,7 @@ end -- @treturn List new list containing -- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` local function concat (l, ...) - if debug._ARGCHECK then + if _ARGCHECK then argcheck ("std.list.concat", 1, "List", l) argcheck ("std.list.concat", 2, {"List", "table"}, select (1, ...)) for i, v in ipairs {...} do @@ -148,7 +149,7 @@ end -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` -- @see enpair local function depair (ls) - if debug._ARGCHECK then + if _ARGCHECK then local fname = "std.list.depair" argcheck (fname, 1, {"List", "table"}, ls) @@ -315,7 +316,7 @@ end -- @tparam List ls a list of lists -- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` local function map_with (fn, ls) - if debug._ARGCHECK then + if _ARGCHECK then local fname = "std.list.map_with" argscheck (fname, {"function", "List"}, {fn, ls}) @@ -338,7 +339,7 @@ end -- @treturn List list of `f` fields -- @see std.list:project local function project (f, l) - if debug._ARGCHECK then + if _ARGCHECK then local fname = "std.list.project" argcheck (fname, 2, "List", l) @@ -488,7 +489,7 @@ end -- @treturn List new list containing -- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` local function transpose (ls) - if debug._ARGCHECK then + if _ARGCHECK then local fname = "std.list.transpose" argcheck (fname, 1, {"table", "List"}, ls) @@ -519,7 +520,7 @@ end -- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` -- where `N = max {map (function (l) return #l end, ls)}` local function zip_with (ls, fn) - if debug._ARGCHECK then + if _ARGCHECK then local fname = "std.list.zip_with" argscheck (fname, {"List", "function"}, {ls, fn}) diff --git a/lib/std/package.lua b/lib/std/package.lua index edff40b..0589591 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -11,8 +11,9 @@ ]] +local _ARGCHECK = require "std.debug_init"._ARGCHECK + local base = require "std.base" -local debug = require "std.debug_init" local case = require "std.functional".case local catfile = require "std.io".catfile local invert = require "std.table".invert @@ -90,7 +91,7 @@ end -- @usage package.path = normalize (user_paths, sys_paths, package.path) local function normalize (...) local t = {...} - if debug._ARGCHECK then + if _ARGCHECK then if #t < 1 then argcheck ("std.package.normalize", 1, "string") end for i, v in ipairs (t) do argcheck ("std.package.normalize", i, "string", v) @@ -133,7 +134,7 @@ local unpack = unpack or table.unpack local function insert (pathstrings, ...) local args, types = {pathstrings, ...} - if debug._ARGCHECK then + if _ARGCHECK then if #args == 1 then types = {"string", {"int", "string"}} elseif #args == 2 then diff --git a/lib/std/string.lua b/lib/std/string.lua index 7f52e77..f3de3e7 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -10,8 +10,9 @@ @module std.string ]] +local _ARGCHECK = require "std.debug_init"._ARGCHECK + local base = require "std.base" -local debug = require "std.debug_init" local List = require "std.list" local StrBuf = require "std.strbuf" local table = require "std.table" @@ -166,7 +167,7 @@ end -- @usage words = split "a very short sentence" local split -if debug._ARGCHECK then +if _ARGCHECK then split = function (s, sep) argscheck ("std.string.split", {"string", "string?"}, {s, sep}) diff --git a/lib/std/table.lua b/lib/std/table.lua index 654e323..cc34020 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -10,8 +10,9 @@ @module std.table ]] +local _ARGCHECK = require "std.debug_init"._ARGCHECK + local base = require "std.base" -local init = require "std.debug_init" local M -- forward declaration @@ -90,7 +91,7 @@ end -- @usage -- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") local function clone (t, map, nometa) - if init._ARGCHECK then + if _ARGCHECK then local types = {"table", "table", {"boolean?", ":nometa"}} if type (map) ~= "table" then types = {"table", {"table?", "boolean?", ":nometa"}} @@ -133,7 +134,7 @@ local clone_rename = base.deprecate (function (map, t) -- @usage -- partialcopy = clone_select (original, {"this", "and_this"}, true) local function clone_select (t, keys, nometa) - if init._ARGCHECK then + if _ARGCHECK then local types = {"table", "table", {"boolean?", ":nometa"}} if type (keys) ~= "table" then types = {"table", {"table?", "boolean?", ":nometa"}} @@ -197,7 +198,7 @@ end -- @see std.table.merge_select -- @usage merge (_G, require "std.debug", {say = "log"}, ":nometa") local function merge (t, u, map, nometa) - if init._ARGCHECK then + if _ARGCHECK then local types = {"table", "table", "table", {"boolean?", ":nometa"}} if type (map) ~= "table" then types = {"table", "table", {"table?", "boolean?", ":nometa"}} @@ -222,7 +223,7 @@ end -- @see std.table.clone_select -- @usage merge_select (_G, require "std.debug", {"say"}, false) local function merge_select (t, u, keys, nometa) - if init._ARGCHECK then + if _ARGCHECK then local types = {"table", "table", "table", {"boolean?", ":nometa"}} if type (keys) ~= "table" then types = {"table", "table", {"table?", "boolean?", ":nometa"}} @@ -243,7 +244,7 @@ end -- @usage lookup = metamethod (require "std.object", "__index") local metamethod -if init._ARGCHECK then +if _ARGCHECK then metamethod = function (x, n) argscheck ("std.table.metamethod", {{"object", "table"}, "string"}, {x, n}) From a74017b69850f8d0b60925b723ef16e68a62a91a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Jun 2014 16:51:58 +0700 Subject: [PATCH 228/703] doc: fix some errors in functional usage docs. * lib/std/functional.lua (collect): list.relems requires a List. (map, filter, fold): list.elems requires a List. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 6e2a877..38e0f0e 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -186,7 +186,7 @@ end -- @see filter -- @see map -- @usage --- > =collect (std.list.relems, {"a", "b", "c"}) +-- > =collect (std.list.relems, List {"a", "b", "c"}) -- {"c", "b", "a"} local function collect (i, ...) argcheck ("std.functional.collect", 1, "function", i) @@ -205,7 +205,7 @@ end -- @return result table -- @see filter -- @usage --- > map (function (e) return e % 2 end, std.list.elements, {1, 2, 3, 4}) +-- > map (function (e) return e % 2 end, std.list.elems, List {1, 2, 3, 4}) -- {1, 0, 1, 0} local function map (f, i, ...) argscheck ("std.functional.map", {"function", "function"}, {f, i}) @@ -227,7 +227,7 @@ end -- @return result table containing elements e for which p (e) -- @see collect -- @usage --- > filter (function (e) return e % 2 == 0 end, std.list.elements, {1, 2, 3, 4}) +-- > filter (function (e) return e % 2 == 0 end, std.list.elems, List {1, 2, 3, 4}) -- {2, 4} local function filter (p, i, ...) argscheck ("std.functional.filter", {"function", "function"}, {p, i}) @@ -246,10 +246,11 @@ end -- @param f function -- @param d initial first argument -- @param i iterator +-- @param ... iterator arguments -- @return result -- @see std.list.foldl -- @see std.list.foldr --- @usage fold (math.pow, 1, std.list.elems, {2, 3, 4}) +-- @usage fold (math.pow, 1, std.list.elems, List {2, 3, 4}) local function fold (f, d, i, ...) argscheck ("std.functional.fold", {"function", "any", "function"}, {f, d, i}) From 47b2087c6657795ba118644c23d44749d92739ee Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Jun 2014 16:58:22 +0700 Subject: [PATCH 229/703] doc: add functional.memoize usage example. * lib/std/functional.lua (memoize): Add a usage example. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 38e0f0e..f506359 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -144,6 +144,8 @@ end -- @param fn function that returns a single result -- @param normalize[opt] function to normalize arguments -- @return memoized function +-- @usage +-- local fast = memoize (function (...) --[[ slow code ]] end) local function memoize (fn, normalize) argscheck ("std.functional.memoize", {"function", "function?"}, {fn, normalize}) From 3288588650b4c45cb80713a2fc392755b8dc4965 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Jun 2014 18:09:10 +0700 Subject: [PATCH 230/703] refactor: differentiate module tables and prototype objects. It turns out that documentation and code is much clearer when we differentiate between `list` (the module table for `std.list`) and `List` (the prototype List object), because that makes it explicit whether we're calling a module function (`list.append`) or performing an operation with the prototype (`List.clone {}`). As a bonus, we gain a bit of speed by cloning the prototype object from the module table, by virtue of not having a `_functions` table to administer. * lib/std/array.lua, lib/std/base.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/object.lua, lib/std/optparse.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua: Always use the module table or prototype object as appropriate. Make sure the LDocs don't contradict us. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 35 +++++++++++++++++++---------------- lib/std/base.lua | 27 ++++++++++++++------------- lib/std/container.lua | 33 ++++++++++++++++++++------------- lib/std/debug.lua | 14 ++++++++------ lib/std/functional.lua | 6 ++++-- lib/std/io.lua | 5 ++++- lib/std/list.lua | 11 ++++++++--- lib/std/math.lua | 1 + lib/std/object.lua | 42 ++++++++++++++++++++++++++++++------------ lib/std/optparse.lua | 2 +- lib/std/set.lua | 6 ++++-- lib/std/strbuf.lua | 3 ++- lib/std/string.lua | 7 +++++-- lib/std/table.lua | 7 ++++--- lib/std/tree.lua | 4 +++- 15 files changed, 127 insertions(+), 76 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index 5b7949e..62b805a 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -6,9 +6,10 @@ Create a new array with: - > Array = require "std.array" - > array = Array ("int", {0xdead, 0xbeef, 0xfeed}) - > =array[1], array[2], array[3], array[-3], array[-4] + > array = require "std.array" + > Array = array () + > a = Array ("int", {0xdead, 0xbeef, 0xfeed}) + > =a[1], a[2], a[3], a[-3], a[-4] 57005 48879 65261 57005 nil All the indices passed to array methods use 1-based counting. @@ -30,24 +31,18 @@ ]] -local base = require "std.base" -local argcheck, argscheck = base.argcheck, base.argscheck - -local Container = require "std.container" -local prototype = Container.prototype - local _ARGCHECK = require "std.debug_init"._ARGCHECK local have_alien, alien = pcall (require, "alien") -local buffer, memmove, memset -if have_alien then - buffer, memmove, memset = alien.buffer, alien.memmove, alien.memset -else - buffer = function () return {} end -end +local base = require "std.base" +local container = require "std.container" + +local Container = container {} local typeof = type +local argcheck, argscheck, prototype = + base.argcheck, base.argscheck, base.prototype --[[ ================= ]]-- @@ -55,6 +50,14 @@ local typeof = type --[[ ================= ]]-- +local buffer, memmove, memset +if have_alien then + buffer, memmove, memset = alien.buffer, alien.memmove, alien.memset +else + buffer = function () return {} end +end + + --- Number of bytes needed in an alien.buffer for each `type` element. -- @string type name of an element type -- @treturn int bytes per `type`, or 0 if alien.buffer cannot store `type`s @@ -199,7 +202,7 @@ core_metatable = { -- @tparam[opt] int|table init initial size or list of initial elements -- @treturn std.array a new array object -- @usage - -- local Array = require "std.array" + -- local Array = require "std.array" {} -- not a typo! -- local new = Array ("int", {1, 2, 3}) __call = function (self, type, init) if _ARGCHECK then diff --git a/lib/std/base.lua b/lib/std/base.lua index 5f48c4f..8e3c6ea 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -22,6 +22,9 @@ @module std.base ]] + +local _ARGCHECK = require "std.debug_init"._ARGCHECK + local typeof = type -- Doc-commented in object.lua @@ -30,21 +33,9 @@ local function prototype (o) end -local _ARGCHECK = require "std.debug_init"._ARGCHECK - local argcheck, argerror, argscheck -if not _ARGCHECK then - - local function nop () end - - -- Turn off argument checking if _DEBUG is false, or a table containing - -- a false valued `argcheck` field. - - argcheck = nop - argscheck = nop - -else +if _ARGCHECK then --- Concatenate a table of strings using ", " and " or " delimiters. -- @tparam table alternatives a table of strings @@ -165,6 +156,16 @@ else end end +else + + local function nop () end + + -- Turn off argument checking if _DEBUG is false, or a table containing + -- a false valued `argcheck` field. + + argcheck = nop + argscheck = nop + end diff --git a/lib/std/container.lua b/lib/std/container.lua index 67ce7e5..6d1fac5 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -20,19 +20,18 @@ initial prototype object returned by `require`, but are **not** passed on to derived objects during cloning: - > Container = require "std.container" - > x = Container {} - > = Container.prototype (x) + > container = require "std.container" -- module table + > Container = container {} -- prototype object + > = container:prototype () Object - > = x.prototype (o) + > = Container:prototype () stdin:1: attempt to call field 'prototype' (a nil value) ... - To add functions like this to your own prototype objects, pass a table - of the module functions in the `_functions` private field before - cloning, and those functions will not be inherited by clones. + To add module functions to your own prototype containers, pass a table + of those module functions in the `_functions` private field before + cloning, and they will not be inherited by subsequent clones. - > Container = require "std.container" > Graph = Container { >> _type = "Graph", >> _functions = { @@ -49,10 +48,18 @@ > = g.nodes nil - When making your own prototypes, start from @{std.container} if you - want to access the contents of your objects with the `[]` operator, or - @{std.object} if you want to access the functionality of your objects - with named object methods. + Cloning from the module table itself is somewhat slower than cloning + derived objects -- due to the time spent skipping over the module + table's `_function` entries by the clone constructor. You can avoid + that overhead by creating an explicit *prototype object*: + + local container = require "std.container" -- module table + local Container = container {} -- prototype object + + When making your own prototypes, derive from @{std.container} if you want + to access the contents of your objects with the `[]` operator, or from + @{std.object} if you want to access the functionality of your objects with + named object methods. @classmod std.container ]] @@ -196,7 +203,7 @@ local metatable = { -- @treturn std.container a clone of the called container. -- @see std.object:__call -- @usage - -- local Container = require "std.container" + -- local Container = require "std.container" {} -- not a typo! -- local new = Container {"init", {"elements"}, 2, "insert"} __call = function (self, x, ...) argcheck ("std.container.__call", 1, "object", self) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index ad1c972..b8f860c 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -28,8 +28,10 @@ @module std.debug ]] + +local _DEBUG = require "std.debug_init"._DEBUG + local base = require "std.base" -local init = require "std.debug_init" local functional = require "std.functional" local string = require "std.string" @@ -74,9 +76,9 @@ local function say (n, ...) level = arg[1] table.remove (arg, 1) end - if init._DEBUG and - ((type (init._DEBUG) == "table" and type (init._DEBUG.level) == "number" and - init._DEBUG.level >= level) + if _DEBUG and + ((type (_DEBUG) == "table" and type (_DEBUG.level) == "number" and + _DEBUG.level >= level) or level <= 1) then io.stderr:write (tabify (arg) .. "\n") end @@ -120,8 +122,8 @@ local function trace (event) io.stderr:write (s .. "\n") end --- Set hooks according to init._DEBUG -if type (init._DEBUG) == "table" and init._DEBUG.call then +-- Set hooks according to _DEBUG +if type (_DEBUG) == "table" and _DEBUG.call then debug.sethook (trace, "cr") end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index f506359..2fc459f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -3,11 +3,13 @@ @module std.functional ]] -local base = require "std.base" -local argcheck, argscheck = base.argcheck, base.argscheck local _ARGCHECK = require "std.debug_init"._ARGCHECK +local base = require "std.base" + +local argcheck, argscheck = base.argcheck, base.argscheck + local functional -- forward declaration diff --git a/lib/std/io.lua b/lib/std/io.lua index 5f2546f..fd5fcdc 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -10,9 +10,11 @@ @module std.io ]] -local base = require "std.base" + local _ARGCHECK = require "std.debug_init"._ARGCHECK +local base = require "std.base" + local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } @@ -20,6 +22,7 @@ local package = { local argcheck, argerror, leaves, split = base.argcheck, base.argerror, base.leaves, base.split + local M -- forward declaration diff --git a/lib/std/list.lua b/lib/std/list.lua index 0c577e7..3ed40e6 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -6,7 +6,8 @@ In addition to calling methods on list objects in OO style... - local List = require "std.list" + local list = require "std.list" -- module table + local List = list {} -- prototype object local l = List {1, 2, 3} for e in l:relems () do print (e) end => 3 @@ -17,7 +18,6 @@ argument in the first or last parameter, check the documentation for details: - local List = require "std.list" local l = List {1, 2, 3} for e in List.relems (l) do print (e) end => 3 @@ -27,17 +27,22 @@ @classmod std.list ]] + local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" local func = require "std.functional" -local Object = require "std.object" +local object = require "std.object" + local argcheck, argerror, argscheck, ielems, prototype = base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype +local Object = object {} + local List -- forward declaration + ------ -- An Object derived List. -- @table List diff --git a/lib/std/math.lua b/lib/std/math.lua index 2213252..a6b6e18 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -10,6 +10,7 @@ @module std.math ]] + local base = require "std.base" diff --git a/lib/std/object.lua b/lib/std/object.lua index aa373ab..d8adfe8 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -31,6 +31,8 @@ Objects, then, are essentially tables of `field\_n = value\_n` pairs: + > object = require "std.object" -- module table + > Object = object {} -- root object > o = Object { >> field_1 = "value_1", >> method_1 = function (self) return self.field_1 end, @@ -49,23 +51,39 @@ metatable for `new_object` that also contains a copy of all the entries in the `proto_object` metatable. - Note that Object methods are stored in the `\_\_index` field of their - metatable, and so cannot also use `\_\_index` to lookup references with + While clones of @{std.object} inherit all properties of their prototype, + it's idiomatic to always keep separate tables for the module table and + the root object itself: That way you can't mistakenly engage the slower + clone-from-module-table process accidentally if the underlying object + later changes from being an `Object` to being a `Container`. + + local object = require "std.object" -- module table + local Object = object {} -- root object + + local prototype = object.prototype + + local Derived = Object { _type = "Derived" } + + Note that Object methods are stored in the `__index` field of their + metatable, and so cannot also use `__index` to lookup references with square brackets. See @{std.container} objects if you want to do that. @classmod std.object ]] - -- Surprise!! The real root object is Container, which has less -- functionality than Object, but that makes the heirarchy hard to -- explain, so the documentation pretends this is the root object, and -- Container is derived from it. Confused? ;-) -local Container = require "std.container" -local getmetamethod = require "std.base".getmetamethod +local base = require "std.base" +local container = require "std.container" + +local Container = container {} +local getmetamethod, prototype = base.getmetamethod, base.prototype + --- Root object. @@ -83,7 +101,7 @@ return Container { -- No need for explicit module functions here, because calls to, e.g. -- `Object.prototype` will automatically fall back metamethods in - -- `\_\_index`. + -- `__index`. __index = { --- Clone an Object. @@ -97,7 +115,7 @@ return Container { -- @usage -- local object = require "std.object" -- new = object.clone (object, {"foo", "bar"}) - clone = getmetamethod (Container, "__call"), + clone = getmetamethod (container, "__call"), --- Type of an object, or primitive. @@ -143,8 +161,8 @@ return Container { -- @function prototype -- @treturn string type of this object -- @see std.object.prototype - -- @usage if object:prototype () ~= "table" then ... end - prototype = Container.prototype.call, + -- @usage if anobject:prototype () ~= "table" then ... end + prototype = prototype, --- Return `obj` with references to the fields of `src` merged in. @@ -177,11 +195,11 @@ return Container { -- object.mapfields (obj, src, map) -- ... -- end - mapfields = Container.mapfields.call, + mapfields = container.mapfields.call, -- Backwards compatibility: - type = Container.prototype.call, + type = prototype, }, @@ -193,7 +211,7 @@ return Container { -- @treturn std.object a clone of the this object. -- @see clone -- @usage - -- local Object = require "std.object" + -- local Object = require "std.object" {} -- not a typo! -- new = Object {"initialisation", "elements"} diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 5b43ece..14ec65d 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -415,7 +415,7 @@ end -- @static -- @string opt option name -- @string optarg option argument, must be an existing file --- @treturn `optarg` +-- @treturn string *optarg* local function file (self, opt, optarg) local h, errmsg = io.open (optarg, "r") if h == nil then diff --git a/lib/std/set.lua b/lib/std/set.lua index 3c2c1ed..aa19dc6 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -12,8 +12,10 @@ ]] local base = require "std.base" -local Container = require "std.container" -local prototype = require "std.object".prototype +local container = require "std.container" + +local Container = container {} +local prototype = base.prototype local Set -- forward declaration diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 114a24c..9893676 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -4,7 +4,8 @@ ]] -local Object = require "std.object" +local object = require "std.object" +local Object = object {} --- Add a string to a buffer. diff --git a/lib/std/string.lua b/lib/std/string.lua index f3de3e7..c324e46 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -13,10 +13,13 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" -local List = require "std.list" -local StrBuf = require "std.strbuf" +local list = require "std.list" +local strbuf = require "std.strbuf" local table = require "std.table" +local List = list {} +local StrBuf = strbuf {} + local argcheck, argscheck, getmetamethod, base_split = base.argcheck, base.argscheck, base.getmetamethod, base.split diff --git a/lib/std/table.lua b/lib/std/table.lua index cc34020..6e3d2fe 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -10,17 +10,18 @@ @module std.table ]] + local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" - -local M -- forward declaration - local argcheck, argscheck, getmetamethod, ielems = base.argcheck, base.argscheck, base.getmetamethod, base.ielems +local M -- forward declaration + + --[[ ================= ]]-- --[[ Helper Functions. ]]-- diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 1427b8a..8840a66 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -12,9 +12,11 @@ ]] local base = require "std.base" -local Container = require "std.container" +local container = require "std.container" local func = require "std.functional" +local Container = container {} + local ielems, base_leaves, prototype = base.ielems, base.leaves, base.prototype local fold, op = func.fold, func.op From edba31e4c3f67ac3810bf530ee075a080401edb9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 10 Jun 2014 15:10:14 +0700 Subject: [PATCH 231/703] list: prefer numeric comparison to asciibetical in compare. Close #60. * lib/std/list.lua (compare): If tonumber can make numbers out of both arguments, use those results in preference to strings. * specs/list_spec.yaml (compare): Specify behaviours with elements that can be coerced to numbers. * specs/string_spec.yaml (require_string): Remove pending #60 commands. Add some more happy path examples. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 8 ++++++-- specs/list_spec.yaml | 15 +++++++++++++++ specs/string_spec.yaml | 12 +++++++++--- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 3ed40e6..45430a8 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -73,9 +73,13 @@ local function compare (l, m) argscheck ("std.list.compare", {"List", {"List", "table"}}, {l, m}) for i = 1, math.min (#l, #m) do - if l[i] < m[i] then + 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 l[i] > m[i] then + elseif li > mi then return 1 end end diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 19ec47d..09cdb93 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -119,30 +119,45 @@ specify std.list: - it returns +1 when the first list has additional elements: | expect (a:compare {"foo"}).to_be (1) expect (a:compare (List {"foo"})).to_be (1) + - it compares numerically when both arguments can be coerced: + a, b = List {"1", "2", "3"}, List {"1", "2", "10"} + expect (a:compare (b)).to_be (-1) - context when called as a '<' list metamethod: - it succeeds when the first list is less than the second: expect (a < b).to_be (true) - it fails when the first list is not less than the second: expect (a < a).to_be (false) expect (b < a).to_be (false) + - it compares numerically when both arguments can be coerced: + a, b = List {"1", "2", "3"}, List {"1", "2", "10"} + expect (a < b).to_be (true) - context when called as a '>' list metamethod: - it succeeds when the first list is greater than the second: expect (b > a).to_be (true) - it fails when the first list is not greater than the second: expect (b > b).to_be (false) expect (a > b).to_be (false) + - it compares numerically when both arguments can be coerced: + a, b = List {"1", "2", "3"}, List {"1", "2", "10"} + expect (a > b).to_be (false) - context when called as a '<=' list metamethod: - it succeeds when the first list is less than or equal to the second: expect (a <= b).to_be (true) expect (a <= a).to_be (true) - it fails when the first list is not less than or equal to the second: expect (b <= a).to_be (false) + - it compares numerically when both arguments can be coerced: + a, b = List {"1", "2", "3"}, List {"1", "2", "10"} + expect (a <= b).to_be (true) - context when called as a '>=' list metamethod: - it succeeds when the first list is greater than or equal to the second: expect (b >= a).to_be (true) expect (b >= b).to_be (true) - it fails when the first list is not greater than or equal to the second: expect (a >= b).to_be (false) + - it compares numerically when both arguments can be coerced: + a, b = List {"1", "2", "3"}, List {"1", "2", "10"} + expect (a >= b).to_be (false) - describe concat: diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index cbfd24b..84b8207 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -568,7 +568,6 @@ specify std.string: expect (f ("std", "1.3")).to_error () expect (f ("std", "2.1.2")).to_error () expect (f ("std", "2")).to_error () - pending "issue #60" expect (f ("std", "1.2.10")).to_error () - it diagnoses module too new: | std = require "std" @@ -578,8 +577,15 @@ specify std.string: expect (f ("std", nil, "1.1.2")).to_error () expect (f ("std", nil, "1")).to_error () - it returns modules with version in range: | - pending "issue #60" - expect (f ("std", nil, "1.2.10")).not_to_error () + std = require "std" + std.version = "1.2.3" + 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) - describe rtrim: From 7d3b294d81518c6c2fa1b05ff95ac49bc9fbed08 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 13 Jun 2014 11:52:49 +0700 Subject: [PATCH 232/703] doc: disable LDoc backtick references. * build-aux/config.ld.in (backtick_references): Set to false, so we can write fixed-width font words in LDoc comments. Signed-off-by: Gary V. Vaughan --- build-aux/config.ld.in | 1 + 1 file changed, 1 insertion(+) diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index ce0b186..b394643 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -28,4 +28,5 @@ file = { } format = "markdown" +backtick_references = false sort = true From 6dc5009a93d6486a95d6bc40e130132705f255ba Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 10 Jun 2014 17:29:19 +0700 Subject: [PATCH 233/703] table: add elems and ielems module functions. * specs/table_spec.yaml (elems, ielems): Specify behaviour of new iterators. * lib/std/table.lua (elems): Iterate over all values of a table, for orthogonality with std.list and std.set. (ielems): Expose base.ielems in a type checking wrapper. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++++ lib/std/base.lua | 1 - lib/std/table.lua | 39 ++++++++++++++++++++++++++++-- specs/table_spec.yaml | 55 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 92 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index e154e8c..d5670cd 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,10 @@ Stdlib NEWS - User visible changes container management. When alien is installed, and element types are compatible, use alien.buffers for efficient element management. + - New `table.elems` and `table.ielems` functions for iterating table + values cleanly, and for orthogonality between std.table, std.base, + std.list and std.set. + ** Incompatible changes: - `functional.bind` sets fixed positional arguments when called as diff --git a/lib/std/base.lua b/lib/std/base.lua index 8e3c6ea..a76d7de 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -210,7 +210,6 @@ end --- An iterator over the integer keyed elements of a table. --- @function elems -- @tparam table t a table -- @treturn function iterator function -- @treturn *t* diff --git a/lib/std/table.lua b/lib/std/table.lua index 6e3d2fe..22b6301 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -15,7 +15,7 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" -local argcheck, argscheck, getmetamethod, ielems = +local argcheck, argscheck, getmetamethod, base_ielems = base.argcheck, base.argscheck, base.getmetamethod, base.ielems @@ -66,7 +66,7 @@ local function merge_namedfields (t, u, keys, nometa) if not nometa then setmetatable (t, getmetatable (u)) end - for k in ielems (keys) do + for k in base_ielems (keys) do t[k] = u[k] end return t @@ -147,6 +147,26 @@ local function clone_select (t, keys, nometa) end +--- An iterator over all values of a table. +-- @tparam table t a table +-- @treturn function iterator function +-- @treturn *t* +-- @return `true` +-- @usage for func in elems (_G) do ... end +local function elems (t) + argcheck ("std.table.elems", 1, "table", t) + + local k, v = nil + return function (t) + k, v = next (t, k) + if k then + return v + end + end, + t, true +end + + --- Return whether table is empty. -- @tparam table t any table -- @return `true` if *t* is empty, otherwise `false` @@ -158,6 +178,19 @@ local function empty (t) end +--- An iterator over the integer keyed elements of a table. +-- @tparam table t a table +-- @treturn function iterator function +-- @treturn *t* +-- @return `true` +-- @usage for value in ielems {"a", "b", "c"} do ... end +local function ielems (t) + argcheck ("std.table.ielems", 1, "table", t) + + return base_ielems (t) +end + + --- Invert a table. -- @tparam table t a table with `{k=v, ...}` -- @treturn table inverted table `{v=k, ...}` @@ -392,7 +425,9 @@ end M = { clone = clone, clone_select = clone_select, + elems = elems, empty = empty, + ielems = ielems, invert = invert, keys = keys, merge = merge, diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 14c7483..254bd19 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -3,10 +3,11 @@ before: | this_module = "std.table" global_table = "_G" - extend_base = { "clone", "clone_rename", "clone_select", "empty", - "invert", "keys", "merge", "merge_select", - "metamethod", "monkey_patch", "new", "pack", - "ripairs", "size", "sort", "totable", "values" } + extend_base = { "clone", "clone_rename", "clone_select", "elems", + "empty", "ielems", "invert", "keys", "merge", + "merge_select", "metamethod", "monkey_patch", "new", + "pack", "ripairs", "size", "sort", "totable", + "values" } M = require "std.table" @@ -146,6 +147,29 @@ specify std.table: expect (getmetatable (f (withmt, {"k1"}, ":nometa"))).to_be (nil) +- describe elems: + - before: + f = M.elems + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.elems' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.elems' (table expected, got boolean)" + + - 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 works for an empty list: + t = {} + for e in f {} do t[#t + 1] = e end + expect (t).to_equal {} + + - describe empty: - before: f = M.empty @@ -164,6 +188,29 @@ specify std.table: expect (f {false}).to_be (false) +- describe ielems: + - before: + f = M.ielems + + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.table.ielems' (table expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.table.ielems' (table expected, got boolean)" + + - it is an iterator over integer-keyed table values: + t = {} + for e in f {"foo", bar = "baz", 42} do + t[#t + 1] = e + end + expect (t).to_equal {"foo", 42} + - it works for an empty list: + t = {} + for e in f {} do t[#t + 1] = e end + expect (t).to_equal {} + + - describe invert: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } From 3c77081714c668ba7d72f10f62ddbff5363d11fc Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 12 Jun 2014 13:17:41 +0700 Subject: [PATCH 234/703] list: deprecate index_key and index_value. * specs/list_spec.yaml (index_key, index_value): Check that the functions issue a depraction warning on first use. * lib/std/list.lua (index_key, index_value): Add deprecation warning with base.deprecate. * NEWS (Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 +++++ lib/std/list.lua | 73 +++++++++++++++++++++++++------------------- specs/list_spec.yaml | 20 ++++++++++++ 3 files changed, 69 insertions(+), 32 deletions(-) diff --git a/NEWS b/NEWS index d5670cd..6fa7318 100644 --- a/NEWS +++ b/NEWS @@ -41,6 +41,14 @@ Stdlib NEWS - User visible changes need to remove the previously ignored arguments that correspond to the fixed argument positions in the `bind` invocation. + - `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 will continue to work + for another release or two, issuing a deprecation warning on first + use. After that, in some future release, they will be removed + entirely. + - `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, diff --git a/lib/std/list.lua b/lib/std/list.lua index 45430a8..c23d24e 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -272,11 +272,12 @@ local function foldr (fn, e, l) end +-- DEPRECATED: Remove in first release following 2015-06-07. --- Make an index of a list of tables on a given field -- @param f field -- @tparam List l list of tables `{t1, ..., tn}` -- @treturn table index `{t1[f]=1, ..., tn[f]=n}` -local function index_key (f, l) +local index_key = base.deprecate (function (f, l) argcheck ("std.list.index_key", 2, "List", l) local r = {} @@ -287,14 +288,16 @@ local function index_key (f, l) end end return r -end +end, nil, + "list.index_key is deprecated, use list.filter and table.invert instead.") ---- Copy a list of tables, indexed on a given field +-- DEPRECATED: Remove in first release following 2015-06-07. +-- Copy a list of tables, indexed on a given field -- @param f field whose value should be used as index -- @tparam List l list of tables `{i1=t1, ..., in=tn}` -- @treturn table index `{t1[f]=t1, ..., tn[f]=tn}` -local function index_value (f, l) +local index_value = base.deprecate (function (f, l) argcheck ("std.list.index_value", 2, "List", l) local r = {} @@ -305,7 +308,8 @@ local function index_value (f, l) end end return r -end +end, nil, + "list.index_value is deprecated, use list.filter and table.invert instead.") --- Map a function over a list. @@ -546,6 +550,37 @@ local function zip_with (ls, fn) end +--- @export +local _functions = { + append = append, + compare = compare, + concat = concat, + cons = cons, + depair = depair, + elems = elems, + enpair = enpair, + filter = filter, + flatten = flatten, + foldl = foldl, + foldr = foldr, + map = map, + map_with = map_with, + project = project, + relems = relems, + rep = rep, + reverse = reverse, + shape = shape, + sub = sub, + tail = tail, + transpose = transpose, + zip_with = zip_with, +} + +-- Deprecated and undocumented. +_functions.index_key = index_key +_functions.index_value = index_value + + List = Object { -- Derived object type. _type = "List", @@ -734,33 +769,7 @@ List = Object { }, - --- @export - _functions = { - append = append, - compare = compare, - concat = concat, - cons = cons, - depair = depair, - elems = elems, - enpair = enpair, - filter = filter, - flatten = flatten, - foldl = foldl, - foldr = foldr, - index_key = index_key, - index_value = index_value, - map = map, - map_with = map_with, - project = project, - relems = relems, - rep = rep, - reverse = reverse, - shape = shape, - sub = sub, - tail = tail, - transpose = transpose, - zip_with = zip_with, - }, + _functions = _functions, } diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 09cdb93..59b4cbd 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -410,6 +410,16 @@ specify std.list: - before: f = list.index_key + - it writes a deprecation warning to standard error on first call: | + f = list.index_key.call + _, err = capture (f, {1, List {{1}}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "index_key is deprecated" + end + _, err = capture (f, {1, List {{1}}}) + expect (err).to_be (nil) + - it diagnoses missing arguments: | expect (f ()). to_be "bad argument #2 to 'std.list.index_key' (List expected, got no value)" @@ -442,6 +452,16 @@ specify std.list: - before: f = list.index_value + - it writes a deprecation warning to standard error on first call: | + f = list.index_value.call + _, err = capture (f, {1, List {{1}}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "index_value is deprecated" + end + _, err = capture (f, {1, List {{1}}}) + expect (err).to_be (nil) + - it diagnoses missing arguments: | expect (f ()). to_be "bad argument #2 to 'std.list.index_value' (List expected, got no value)" From 0a0707fbcc9b46a0c8f55baff212897db9efb62d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 10 Jun 2014 20:45:37 +0700 Subject: [PATCH 235/703] refactor: use a function to export math apis. * lib/std/base.lua (export): New function. Add a function to the module export table, with or without argument checking as appropriate. * lib/std/math.lua (floor, monkey_path, round): Simplify accordingly. (M): Store the module prefix at index 1, for export argerror calls. * specs/spec_helper.lua.in (show_apis): Ignore module prefix at index 1 of export table. * specs/math_spec.yaml (round): Correct a typo in argerrors. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 23 +++++++++++++++++++++++ lib/std/math.lua | 40 ++++++++++++++-------------------------- specs/math_spec.yaml | 6 +++--- specs/spec_helper.lua.in | 4 +++- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index a76d7de..fd68f38 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -209,6 +209,28 @@ local function deprecate (fn, name, warnmsg) end +--- Export a function definition, optionally with argument type checking. +-- @tparam table M module table +-- @string name key in *M* for *fn* +-- @tparam table types *fn* argument type constraints +-- @func fn value to store at *name* in *M* +local function export (M, name, types, fn) + if _ARGCHECK then + argscheck ("std.base.export", {"table", "string", "#table", "function"}, + {M, name, types, fn}) + + -- When argument checking is enabled, wrap in type checking function. + local name, inner = M[1] .. "." .. name, fn + fn = function (...) + argscheck (name, types, {...}) + return inner (...) + end + end + + M[name] = fn +end + + --- An iterator over the integer keyed elements of a table. -- @tparam table t a table -- @treturn function iterator function @@ -288,6 +310,7 @@ local M = { argerror = argerror, argscheck = argscheck, deprecate = deprecate, + export = export, getmetamethod = getmetamethod, ielems = ielems, leaves = leaves, diff --git a/lib/std/math.lua b/lib/std/math.lua index a6b6e18..fbb2d4f 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,67 +11,55 @@ ]] -local base = require "std.base" +local export = require "std.base".export - -local _floor = math.floor -local argcheck, argscheck = base.argcheck, base.argscheck - - -local M -- forward declaration +local M = { "std.math" } --- 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) -local function floor (n, p) - argscheck ("std.math.floor", {"number", "int?"}, {n, p}) +local _floor = math.floor + +export (M, "floor", {"number", "int?"}, function (n, p) if p and p ~= 0 then local e = 10 ^ p return _floor (n * e) / e else return _floor (n) end -end +end) --- Overwrite core methods with `std` enhanced versions. -- -- Replaces core `math.floor` with `std.math` version. +-- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage require "std.math".monkey_patch () -local function monkey_patch (namespace) - argcheck ("std.math.monkey_patch", 1, "table?", namespace) +export (M, "monkey_patch", {"table?"}, function (namespace) namespace = namespace or _G - - namespace.math.floor = floor + namespace.math.floor = M.floor return M -end +end) --- 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) -local function round (n, p) - argscheck ("std.math.floor", {"number", "int?"}, {n, p}) - +export (M, "round", {"number", "int?"}, function (n, p) local e = 10 ^ (p or 0) return _floor (n * e + 0.5) / e -end - +end) ---- @export -local M = { - floor = floor, - monkey_patch = monkey_patch, - round = round, -} for k, v in pairs (math) do M[k] = M[k] or v diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 9275267..475d130 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -74,12 +74,12 @@ specify std.math: f = M.round - it diagnoses missing arguments: | expect (f ()). - to_error "bad argument #1 to 'std.math.floor' (number expected, got no value)" + to_error "bad argument #1 to 'std.math.round' (number expected, got no value)" - it diagnoses wrong argument types: | expect (f (1.2, false)). - to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got boolean)" + to_error "bad argument #2 to 'std.math.round' (int or nil expected, got boolean)" expect (f (1.2, 3.4)). - to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got number)" + to_error "bad argument #2 to 'std.math.round' (int or nil expected, got number)" - it rounds to the nearest integer: expect (f (1.2)).to_be (1) expect (f (1.9)).to_be (2) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 91ea6d0..90a088a 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -115,7 +115,9 @@ function show_apis (argt) local M = require "]] .. not_in .. [[" for k in pairs (M) do - if from[k] ~= M[k] then print (k) end + -- 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 ]]) From e6ef015ed228eb7d28c77ebba4d139c04fb69ca0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Jun 2014 00:11:37 +0700 Subject: [PATCH 236/703] refactor: use a function to export functional apis. * lib/std/base.lua (argcheck): Accept "func" as an alias for "function". (export): When the last types element ends with "*", check type of remaining unchecked args against it. If there is no "*" mark in the types list, and more arguments are passed than types entries, throw a "too many arguments" error. Return the unwrapped function argument, so it can be captured back into a local by the caller. * specs/functional_spec.yaml (case, curry, eval, memoize): Check these fixed argument functions throw a "too many arguments" error when called with too many arguments. * specs/math_spec.yaml (floor, monkey_patch, round): Likewise. * lib/std/functional.lua (bind, case, collect, compose, curry) (eval, filter, fold, map, memoize): Use base.export for conditional argument checking. Simplify accordingly. (functional): Rename from this... (M): ...to this. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 42 ++++++++-- lib/std/functional.lua | 155 +++++++++++++++---------------------- specs/functional_spec.yaml | 12 +++ specs/math_spec.yaml | 9 +++ 4 files changed, 120 insertions(+), 98 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index fd68f38..da66e4a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -96,7 +96,8 @@ if _ARGCHECK then ok = true end - elseif check == "function" then + elseif check == "function" or check == "func" then + expected[i] = "function" if actualtype == "function" or (getmetatable (actual) or {}).__call ~= nil then @@ -210,24 +211,55 @@ end --- Export a function definition, optionally with argument type checking. +-- In addition to checking that each argument type matches the corresponding +-- element in the *types* table with `argcheck`, if the final element of +-- *types* ends with an asterisk, remaining unchecked arguments are checked +-- against that type. -- @tparam table M module table -- @string name key in *M* for *fn* -- @tparam table types *fn* argument type constraints -- @func fn value to store at *name* in *M* local function export (M, name, types, fn) + local inner = fn + + -- When argument checking is enabled, wrap in type checking function. if _ARGCHECK then argscheck ("std.base.export", {"table", "string", "#table", "function"}, - {M, name, types, fn}) + {M, name, types, inner}) + + local name = M[1] .. "." .. name + + local max, fin = #types, types[#types]:match "^(.+)%*$" + if fin then + max = math.huge + types[#types] = fin + end - -- When argument checking is enabled, wrap in type checking function. - local name, inner = M[1] .. "." .. name, fn fn = function (...) - argscheck (name, types, {...}) + local args = {...} + local typec, argc = #types, #args + for i = 1, typec do + argcheck (name, i, types[i], args[i]) + end + if max == math.huge then + for i = typec + 1, argc do + argcheck (name, i, types[typec], args[i]) + end + end + + if argc > max then + local fmt + fmt = "too many arguments to '%s' (no more than %d expected, got %d)" + error (string.format (fmt, name, max, argc), 2) + end + return inner (...) end end M[name] = fn + + return inner end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 2fc459f..6c0c88f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -4,34 +4,30 @@ ]] -local _ARGCHECK = require "std.debug_init"._ARGCHECK +local export = require "std.base".export -local base = require "std.base" - -local argcheck, argscheck = base.argcheck, base.argscheck - -local functional -- forward declaration +local M = { "std.functional" } --- Identity function. +-- @function id -- @param ... -- @return the arguments passed to the function -local function id (...) +function M.id (...) return ... end --- Partially apply a function. --- @param f function to apply partially --- @tparam t table {p1=a1, ..., pn=an} table of parameters to bind to given arguments --- @return function with pi already bound +-- @function bind +-- @func f function to apply partially +-- @tparam table t {p1=a1, ..., pn=an} table of parameters to bind to given arguments +-- @return function with *pi* already bound -- @usage -- > cube = bind (math.pow, {[2] = 3}) -- > =cube (2) -- 8 -local function bind (f, ...) - argscheck ("std.functional.bind", "function", f) - +local bind = export (M, "bind", {"func", "any?*"}, function (f, ...) local fix = {...} -- backwards compatibility with old API; DEPRECATED: remove in first release after 2015-04-21 if type (fix[1]) == "table" and fix[2] == nil then fix = fix[1] @@ -48,13 +44,14 @@ local function bind (f, ...) end return f (unpack (arg)) end -end +end) --- A rudimentary case statement. -- Match `with` against keys in `branches` table, and return the result -- of running the function in the table value for the matching key, or -- the first non-key value function if no key matches. +-- @function case -- @param with expression to match -- @tparam table branches map possible matches to functions -- @return the return value from function with a matching key, or nil. @@ -64,26 +61,24 @@ end -- string = function () return something else end, -- function (s) error ("unhandled type: "..s) end, -- }) -local function case (with, branches) - argcheck ("std.functional.case", 2, "#table", branches) - +export (M, "case", {"any?", "#table"}, function (with, branches) local fn = branches[with] or branches[1] if fn then return fn (with) end -end +end) --- Curry a function. --- @param f function to curry --- @param n number of arguments --- @return curried version of f +-- @function curry +-- @func f function to curry +-- @int n number of arguments +-- @treturn function curried version of *f* -- @usage -- > add = curry (function (x, y) return x + y end, 2) -- > incr, decr = add (1), add (-1) -- > =incr (99), decr (99) -- 100 98 -local function curry (f, n) - argscheck ("std.functional.curry", {"function", "int"}, {f, n}) - +local curry +curry = export (M, "curry", {"func", "int"}, function (f, n) if n <= 1 then return f else @@ -91,13 +86,14 @@ local function curry (f, n) return curry (bind (f, x), n - 1) end end -end +end) --- Compose functions. --- @tparam function ... functions to compose --- @return composition of fn (... (f1) ...): note that this is the reverse --- of what you might expect, but means that code like: +-- @function compose +-- @func ... functions to compose +-- @treturn function composition of fn (... (f1) ...): note that this is the +-- reverse of what you might expect, but means that code like: -- -- functional.compose (function (x) return f (x) end, -- function (x) return g (x) end)) @@ -109,17 +105,8 @@ end -- b -- c -- a -local function compose (...) +export (M, "compose", {"func*"}, function (...) local arg = {...} - if _ARGCHECK then - if #arg < 1 then - argcheck ("std.functional.compose", 1, "function", nil) - end - for i in ipairs (arg) do - argcheck ("std.functional.compose", i, "function", arg[i]) - end - end - local fns, n = arg, #arg return function (...) local arg = {...} @@ -128,7 +115,7 @@ local function compose (...) end return unpack (arg) end -end +end) --- Signature of memoize `normalize` functions. @@ -143,15 +130,13 @@ end -- arguments, it passes arguments to `normalize` (std.string.tostring -- by default). You may need a more sophisticated function if memoize -- should handle complicated argument equivalencies. --- @param fn function that returns a single result --- @param normalize[opt] function to normalize arguments --- @return memoized function +-- @function memoize +-- @func fn function that returns a single result +-- @func normalize[opt] function to normalize arguments +-- @treturn functable memoized function -- @usage -- local fast = memoize (function (...) --[[ slow code ]] end) -local function memoize (fn, normalize) - argscheck ("std.functional.memoize", {"function", "function?"}, - {fn, normalize}) - +export (M, "memoize", {"func", "func?"}, function (fn, normalize) if normalize == nil then -- Call require here, to avoid pulling in all of 'std.string' -- even when memoize is never called. @@ -170,50 +155,49 @@ local function memoize (fn, normalize) return v end }) -end +end) --- Evaluate a string. +-- @function eval -- @string s string of Lua code -- @return result of evaluating `s` -- @usage eval "math.pow (2, 10)" -local function eval (s) - argscheck ("std.functional.eval", "string", s) +export (M, "eval", {"string"}, function (s) return loadstring ("return " .. s)() -end +end) --- Collect the results of an iterator. --- @tparam function i iterator --- @param ... arguments --- @return results of running the iterator on *arguments +-- @function collect +-- @func i iterator +-- @param ... iterator arguments +-- @return results of running the iterator on *arguments* -- @see filter -- @see map -- @usage -- > =collect (std.list.relems, List {"a", "b", "c"}) -- {"c", "b", "a"} -local function collect (i, ...) - argcheck ("std.functional.collect", 1, "function", i) - +export (M, "collect", {"func", "any*"}, function (i, ...) local t = {} for e in i (...) do t[#t + 1] = e end return t -end +end) --- Map a function over an iterator. --- @tparam function f function --- @tparam function i iterator --- @return result table +-- @function map +-- @func f function +-- @func i iterator +-- @param ... iterator arguments +-- @treturn table results -- @see filter -- @usage -- > map (function (e) return e % 2 end, std.list.elems, List {1, 2, 3, 4}) -- {1, 0, 1, 0} -local function map (f, i, ...) - argscheck ("std.functional.map", {"function", "function"}, {f, i}) - +export (M, "map", {"func", "func", "any*"}, function (f, i, ...) local t = {} for e in i (...) do local r = f (e) @@ -222,20 +206,20 @@ local function map (f, i, ...) end end return t -end +end) --- Filter an iterator with a predicate. --- @param p predicate --- @param i iterator --- @return result table containing elements e for which p (e) +-- @function filter +-- @func p predicate +-- @func i iterator +-- @param ... iterator arguments +-- @treturn table elements e for which `p (e)` is not falsey. -- @see collect -- @usage -- > filter (function (e) return e % 2 == 0 end, std.list.elems, List {1, 2, 3, 4}) -- {2, 4} -local function filter (p, i, ...) - argscheck ("std.functional.filter", {"function", "function"}, {p, i}) - +export (M, "filter", {"func", "func", "any*"}, function (p, i, ...) local t = {} for e in i (...) do if p (e) then @@ -243,42 +227,27 @@ local function filter (p, i, ...) end end return t -end +end) --- Fold a binary function into an iterator. --- @param f function +-- @function fold +-- @func f function -- @param d initial first argument --- @param i iterator +-- @func i iterator -- @param ... iterator arguments -- @return result -- @see std.list.foldl -- @see std.list.foldr -- @usage fold (math.pow, 1, std.list.elems, List {2, 3, 4}) -local function fold (f, d, i, ...) - argscheck ("std.functional.fold", {"function", "any", "function"}, {f, d, i}) - +export (M, "fold", {"func", "any", "func", "any*"}, function (f, d, i, ...) local r = d for e in i (...) do r = f (r, e) end return r -end +end) ---- @export -functional = { - bind = bind, - case = case, - collect = collect, - compose = compose, - curry = curry, - eval = eval, - filter = filter, - fold = fold, - id = id, - map = map, - memoize = memoize, -} --- Functional forms of infix operators. -- Defined here so that other modules can write to it. @@ -293,7 +262,7 @@ functional = { -- @field not logical not -- @field == equality -- @field ~= inequality -functional.op = { +M.op = { ["[]"] = function (t, s) return t and t[s] or nil end, ["+"] = function (a, b) return a + b end, ["-"] = function (a, b) return a - b end, @@ -306,4 +275,4 @@ functional.op = { ["~="] = function (a, b) return a ~= b end, } -return functional +return M diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 7aabdcc..8373c88 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -53,6 +53,9 @@ specify std.functional: - it diagnoses wrong argument types: | expect (M.case ("no", false)). to_error "bad argument #2 to 'std.functional.case' (non-empty table expected, got boolean)" + - it diagnoses too many arguments: + expect (M.case (1, {2}, false)). + to_error "too many arguments to 'std.functional.case' (no more than 2 expected, got 3)" - it matches against branch keys: expect (M.case ("yes", branches)).to_be (true) expect (M.case ("no", branches)).to_be (false) @@ -109,6 +112,9 @@ specify std.functional: to_error "bad argument #1 to 'std.functional.curry' (function expected, got boolean)" expect (M.curry (M.id, 1.234)). to_error "bad argument #2 to 'std.functional.curry' (int expected, got number)" + - it diagnoses too many arguments: + expect (M.curry (M.id, 2, false)). + to_error "too many arguments to 'std.functional.curry' (no more than 2 expected, got 3)" - it returns a zero argument function uncurried: expect (M.curry (M.id, 0)).to_be (M.id) - it returns a one argument function uncurried: @@ -130,6 +136,9 @@ specify std.functional: - it diagnoses wrong argument types: | expect (M.eval (false)). to_error "bad argument #1 to 'std.functional.eval' (string expected, got boolean)" + - it diagnoses too many arguments: + expect (M.eval ("1", false)). + to_error "too many arguments to 'std.functional.eval' (no more than 1 expected, got 2)" - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable "=" code. expect (M.eval "=").to_error () @@ -242,6 +251,9 @@ specify std.functional: to_error "bad argument #1 to 'std.functional.memoize' (function expected, got boolean)" expect (M.memoize (M.id, false)). to_error "bad argument #2 to 'std.functional.memoize' (function or nil expected, got boolean)" + - it diagnoses too many arguments: + expect (M.memoize (M.id, M.id, false)). + to_error "too many arguments to 'std.functional.memoize' (no more than 2 expected, got 3)" - it returns the same object for the same arguments: t = memfn (1) expect (memfn (1)).to_be (t) diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 475d130..2c6cd01 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -40,6 +40,9 @@ specify std.math: to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got boolean)" expect (f (1.2, 3.4)). to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got number)" + - it diagnoses too many arguments: + expect (f (1, 2, 3)). + to_error "too many arguments to 'std.math.floor' (no more than 2 expected, got 3)" - it rounds to the nearest smaller integer: expect (f (1.2)).to_be (1) expect (f (1.9)).to_be (1) @@ -65,6 +68,9 @@ specify std.math: - it diagnoses wrong argument types: | expect (f (false)). to_error "bad argument #1 to 'std.math.monkey_patch' (table or nil expected, got boolean)" + - it diagnoses too many arguments: + expect (f (t, false)). + to_error "too many arguments to 'std.math.monkey_patch' (no more than 1 expected, got 2)" - it installs math.floor function: expect (t.math.floor).to_be (M.floor) @@ -80,6 +86,9 @@ specify std.math: to_error "bad argument #2 to 'std.math.round' (int or nil expected, got boolean)" expect (f (1.2, 3.4)). to_error "bad argument #2 to 'std.math.round' (int or nil expected, got number)" + - it diagnoses too many arguments: + expect (f (1, 2, 3)). + to_error "too many arguments to 'std.math.round' (no more than 2 expected, got 3)" - it rounds to the nearest integer: expect (f (1.2)).to_be (1) expect (f (1.9)).to_be (2) From e9434e55389a9f476e9f0c4760243001214d760f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Jun 2014 13:01:53 +0700 Subject: [PATCH 237/703] refactor: use a function to simplify bad argument specs. * specs/spec_helper.lua.in (badarg, toomanyarg): Assemble a suitable error string from arguments. * specs/function_spec.yaml, specs/math_spec.yaml: Simplify accordingly. Signed-off-by: Gary V. Vaughan --- specs/functional_spec.yaml | 290 ++++++++++++++++++++----------------- specs/math_spec.yaml | 48 +++--- specs/spec_helper.lua.in | 30 ++++ 3 files changed, 212 insertions(+), 156 deletions(-) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 8373c88..621b255 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -18,27 +18,32 @@ specify std.functional: - describe bind: - - it diagnoses missing arguments: | - expect (M.bind ()). - to_error "bad argument #1 to 'std.functional.bind' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.bind (false)). - to_error "bad argument #1 to 'std.functional.bind' (function expected, got boolean)" + - before: + fname = "bind" + f = M[fname] + + # Long-hand badarg calls until we know bind meets specifications! + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (this_module, fname, 1, "function")) + - it diagnoses wrong argument types: + expect (f (false)). + to_error (badarg (this_module, fname, 1, "function", "boolean")) + - it does not affect normal operation if no arguments are bound: - expect (M.bind (math.min, {}) (2, 3, 4)).to_be (2) + expect (f (math.min, {}) (2, 3, 4)).to_be (2) - it takes the extra arguments into account: - expect (M.bind (math.min, {1, 0}) (2, 3, 4)).to_be (0) + expect (f (math.min, {1, 0}) (2, 3, 4)).to_be (0) - it appends final call arguments: - expect (M.bind (math.max, {2, 3}) (4, 5, 1)).to_be (5) + expect (f (math.max, {2, 3}) (4, 5, 1)).to_be (5) - it does not require all arguments in final call: div = function (a, b) return a / b end - expect (M.bind (div, {100}) (25)).to_be (4) + expect (f (div, {100}) (25)).to_be (4) - it supports out of order extra arguments: - expect (M.bind (math.pow, {[2] = 3}) (2)).to_be (8) + expect (f (math.pow, {[2] = 3}) (2)).to_be (8) - it supports the legacy api: - expect (M.bind (math.min) (2, 3, 4)).to_be (2) - expect (M.bind (math.min, 1, 0) (2, 3, 4)).to_be (0) - expect (M.bind (math.pow, nil, 3) (2)).to_be (8) + expect (f (math.min) (2, 3, 4)).to_be (2) + expect (f (math.min, 1, 0) (2, 3, 4)).to_be (0) + expect (f (math.pow, nil, 3) (2)).to_be (8) - describe case: @@ -47,26 +52,30 @@ specify std.functional: no = function () return false end default = function (s) return s end branches = { yes = yes, no = no, default } - - it diagnoses missing arguments: | - expect (M.case (nil)). - to_error "bad argument #2 to 'std.functional.case' (non-empty table expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.case ("no", false)). - to_error "bad argument #2 to 'std.functional.case' (non-empty table expected, got boolean)" + fname = "case" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (2, "non-empty table")) + - it diagnoses wrong argument types: + expect (f ("no", false)). + to_error (msg (2, "non-empty table", "boolean")) - it diagnoses too many arguments: - expect (M.case (1, {2}, false)). - to_error "too many arguments to 'std.functional.case' (no more than 2 expected, got 3)" + expect (f (1, {2}, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - it matches against branch keys: - expect (M.case ("yes", branches)).to_be (true) - expect (M.case ("no", branches)).to_be (false) + expect (f ("yes", branches)).to_be (true) + expect (f ("no", branches)).to_be (false) - it has a default for unmatched keys: - expect (M.case ("none", branches)).to_be "none" + expect (f ("none", branches)).to_be "none" - it returns nil for unmatched keys with no default: - expect (M.case ("none", { yes = yes, no = no })).to_be (nil) + expect (f ("none", { yes = yes, no = no })).to_be (nil) - it evaluates `with` exactly once: s = "prince" function acc () s = s .. "s"; return s end - expect (M.case (acc (), { + expect (f (acc (), { prince = function () return "one" end, princes = function () return "many" end, princess = function () return "one" end, @@ -75,104 +84,115 @@ specify std.functional: - describe collect: - - it diagnoses missing arguments: | - expect (M.collect ()). - to_error "bad argument #1 to 'std.functional.collect' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.collect (false)). - to_error "bad argument #1 to 'std.functional.collect' (function expected, got boolean)" + - before: + fname = "collect" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + - it collects iterator results: - expect (M.collect (ipairs, {"a", "b", "c"})).to_equal {1, 2, 3} + expect (f (ipairs, {"a", "b", "c"})).to_equal {1, 2, 3} - describe compose: - - it diagnoses missing arguments: | - expect (M.compose ()). - to_error "bad argument #1 to 'std.functional.compose' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.compose (false)). - to_error "bad argument #1 to 'std.functional.compose' (function expected, got boolean)" - expect (M.compose (M.id, false)). - to_error "bad argument #2 to 'std.functional.compose' (function expected, got boolean)" + - before: + fname = "compose" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, false)).to_error (msg (2, "function", "boolean")) + - it composes a single function correctly: - expect (M.compose (M.id) (1)).to_be (1) + expect (f (M.id) (1)).to_be (1) - it composes functions in the correct order: - expect (M.compose (math.sin, math.cos) (1)). + expect (f (math.sin, math.cos) (1)). to_be (math.cos (math.sin (1))) - describe curry: - - it diagnoses missing arguments: | - expect (M.curry ()). - to_error "bad argument #1 to 'std.functional.curry' (function expected, got no value)" - expect (M.curry (M.id)). - to_error "bad argument #2 to 'std.functional.curry' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.curry (false)). - to_error "bad argument #1 to 'std.functional.curry' (function expected, got boolean)" - expect (M.curry (M.id, 1.234)). - to_error "bad argument #2 to 'std.functional.curry' (int expected, got number)" + - before: + fname = "curry" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (f)).to_error (msg (2, "int")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, 1.234)).to_error (msg (2, "int", "number")) - it diagnoses too many arguments: - expect (M.curry (M.id, 2, false)). - to_error "too many arguments to 'std.functional.curry' (no more than 2 expected, got 3)" + expect (f (f, 2, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - it returns a zero argument function uncurried: - expect (M.curry (M.id, 0)).to_be (M.id) + expect (f (f, 0)).to_be (f) - it returns a one argument function uncurried: - expect (M.curry (M.id, 1)).to_be (M.id) + expect (f (f, 1)).to_be (f) - it curries a two argument function: - expect (M.curry (M.id, 2)).not_to_be (M.id) + expect (f (f, 2)).not_to_be (f) - it evaluates intermediate arguments one at a time: - expect (M.curry (math.min, 3) (2) (3) (4)).to_equal (2) + expect (f (math.min, 3) (2) (3) (4)).to_equal (2) - it returns a curried function that can be partially applied: - bin = M.curry (math.pow, 2) (2) + bin = f (math.pow, 2) (2) expect (bin (2)).to_be (math.pow (2, 2)) expect (bin (10)).to_be (math.pow (2, 10)) - describe eval: - - it diagnoses missing arguments: | - expect (M.eval ()). - to_error "bad argument #1 to 'std.functional.eval' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.eval (false)). - to_error "bad argument #1 to 'std.functional.eval' (string expected, got boolean)" + - before: + fname = "eval" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (M.eval ("1", false)). - to_error "too many arguments to 'std.functional.eval' (no more than 1 expected, got 2)" + expect (f ("1", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable "=" code. - expect (M.eval "=").to_error () + expect (f "=").to_error () - it evaluates a string of lua code: - expect (M.eval "math.pow (2, 10)").to_be (math.pow (2, 10)) + expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) - describe filter: - before: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) - - it diagnoses missing arguments: | - expect (M.filter ()). - to_error "bad argument #1 to 'std.functional.filter' (function expected, got no value)" - expect (M.filter (M.id)). - to_error "bad argument #2 to 'std.functional.filter' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.filter (false)). - to_error "bad argument #1 to 'std.functional.filter' (function expected, got boolean)" - expect (M.filter (M.id, false)). - to_error "bad argument #2 to 'std.functional.filter' (function expected, got boolean)" + fname = "filter" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (f)).to_error (msg (2, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, false)).to_error (msg (2, "function", "boolean")) + - it iterates through element keys: - expect (M.filter (M.id, ipairs, elements)). - to_equal {1, 2, 3, 4, 5} - expect (M.filter (M.id, pairs, inverse)). - to_contain.a_permutation_of (elements) + expect (f (M.id, ipairs, elements)).to_equal {1, 2, 3, 4, 5} + expect (f (M.id, pairs, inverse)).to_contain.a_permutation_of (elements) - it passes each iterated element to filter function: t = {} - M.filter (function (e) t[#t + 1] = e end, pairs, inverse) + f (function (e) t[#t + 1] = e end, pairs, inverse) expect (t).to_contain.a_permutation_of (elements) - it returns a table of filtered keys: - expect (M.filter (function (e) return e % 2 == 0 end, ipairs, elements)). + expect (f (function (e) return e % 2 == 0 end, ipairs, elements)). to_equal {2, 4} - expect (M.filter (function (e) return e:match "[aeiou]" end, pairs, inverse)). + expect (f (function (e) return e:match "[aeiou]" end, pairs, inverse)). to_contain.a_permutation_of {"a", "e"} @@ -180,80 +200,82 @@ specify std.functional: - before: list = require "std.list" List = list {} - - it diagnoses missing arguments: | - expect (M.fold ()). - to_error "bad argument #1 to 'std.functional.fold' (function expected, got no value)" - expect (M.fold (M.id)). - to_error "bad argument #2 to 'std.functional.fold' (any value expected, got no value)" - expect (M.fold (M.id, 1)). - to_error "bad argument #3 to 'std.functional.fold' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.fold (false)). - to_error "bad argument #1 to 'std.functional.fold' (function expected, got boolean)" - expect (M.fold (M.id, 1, false)). - to_error "bad argument #3 to 'std.functional.fold' (function expected, got boolean)" + fname = "fold" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (f)).to_error (msg (2, "any value")) + expect (f (f, 1)).to_error (msg (3, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, 1, false)).to_error (msg (3, "function", "boolean")) + - it calls a binary function over element keys: - expect (M.fold (M.op["+"], 2, list.elems, List {3})). + expect (f (M.op["+"], 2, list.elems, List {3})). to_be (2 + 3) - expect (M.fold (M.op["*"], 2, list.elems, List {3, 4})). + expect (f (M.op["*"], 2, list.elems, List {3, 4})). to_be (2 * 3 * 4) - it folds elements from left to right: - expect (M.fold (math.pow, 2, list.elems, List {3, 4})). + expect (f (math.pow, 2, list.elems, List {3, 4})). to_be (math.pow (math.pow (2, 3), 4)) - describe id: + - before: + f = M.id - it returns argument unchanged: - expect (M.id (true)).to_be (true) - expect (M.id {1, 1, 2, 3}).to_equal {1, 1, 2, 3} + expect (f (true)).to_be (true) + expect (f {1, 1, 2, 3}).to_equal {1, 1, 2, 3} - it returns multiple arguments unchanged: - expect ({M.id (1, "two", false)}).to_equal {1, "two", false} + expect ({f (1, "two", false)}).to_equal {1, "two", false} - describe map: - before: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) - - it diagnoses missing arguments: | - expect (M.map ()). - to_error "bad argument #1 to 'std.functional.map' (function expected, got no value)" - expect (M.map (M.id)). - to_error "bad argument #2 to 'std.functional.map' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.map (false)). - to_error "bad argument #1 to 'std.functional.map' (function expected, got boolean)" - expect (M.map (M.id, false)). - to_error "bad argument #2 to 'std.functional.map' (function expected, got boolean)" + fname = "map" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (f)).to_error (msg (2, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, false)).to_error (msg (2, "function", "boolean")) + - it iterates through element keys: - expect (M.map (M.id, ipairs, elements)). - to_equal {1, 2, 3, 4, 5} - expect (M.map (M.id, pairs, inverse)). - to_contain.a_permutation_of (elements) + expect (f (M.id, ipairs, elements)).to_equal {1, 2, 3, 4, 5} + expect (f (M.id, pairs, inverse)).to_contain.a_permutation_of (elements) - it passes each iterated element to map function: t = {} - M.map (function (e) t[#t + 1] = e end, pairs, inverse) + f (function (e) t[#t + 1] = e end, pairs, inverse) expect (t).to_contain.a_permutation_of (elements) - it returns a table of mapped keys: - expect (M.map (function (e) return e % 2 end, ipairs, elements)). + expect (f (function (e) return e % 2 end, ipairs, elements)). to_equal {1, 0, 1, 0, 1} - expect (M.map (function (e) return e .. "x" end, pairs, inverse)). + expect (f (function (e) return e .. "x" end, pairs, inverse)). to_contain.a_permutation_of {"ax", "bx", "cx", "dx", "ex"} - describe memoize: - before: - memfn = M.memoize (function (x) return {x} end) - - it diagnoses missing arguments: | - expect (M.memoize ()). - to_error "bad argument #1 to 'std.functional.memoize' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.memoize (false)). - to_error "bad argument #1 to 'std.functional.memoize' (function expected, got boolean)" - expect (M.memoize (M.id, false)). - to_error "bad argument #2 to 'std.functional.memoize' (function or nil expected, got boolean)" + fname = "memoize" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + memfn = f (function (x) return {x} end) + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, false)).to_error (msg (2, "function or nil", "boolean")) - it diagnoses too many arguments: - expect (M.memoize (M.id, M.id, false)). - to_error "too many arguments to 'std.functional.memoize' (no more than 2 expected, got 3)" + expect (f (f, f, false)).to_error (toomanyarg (this_module, fname, 2, 3)) + - it returns the same object for the same arguments: t = memfn (1) expect (memfn (1)).to_be (t) diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 2c6cd01..8e6bd61 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -31,18 +31,18 @@ specify std.math: - describe floor: - before: - f = M.floor + fname = "floor" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.math.floor' (number expected, got no value)" + expect (f ()).to_error (msg (1, "number")) - it diagnoses wrong argument types: | - expect (f (1.2, false)). - to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got boolean)" - expect (f (1.2, 3.4)). - to_error "bad argument #2 to 'std.math.floor' (int or nil expected, got number)" + expect (f (1.2, false)).to_error (msg (2, "int or nil", "boolean")) + expect (f (1.2, 3.4)).to_error (msg (2, "int or nil", "number")) - it diagnoses too many arguments: - expect (f (1, 2, 3)). - to_error "too many arguments to 'std.math.floor' (no more than 2 expected, got 3)" + expect (f (1, 2, 3)).to_error (toomanyarg (this_module, fname, 2, 3)) + - it rounds to the nearest smaller integer: expect (f (1.2)).to_be (1) expect (f (1.9)).to_be (1) @@ -60,35 +60,39 @@ specify std.math: - describe monkey_patch: - before: - f = M.monkey_patch t = { math = {}, } - f (t) + fname = "monkey_patch" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.math.monkey_patch' (table or nil expected, got boolean)" + expect (f (false)).to_error (msg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)). - to_error "too many arguments to 'std.math.monkey_patch' (no more than 1 expected, got 2)" + expect (f (t, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it installs math.floor function: + f (t) expect (t.math.floor).to_be (M.floor) - describe round: - before: - f = M.round + fname = "round" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.math.round' (number expected, got no value)" + expect (f ()).to_error (msg (1, "number")) - it diagnoses wrong argument types: | expect (f (1.2, false)). - to_error "bad argument #2 to 'std.math.round' (int or nil expected, got boolean)" + to_error (msg (2, "int or nil", "boolean")) expect (f (1.2, 3.4)). - to_error "bad argument #2 to 'std.math.round' (int or nil expected, got number)" + to_error (msg (2, "int or nil", "number")) - it diagnoses too many arguments: - expect (f (1, 2, 3)). - to_error "too many arguments to 'std.math.round' (no more than 2 expected, got 3)" + expect (f (1, 2, 3)).to_error (toomanyarg (this_module, fname, 2, 3)) + - it rounds to the nearest integer: expect (f (1.2)).to_be (1) expect (f (1.9)).to_be (2) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 90a088a..a146473 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -4,6 +4,11 @@ local std = require "specl.std" package.path = std.package.normalize ("lib/?.lua", package.path) + +-- Error message specifications use this to shorten argument lists. +bind = require "std.functional".bind + + -- Substitute configured LUA so that hell.spawn doesn't pick up -- a different Lua binary to the one used by Specl itself. If -- we could rely on luaposix availability `posix.getenv` would @@ -65,6 +70,31 @@ local function tabulate_output (code) end +--- Return a formatted bad argument string. +-- @string fname base-name of the erroring function +-- @int i argument number +-- @string want expected argument type +-- @string[opt="no value"] got actual argument type +-- @usage +-- expect (f ()).to_error (badarg (module, name, 1, "function")) +function badarg (mname, fname, i, want, got) + return string.format ("bad argument #%d to '%s.%s' (%s expected, got %s)", + i, mname, fname, want, got or "no value") +end + + +--- Return a formatted too many arguments string. +-- @string fname base-name of the erroring function +-- @int want maximum expected number of arguments +-- @int got actual number of arguments +-- @usage +-- expect (f (1, 2)).to_error (toomanyarg (module, name, 1, 2)) +function toomanyarg (mname, fname, want, got) + return string.format ("too many arguments to '%s.%s' (no more than %d expected, got %d)", + mname, fname, want, got) +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"`: From 3ee9bb7475311e024bea8e1c0dee26cc595d7260 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Jun 2014 13:44:46 +0700 Subject: [PATCH 238/703] refactor: use pipe-delimited strings for argcheck type lists. * specs/debug_spec.yaml (argcheck, argscheck): Adjust for pipe-delimited string instead of table of strings. * lib/std/base (argcheck): Use base.split to make a table from expected argument. * lib/std/array.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/io.lua, lib/std/list.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua: Adjust argcheck and argscheck types argument accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/array.lua | 8 +++---- lib/std/base.lua | 42 ++++++++++++++++---------------- lib/std/container.lua | 2 +- lib/std/debug.lua | 2 +- lib/std/io.lua | 6 ++--- lib/std/list.lua | 14 +++++------ lib/std/package.lua | 4 ++-- lib/std/string.lua | 10 ++++---- lib/std/table.lua | 20 ++++++++-------- specs/debug_spec.yaml | 56 +++++++++++++++++++++---------------------- 10 files changed, 82 insertions(+), 82 deletions(-) diff --git a/lib/std/array.lua b/lib/std/array.lua index 62b805a..5e50d5f 100644 --- a/lib/std/array.lua +++ b/lib/std/array.lua @@ -209,10 +209,10 @@ core_metatable = { if init ~= nil then -- When called with 2 arguments: argcheck ("Array", 1, "string", type) - argcheck ("Array", 2, {"int", "table"}, init) + argcheck ("Array", 2, "int|table", init) elseif type ~= nil then -- When called with 1 argument: - argcheck ("Array", 1, {"int", "string", "table"}, type) + argcheck ("Array", 1, "int|string|table", type) end end @@ -316,7 +316,7 @@ core_metatable = { -- @treturn string the element at index `n` -- @usage rightmost = anarray[anarray.length] __index = function (self, n) - argcheck ("__index", 2, {"int", "string"}, n) + argcheck ("__index", 2, "int|string", n) if typeof (n) == "number" then if n < 0 then n = n + self.length + 1 end @@ -492,7 +492,7 @@ alien_metatable = { end, __index = function (self, n) - argcheck ("__index", 2, {"int", "string"}, n) + argcheck ("__index", 2, "int|string", n) if typeof (n) == "number" then if n < 0 then n = n + self.length + 1 end diff --git a/lib/std/base.lua b/lib/std/base.lua index da66e4a..1e68989 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -33,6 +33,26 @@ local function prototype (o) 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 +local function split (s, sep) + sep = sep or "%s+" + local b, len, t, patt = 0, #s, {}, "(.-)" .. sep + if sep == "" then patt = "(.)"; t[#t + 1] = "" end + while b <= len do + local e, n, m = string.find (s, patt, b + 1) + t[#t + 1] = m or s:sub (b + 1, len) + b = n or len + 1 + end + return t +end + + local argcheck, argerror, argscheck if _ARGCHECK then @@ -59,7 +79,7 @@ if _ARGCHECK then -- Doc-commented in debug.lua function argcheck (name, i, expected, actual, level) level = level or 2 - if prototype (expected) ~= "table" then expected = {expected} end + expected = split (expected, "|") -- Strip trailing "?" but add "nil" to expected when a "?" is found. local add_nil = nil @@ -317,26 +337,6 @@ local function getmetamethod (x, n) 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 -local function split (s, sep) - sep = sep or "%s+" - local b, len, t, patt = 0, #s, {}, "(.-)" .. sep - if sep == "" then patt = "(.)"; t[#t + 1] = "" end - while b <= len do - local e, n, m = string.find (s, patt, b + 1) - t[#t + 1] = m or s:sub (b + 1, len) - b = n or len + 1 - end - return t -end - - local M = { argcheck = argcheck, argerror = argerror, diff --git a/lib/std/container.lua b/lib/std/container.lua index 6d1fac5..ba77c6e 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -135,7 +135,7 @@ end -- renamed according to `map` -- @see std.object.mapfields local function mapfields (obj, src, map) - argscheck ("std.container.mapfields", {"table", {"table", "object"}, "table?"}, + argscheck ("std.container.mapfields", {"table", "table|object", "table?"}, {obj, src, map}) local mt = getmetatable (obj) or {} diff --git a/lib/std/debug.lua b/lib/std/debug.lua index b8f860c..fd87eac 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -165,7 +165,7 @@ local argerror = base.argerror -- `:option` instead of `false` versus `true`. Or you could support -- both: -- --- argcheck ("table.copy", 2, {"boolean", ":nometa", "nil"}, nometa) +-- argcheck ("table.copy", 2, "boolean|:nometa|nil"}, nometa) -- -- A very common pattern is to have a list of possible types including -- "nil" when the argument is optional. Rather than writing long-hand diff --git a/lib/std/io.lua b/lib/std/io.lua index fd5fcdc..432d8eb 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -58,7 +58,7 @@ end -- @see std.io.process_files -- @usage contents = slurp (filename) local function slurp (file) - argcheck ("std.io.slurp", 1, {"file", "string", "nil"}, file) + argcheck ("std.io.slurp", 1, "file|string|nil", file) local h, err = input_handle (file) if h == nil then argerror ("std.io.slurp", 1, err, 2) end @@ -77,7 +77,7 @@ end -- @return list of lines -- @usage list = readlines "/etc/passwd" local function readlines (file) - argcheck ("std.io.readlines", 1, {"file", "string", "nil"}, file) + argcheck ("std.io.readlines", 1, "file|string|nil", file) local h, err = input_handle (file) if h == nil then argerror ("std.io.readlines", 1, err, 2) end @@ -97,7 +97,7 @@ end -- @param ... values to write (as for write) -- @usage writelines (io.stdout, "first line", "next line") local function writelines (h, ...) - argcheck ("std.io.writelines", 1, {"file", "string", "nil"}, h) + argcheck ("std.io.writelines", 1, "file|string|nil", h) if io.type (h) ~= "file" then io.write (h, "\n") diff --git a/lib/std/list.lua b/lib/std/list.lua index c23d24e..5d8bc6a 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -70,7 +70,7 @@ end -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 -- if `l` is greater than `m` local function compare (l, m) - argscheck ("std.list.compare", {"List", {"List", "table"}}, {l, m}) + argscheck ("std.list.compare", {"List", "List|table"}, {l, m}) for i = 1, math.min (#l, #m) do local li, mi = tonumber (l[i]), tonumber (m[i]) @@ -125,9 +125,9 @@ end local function concat (l, ...) if _ARGCHECK then argcheck ("std.list.concat", 1, "List", l) - argcheck ("std.list.concat", 2, {"List", "table"}, select (1, ...)) + argcheck ("std.list.concat", 2, "List|table", select (1, ...)) for i, v in ipairs {...} do - argcheck ("std.list.concat", i + 1, {"List", "table"}, v) + argcheck ("std.list.concat", i + 1, "List|table", v) end end @@ -160,7 +160,7 @@ end local function depair (ls) if _ARGCHECK then local fname = "std.list.depair" - argcheck (fname, 1, {"List", "table"}, ls) + argcheck (fname, 1, "List|table", ls) for i, v in ipairs (ls) do local actual = prototype (v) @@ -245,7 +245,7 @@ end -- @treturn List `l` -- @return `true` local function relems (l) - argcheck ("std.list.relems", 1, {"List", "table"}, l) + argcheck ("std.list.relems", 1, "List|table", l) local n = #l + 1 return function (l) @@ -318,7 +318,7 @@ end, nil, -- @treturn List new list containing `{fn (l[1]), ..., fn (l[#l])}` -- @see std.list:map local function map (fn, l) - argscheck ("std.list.map", {"function", {"List", "table"}}, {fn, l}) + argscheck ("std.list.map", {"function", "List|table"}, {fn, l}) return List (func.map (fn, ielems, l)) end @@ -504,7 +504,7 @@ end local function transpose (ls) if _ARGCHECK then local fname = "std.list.transpose" - argcheck (fname, 1, {"table", "List"}, ls) + argcheck (fname, 1, "table|List", ls) for i, v in ipairs (ls) do local actual = prototype (v) diff --git a/lib/std/package.lua b/lib/std/package.lua index 0589591..4f2fe94 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -67,7 +67,7 @@ end -- @usage i, s = find (package.path, "^[^" .. package.dirsep .. "/]") local function find (pathstrings, patt, init, plain) argscheck ("std.package.find", - {"string", "string", "int?", {"boolean?", ":plain"}}, + {"string", "string", "int?", "boolean|:plain?"}, {pathstrings, patt, init, plain}) local paths = split (pathstrings, M.pathsep) @@ -136,7 +136,7 @@ local function insert (pathstrings, ...) local args, types = {pathstrings, ...} if _ARGCHECK then if #args == 1 then - types = {"string", {"int", "string"}} + types = {"string", "int|string"} elseif #args == 2 then types = {"string", "string"} else diff --git a/lib/std/string.lua b/lib/std/string.lua index c324e46..bcbb847 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -120,7 +120,7 @@ end -- @usage b, e, captures = tfind ("the target string", "%s", 10) local function tfind (s, pattern, init, plain) argscheck ("std.string.tfind", - {"string", "string", "int?", {"boolean?", ":plain"}}, + {"string", "string", "int?", "boolean|:plain?"}, {s, pattern, init, plain}) local function pack (from, to, ...) @@ -143,7 +143,7 @@ end -- end local function finds (s, pattern, init, plain) argscheck ("std.string.finds", - {"string", "string", "int?", {"boolean?", ":plain"}}, + {"string", "string", "int?", "boolean|:plain?"}, {s, pattern, init, plain}) init = init or 1 @@ -295,7 +295,7 @@ end -- local now = os.date "*t" -- print ("%d%s day of the week", now.day, ordinal_suffix (now.day)) local function ordinal_suffix (n) - argcheck ("std.string.ordinal_suffix", 1, {"int", "string"}, n) + argcheck ("std.string.ordinal_suffix", 1, "int|string", n) n = math.abs (n) % 100 local d = n % 10 @@ -376,7 +376,7 @@ end -- @treturn string *n* simplifed using largest available SI suffix. -- @usage print (numbertosi (bitspersecond) .. "bps") local function numbertosi (n) - argcheck ("std.string.numbertosi", 1, {"number", "string"}, n) + argcheck ("std.string.numbertosi", 1, "number|string", n) local SIprefix = { [-8] = "y", [-7] = "z", [-6] = "a", [-5] = "f", @@ -482,7 +482,7 @@ end -- end local function render (x, open, close, elem, pair, sep, roots) argscheck ("std.string.render", - {"any?", "function", "function", "function", "function", "function", "table?"}, + {"any?", "func", "func", "func", "func", "func", "table?"}, {x, open, close, elem, pair, sep, roots}) local function stop_roots (x) diff --git a/lib/std/table.lua b/lib/std/table.lua index 22b6301..3f53de6 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -93,9 +93,9 @@ end -- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") local function clone (t, map, nometa) if _ARGCHECK then - local types = {"table", "table", {"boolean?", ":nometa"}} + local types = {"table", "table", "boolean|:nometa?"} if type (map) ~= "table" then - types = {"table", {"table?", "boolean?", ":nometa"}} + types = {"table", "table|boolean|:nometa?"} end argscheck ("std.table.clone", types, {t, map, nometa}) end @@ -136,9 +136,9 @@ local clone_rename = base.deprecate (function (map, t) -- partialcopy = clone_select (original, {"this", "and_this"}, true) local function clone_select (t, keys, nometa) if _ARGCHECK then - local types = {"table", "table", {"boolean?", ":nometa"}} + local types = {"table", "table", "boolean|:nometa?"} if type (keys) ~= "table" then - types = {"table", {"table?", "boolean?", ":nometa"}} + types = {"table", "table|boolean|:nometa?"} end argscheck ("std.table.clone_select", types, {t, keys, nometa}) end @@ -233,9 +233,9 @@ end -- @usage merge (_G, require "std.debug", {say = "log"}, ":nometa") local function merge (t, u, map, nometa) if _ARGCHECK then - local types = {"table", "table", "table", {"boolean?", ":nometa"}} + local types = {"table", "table", "table", "boolean|:nometa?"} if type (map) ~= "table" then - types = {"table", "table", {"table?", "boolean?", ":nometa"}} + types = {"table", "table", "table|boolean|:nometa?"} end argscheck ("std.table.merge", types, {t, u, map, nometa}) end @@ -258,9 +258,9 @@ end -- @usage merge_select (_G, require "std.debug", {"say"}, false) local function merge_select (t, u, keys, nometa) if _ARGCHECK then - local types = {"table", "table", "table", {"boolean?", ":nometa"}} + local types = {"table", "table", "table", "boolean|:nometa?"} if type (keys) ~= "table" then - types = {"table", "table", {"table?", "boolean?", ":nometa"}} + types = {"table", "table", "table|boolean|:nometa?"} end argscheck ("std.table.merge_select", types, {t, u, keys, nometa}) end @@ -281,7 +281,7 @@ local metamethod if _ARGCHECK then metamethod = function (x, n) - argscheck ("std.table.metamethod", {{"object", "table"}, "string"}, {x, n}) + argscheck ("std.table.metamethod", {"object|table", "string"}, {x, n}) return getmetamethod (x, n) end @@ -389,7 +389,7 @@ end -- @treturn table resulting table or `nil` -- @usage print (table.concat (totable (object))) local function totable (x) - argcheck ("std.table.totable", 1, {"object", "table", "string"}, x) + argcheck ("std.table.totable", 1, "object|table|string", x) local m = getmetamethod (x, "__totable") if m then diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index ebd1ea2..5b583cc 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -201,59 +201,59 @@ specify std.debug: expect (fn ("any", Foo {})).not_to_error () - context with a list of valid types: - it diagnoses missing elements: - expect (fn ({"string", "table"}, nil)). + expect (fn ("string|table", nil)). to_error "string or table expected, got no value" - expect (fn ({"string", "list", "#table"}, nil)). + expect (fn ("string|list|#table", nil)). to_error "string, list or non-empty table expected, got no value" - expect (fn ({"string", "number", "list", "object"}, nil)). + expect (fn ("string|number|list|object", nil)). to_error "string, number, list or object expected, got no value" - it diagnoses mismatched elements: - expect (fn ({"string", "table"}, false)). + expect (fn ("string|table", false)). to_error "string or table expected, got boolean" - expect (fn ({"string", "list", "#table"}, {})). + expect (fn ("string|list|#table", {})). to_error "string, list or non-empty table expected, got empty table" - expect (fn ({"string", "number", "list", "object"}, {})). + expect (fn ("string|number|list|object", {})). to_error "string, number, list or object expected, got empty table" - it matches any type from a list: - expect (fn ({"string", "table"}, "foo")).not_to_error () - expect (fn ({"string", "table"}, {})).not_to_error () - expect (fn ({"string", "table"}, {0})).not_to_error () - expect (fn ({"table", "#table"}, {})).not_to_error () - expect (fn ({"#table", "table"}, {})).not_to_error () + expect (fn ("string|table", "foo")).not_to_error () + expect (fn ("string|table", {})).not_to_error () + expect (fn ("string|table", {0})).not_to_error () + expect (fn ("table|table", {})).not_to_error () + expect (fn ("#table|table", {})).not_to_error () - context with an optional type element: - it diagnoses mismatched elements: expect (fn ("boolean?", "string")). to_error "boolean or nil expected, got string" - expect (fn ({"boolean?", ":symbol"}, {})). + expect (fn ("boolean?|:symbol", {})). to_error "boolean, :symbol or nil expected, got empty table" - it matches nil against a single type: expect (fn ("any?", nil)).not_to_error () expect (fn ("boolean?", nil)).not_to_error () expect (fn ("string?", nil)).not_to_error () - it matches nil against a list of types: - expect (fn ({"boolean?", "table"}, nil)).not_to_error () - expect (fn ({"string?", "table"}, nil)).not_to_error () - expect (fn ({"table?", "#table"}, nil)).not_to_error () - expect (fn ({"#table?", "table"}, nil)).not_to_error () + expect (fn ("boolean?|table", nil)).not_to_error () + expect (fn ("string?|table", nil)).not_to_error () + expect (fn ("table?|#table", nil)).not_to_error () + expect (fn ("#table?|table", nil)).not_to_error () - it matches nil against a list of optional types: - expect (fn ({"boolean?", "table?"}, nil)).not_to_error () - expect (fn ({"string?", "table?"}, nil)).not_to_error () - expect (fn ({"table?", "#table?"}, nil)).not_to_error () - expect (fn ({"#table?", "table?"}, nil)).not_to_error () + expect (fn ("boolean?|table?", nil)).not_to_error () + expect (fn ("string?|table?", nil)).not_to_error () + expect (fn ("table?|#table?", nil)).not_to_error () + expect (fn ("#table?|table?", nil)).not_to_error () - it matches any named type: expect (fn ("any?", false)).not_to_error () expect (fn ("boolean?", false)).not_to_error () expect (fn ("string?", "string")).not_to_error () - it matches any type from a list: - expect (fn ({"boolean?", "table"}, {})).not_to_error () - expect (fn ({"string?", "table"}, {0})).not_to_error () - expect (fn ({"table?", "#table"}, {})).not_to_error () - expect (fn ({"#table?", "table"}, {})).not_to_error () + expect (fn ("boolean?|table", {})).not_to_error () + expect (fn ("string?|table", {0})).not_to_error () + expect (fn ("table?|#table", {})).not_to_error () + expect (fn ("#table?|table", {})).not_to_error () - it matches any type from a list with several optional specifiers: - expect (fn ({"boolean?", "table?"}, {})).not_to_error () - expect (fn ({"string?", "table?"}, {0})).not_to_error () - expect (fn ({"table?", "#table?"}, {})).not_to_error () - expect (fn ({"#table?", "table?"}, {})).not_to_error () + expect (fn ("boolean?|table?", {})).not_to_error () + expect (fn ("string?|table?", {0})).not_to_error () + expect (fn ("table?|table?", {})).not_to_error () + expect (fn ("#table?|table?", {})).not_to_error () - describe argscheck: From 4e67d247d18f9900e2ac6e4507c9ab50998e40a6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 11 Jun 2014 15:32:29 +0700 Subject: [PATCH 239/703] refactor: use a string specifier instead of a table for export. * specs/base_spec.yaml (export): Specify behaviours of export function, particularly with argument errors. * lib/std/base.lua (export): In place of an export name and a table of expected argument types, parse a single `decl` argument into an export name and table of argument types. Support specified argument error behaviours. * lib/std/math.lua (floor, monkey_patch, round): Simplify accordingly. * lib/std/functional.lua (bind, case, collect, compose, curry) (eval, filter, fold, map, memoize): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 45 ++++++++++++++++++---- lib/std/functional.lua | 20 +++++----- lib/std/math.lua | 6 +-- specs/base_spec.yaml | 86 ++++++++++++++++++++++++++++++++++++++---- 4 files changed, 129 insertions(+), 28 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 1e68989..5a207d8 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -27,6 +27,8 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local typeof = type +local toomanyarg_fmt = "too many arguments to '%s' (no more than %d expected, got %d)" + -- Doc-commented in object.lua local function prototype (o) return (getmetatable (o) or {})._type or io.type (o) or type (o) @@ -235,17 +237,46 @@ end -- element in the *types* table with `argcheck`, if the final element of -- *types* ends with an asterisk, remaining unchecked arguments are checked -- against that type. +-- @function export -- @tparam table M module table --- @string name key in *M* for *fn* --- @tparam table types *fn* argument type constraints +-- @string decl function type declaration string -- @func fn value to store at *name* in *M* -local function export (M, name, types, fn) +-- @usage +-- export (M, "round (number, int?)", std.math.round) +local function export (M, decl, fn, ...) local inner = fn + -- Parse "fname (argtype, argtype, argtype...)". + local name, types + if decl then + name, types = decl:match "([%w_][%d%w_]*)%s+%((.*)%)" + end + -- When argument checking is enabled, wrap in type checking function. if _ARGCHECK then - argscheck ("std.base.export", {"table", "string", "#table", "function"}, - {M, name, types, inner}) + local fname = "std.base.export" + local args = {M, decl, fn, ...} + argscheck (fname, {"table", "string", "function"}, args) + + -- Check for other argument errors. + if types == "" then + types = {} + elseif types then + types = split (types, ",%s+") + else + name = decl:match "([%w_][%d%w_]*)" + end + if #args > 3 then + error (string.format (toomanyarg_fmt, fname, 3, #args), 2) + elseif type (M[1]) ~= "string" then + argerror (fname, 1, "module name at index 1 expected, got no value") + elseif name == nil then + argerror (fname, 2, "function name expected") + elseif types == nil then + argerror (fname, 2, "argument type specifications expected") + elseif #types < 1 then + argerror (fname, 2, "at least 1 argument type expected, got 0") + end local name = M[1] .. "." .. name @@ -268,9 +299,7 @@ local function export (M, name, types, fn) end if argc > max then - local fmt - fmt = "too many arguments to '%s' (no more than %d expected, got %d)" - error (string.format (fmt, name, max, argc), 2) + error (string.format (toomanyarg_fmt, name, max, argc), 2) end return inner (...) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 6c0c88f..a1a7b18 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -27,7 +27,7 @@ end -- > cube = bind (math.pow, {[2] = 3}) -- > =cube (2) -- 8 -local bind = export (M, "bind", {"func", "any?*"}, function (f, ...) +local bind = export (M, "bind (func, any?*)", function (f, ...) local fix = {...} -- backwards compatibility with old API; DEPRECATED: remove in first release after 2015-04-21 if type (fix[1]) == "table" and fix[2] == nil then fix = fix[1] @@ -61,7 +61,7 @@ end) -- string = function () return something else end, -- function (s) error ("unhandled type: "..s) end, -- }) -export (M, "case", {"any?", "#table"}, function (with, branches) +export (M, "case (any?, #table)", function (with, branches) local fn = branches[with] or branches[1] if fn then return fn (with) end end) @@ -78,7 +78,7 @@ end) -- > =incr (99), decr (99) -- 100 98 local curry -curry = export (M, "curry", {"func", "int"}, function (f, n) +curry = export (M, "curry (func, int)", function (f, n) if n <= 1 then return f else @@ -105,7 +105,7 @@ end) -- b -- c -- a -export (M, "compose", {"func*"}, function (...) +export (M, "compose (func*)", function (...) local arg = {...} local fns, n = arg, #arg return function (...) @@ -136,7 +136,7 @@ end) -- @treturn functable memoized function -- @usage -- local fast = memoize (function (...) --[[ slow code ]] end) -export (M, "memoize", {"func", "func?"}, function (fn, normalize) +export (M, "memoize (func, func?)", function (fn, normalize) if normalize == nil then -- Call require here, to avoid pulling in all of 'std.string' -- even when memoize is never called. @@ -163,7 +163,7 @@ end) -- @string s string of Lua code -- @return result of evaluating `s` -- @usage eval "math.pow (2, 10)" -export (M, "eval", {"string"}, function (s) +export (M, "eval (string)", function (s) return loadstring ("return " .. s)() end) @@ -178,7 +178,7 @@ end) -- @usage -- > =collect (std.list.relems, List {"a", "b", "c"}) -- {"c", "b", "a"} -export (M, "collect", {"func", "any*"}, function (i, ...) +export (M, "collect (func, any*)", function (i, ...) local t = {} for e in i (...) do t[#t + 1] = e @@ -197,7 +197,7 @@ end) -- @usage -- > map (function (e) return e % 2 end, std.list.elems, List {1, 2, 3, 4}) -- {1, 0, 1, 0} -export (M, "map", {"func", "func", "any*"}, function (f, i, ...) +export (M, "map (func, func, any*)", function (f, i, ...) local t = {} for e in i (...) do local r = f (e) @@ -219,7 +219,7 @@ end) -- @usage -- > filter (function (e) return e % 2 == 0 end, std.list.elems, List {1, 2, 3, 4}) -- {2, 4} -export (M, "filter", {"func", "func", "any*"}, function (p, i, ...) +export (M, "filter (func, func, any*)", function (p, i, ...) local t = {} for e in i (...) do if p (e) then @@ -240,7 +240,7 @@ end) -- @see std.list.foldl -- @see std.list.foldr -- @usage fold (math.pow, 1, std.list.elems, List {2, 3, 4}) -export (M, "fold", {"func", "any", "func", "any*"}, function (f, d, i, ...) +export (M, "fold (func, any, func, any*)", function (f, d, i, ...) local r = d for e in i (...) do r = f (r, e) diff --git a/lib/std/math.lua b/lib/std/math.lua index fbb2d4f..b12b7ba 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -25,7 +25,7 @@ local M = { "std.math" } local _floor = math.floor -export (M, "floor", {"number", "int?"}, function (n, p) +export (M, "floor (number, int?)", function (n, p) if p and p ~= 0 then local e = 10 ^ p return _floor (n * e) / e @@ -42,7 +42,7 @@ end) -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage require "std.math".monkey_patch () -export (M, "monkey_patch", {"table?"}, function (namespace) +export (M, "monkey_patch (table?)", function (namespace) namespace = namespace or _G namespace.math.floor = M.floor return M @@ -55,7 +55,7 @@ end) -- @int[opt=0] p number of decimal places to round to -- @treturn number `n` rounded to `p` decimal places -- @usage roughly = round (exactly, 2) -export (M, "round", {"number", "int?"}, function (n, p) +export (M, "round (number, int?)", function (n, p) local e = 10 ^ (p or 0) return _floor (n * e + 0.5) / e end) diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 42c5f08..17e521e 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -1,11 +1,11 @@ before: - base = require "std.base" + this_module = "std.base" + + M = require (this_module) specify std.base: - describe deprecate: - before: | - deprecate = base.deprecate - function runscript (body, name, args) return luaproc ( "require 'std.base'.deprecate (function (...)" .. @@ -14,13 +14,18 @@ specify std.base: "('" .. table.concat (args or {}, "', '") .. "')" ) end + + fname = "deprecate" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + - it diagnoses missing arguments: | - expect (deprecate ()). - to_error "bad argument #1 to 'std.base.deprecate' (function expected, got no value)" - expect (deprecate (function () end)). + expect (f ()).to_error (msg (1, "function")) + expect (f (function () end)). to_error "missing argument to 'std.base.deprecate' (2 or 3 arguments expected)" + - it returns a function: - f = deprecate (function () end, "clone_rename") + f = M.deprecate (function () end, "clone_rename") expect (type (f)).to_be "function" - context with deprecated function: - it executes the deprecated function: @@ -50,3 +55,70 @@ specify std.base: ]] expect (luaproc (script)). not_to_match_error "^%S+:3:.*deprecated.*\n%S+:4:.*deprecated" + +- describe export: + - before: + function mkstack (level, types) + return string.format ([[ + _DEBUG = %s -- line 1 + local export = require "std.base".export -- line 2 + export ("ohnoes", 1, "table", t, %s) -- line 4 + end -- line 5 + function caller () -- line 6 + local r = ohnoes "not a table" -- line 7 + return "not a tail call" -- line 8 + end -- line 9 + caller () -- line 10 + ]], tostring (debugp), tostring (level)) + end + + fname = "export" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + expect (f ({})).to_error (msg (2, "string")) + expect (f ({}, "")).to_error (msg (3, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f ({}, false)).to_error (msg (2, "string", "boolean")) + expect (f ({}, "", false)).to_error (msg (3, "function", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, "", function () end, false)). + to_error (toomanyarg (this_module, fname, 3, 4)) + - it diagnoses missing module name elemest: + expect (f ({}, "", function () end)). + to_error (msg (1, "module name at index 1")) + - it diagnoses malformed declaration string: | + M = { "base_spec.yaml" } + expect (f (M, "", function () end)).to_error "function name expected" + expect (f (M, "woo", function () end)). + to_error "argument type specifications expected" + expect (f (M, "woo ()", function () end)). + to_error (msg (2, "at least 1 argument type", "0")) + + - it returns the supplied function: + M = { "base_spec.yaml" } + expect (f (M, "expect (any)", f)).to_be (f) + - it stores a function in supplied table at the specified key: + M, MAGIC = { "base_spec.yaml" }, {"unique!"} + f (M, "export (any?)", function () return MAGIC end) + expect (M.export ()).to_be (MAGIC) + - it stores the passed function when _ARGCHECK is disabled: + script = [[ + _DEBUG = false + local debug = require "std.debug_init" + local export = require "std.base".export + M = { "base_spec.yaml" } + export (M, "export (any)", export) + os.exit (M.export == export and 0 or 1) + ]] + expect (luaproc (script)).to_succeed () + - it wraps the passed function with a argscheck call: + fake_module = "base_spec.yaml" + M = { fake_module } + fname = "fake_function" + f (M, "fake_function (#table)", function () end) + msg = bind (badarg, {fake_module, fname}) + expect (M[fname] {}).to_error (msg (1, "non-empty table", "empty table")) From 809e3716898f3eaa2595755b11144d23ed08a304 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 12 Jun 2014 11:17:17 +0700 Subject: [PATCH 240/703] refactor: use export function to simplify debug argchecks. * lib/std/debug.lua (argcheck, argerror, argscheck, trace): Add argument checking. * specs/debug_spec.yaml: Adjust. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 78 ++++++++++++++++++----------------- specs/debug_spec.yaml | 94 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 116 insertions(+), 56 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index fd87eac..87738f6 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -35,16 +35,14 @@ local base = require "std.base" local functional = require "std.functional" local string = require "std.string" +local export = base.export +local M = { "std.debug" } ---- Control std.debug function behaviour. --- To activate debugging set _DEBUG either to any true value --- (equivalent to {level = 1}), or as documented below. --- @class table --- @name _DEBUG --- @field argcheck honor argcheck and argscheck calls --- @field call do call trace debugging --- @field level debugging level --- @usage _DEBUG = { argcheck = false, level = 9 } + + +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- --- Stringify a list of objects, then tabulate the resulting list of strings. @@ -58,6 +56,23 @@ local tabify = functional.compose ( functional.bind (table.concat, {[2] = "\t"})) + +--[[ ============== ]]-- +--[[ API Functions. ]]-- +--[[ ============== ]]-- + + +--- Control std.debug function behaviour. +-- To activate debugging set _DEBUG either to any true value +-- (equivalent to {level = 1}), or as documented below. +-- @class table +-- @name _DEBUG +-- @field argcheck honor argcheck and argscheck calls +-- @field call do call trace debugging +-- @field level debugging level +-- @usage _DEBUG = { argcheck = false, level = 9 } + + --- Print a debugging message to `io.stderr`. -- Display arguments passed through `std.string.tostring` and separated by tab -- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` @@ -69,7 +84,7 @@ local tabify = functional.compose ( -- local _DEBUG = require "std.debug_init"._DEBUG -- _DEBUG.level = 3 -- say (2, "_DEBUG table contents:", _DEBUG) -local function say (n, ...) +function M.say (n, ...) local level = 1 local arg = {n, ...} if type (arg[1]) == "number" then @@ -91,11 +106,12 @@ local level = 0 -- Use as debug.sethook (trace, "cr"), which is done automatically -- when `_DEBUG.call` is set. -- Based on test/trace-calls.lua from the Lua distribution. +-- @function trace -- @string event event causing the call -- @usage -- _DEBUG = { call = true } -- local debug = require "std.debug" -local function trace (event) +export (M, "trace (string)", function (event) local t = debug.getinfo (3) local s = " >>> " .. string.rep (" ", level) if t ~= nil and t.currentline >= 0 then @@ -120,11 +136,11 @@ local function trace (event) s = s .. event .. " " .. (t.name or "(C)") .. " [" .. t.what .. "]" end io.stderr:write (s .. "\n") -end +end) -- Set hooks according to _DEBUG if type (_DEBUG) == "table" and _DEBUG.call then - debug.sethook (trace, "cr") + debug.sethook (M.trace, "cr") end @@ -142,11 +158,14 @@ end -- local h, err = input_handle (file) -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end -- ... -local argerror = base.argerror +export (M, "argerror (string, int, string?, int?)", base.argerror) --- Check the type of an argument against expected types. -- Equivalent to luaL_argcheck in the Lua C API. +-- +-- Call `argerror` if there is a type mismatch. +-- -- Argument `actual` must match one of the types from in `expected`, each -- of which can be the name of a primitive Lua type, a stdlib object type, -- or one of the special options below: @@ -165,17 +184,14 @@ local argerror = base.argerror -- `:option` instead of `false` versus `true`. Or you could support -- both: -- --- argcheck ("table.copy", 2, "boolean|:nometa|nil"}, nometa) +-- argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa) -- -- A very common pattern is to have a list of possible types including -- "nil" when the argument is optional. Rather than writing long-hand -- as above, append a question mark to at least one of the list types --- and omit the explicit "nil" entry. This is particularly effective --- when there is only one acceptable type for an optional argument: --- --- argcheck ("string.assert", 1, "string?", predicate) +-- and omit the explicit "nil" entry: -- --- Call `argerror` if there is a type mismatch. +-- argcheck ("table.copy", 2, "boolean|:nometa?", predicate) -- -- Normally, you should not need to use the `level` parameter, as the -- default is to blame the caller of the function using `argcheck` in @@ -183,36 +199,26 @@ local argerror = base.argerror -- @function argcheck -- @string name function to blame in error message -- @int i argument number to blame in error message --- @tparam table|string expected a list of acceptable argument types +-- @string expected specification for acceptable argument types -- @param actual argument passed -- @int[opt=2] level call stack level to blame for the error -- @usage -- local function case (with, branches) -- argcheck ("std.functional.case", 2, "#table", branches) -- ... -local argcheck = base.argcheck +export (M, "argcheck (string, int, string, any?, int?)", base.argcheck) --- Check that all arguments match specified types. -- @function argscheck -- @string name function to blame in error message --- @tparam table|string expected a list of lists of acceptable argument types --- @tparam table|any actual argument value, or table of argument values +-- @tparam table expected a list of lists of acceptable argument types +-- @tparam table actual argument value, or table of argument values -- @usage -- local function curry (f, n) -- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) -- ... -local argscheck = base.argscheck - - ---- @export -local M = { - argcheck = argcheck, - argerror = argerror, - argscheck = argscheck, - say = say, - trace = trace, -} +export (M, "argscheck (string, #table, table)", base.argscheck) for k, v in pairs (debug) do @@ -227,7 +233,7 @@ end -- debug "oh noes!" local metatable = { __call = function (self, ...) - say (1, ...) + M.say (1, ...) end, } diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 5b583cc..1f5b255 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -31,7 +31,6 @@ specify std.debug: - describe argerror: - before: | - fn = M.argerror function mkstack (level) return string.format ([[ _DEBUG = true -- line 1 @@ -46,20 +45,36 @@ specify std.debug: caller () -- line 10 ]], tostring (level)) end - - it raises a bad argument error: | - expect (fn ('expect', 1)). - to_error "bad argument #1 to 'expect'" + + fname = "argerror" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f "foo").to_error (msg (2, "int")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("foo", false)).to_error (msg (2, "int", "boolean")) + expect (f ("foo", 1, false)). + to_error (msg (3, "string or nil", "boolean")) + expect (f ("foo", 1, "bar", false)). + to_error (msg (4, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("foo", 1, "bar", 2, false)). + to_error (toomanyarg (this_module, fname, 4, 5)) + - it blames the call site by default: | expect (luaproc (mkstack ())).to_contain_error ":4: bad argument" - it honors optional call stack level reporting: | expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" - it reports the calling function name: - expect (fn ('expect', 1)).to_error "'expect'" + expect (f ('expect', 1)).to_error "'expect'" - it reports the argument number: | - expect (fn ('expect', 12345)).to_error "#12345" + expect (f ('expect', 12345)).to_error "#12345" - it reports extra message in parentheses: - expect (fn ('expect', 1, "extramsg")).to_error " (extramsg)" + expect (f ('expect', 1, "extramsg")).to_error " (extramsg)" - describe argcheck: @@ -84,6 +99,25 @@ specify std.debug: caller () -- line 10 ]], tostring (debugp), tostring (level)) end + + fname = "argcheck" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f "foo").to_error (msg (2, "int")) + expect (f ("foo", 1)).to_error (msg (3, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("foo", false)).to_error (msg (2, "int", "boolean")) + expect (f ("foo", 1, false)).to_error (msg (3, "string", "boolean")) + expect (f ("foo", 1, "bar", 2, false)). + to_error (msg (5, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("foo", 1, "bar", 2, 3, false)). + to_error (toomanyarg (this_module, fname, 5, 6)) + - it blames the calling function by default: | expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" - it honors optional call stack level reporting: | @@ -274,6 +308,24 @@ specify std.debug: caller () -- line 10 ]], tostring (debugp)) end + + fname = "argscheck" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f "foo").to_error (msg (2, "non-empty table")) + expect (f ("foo", {"bar"})).to_error (msg (3, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("foo", false)). + to_error (msg (2, "non-empty table", "boolean")) + expect (f ("foo", {"bar"}, false)).to_error (msg (3, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ("foo", {"bar"}, {}, false)). + to_error (toomanyarg (this_module, fname, 3, 4)) + - it blames the calling function: | expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" - it can be disabled by setting _DEBUG to false: @@ -288,19 +340,6 @@ specify std.debug: - it is not disabled by leaving _DEBUG.argcheck unset: expect (luaproc (mkstack ("{}"))). to_contain_error "bad argument" - - context with single non-table specs: - - it diagnoses missing argument: - expect (fn ("boolean", nil)). - to_error "boolean expected, got no value" - - it reports the correct missing argument number: - expect (fn ("boolean", nil)).to_error "#1 " - - it diagnoses mismatched argument: - expect (fn ("boolean", "false")). - to_error "boolean expected, got string" - - it reports the correct mismatched argument number: - expect (fn ("boolean", "false")).to_error "#1 " - - it matches argument type: - expect (fn ("boolean", false)).not_to_error () - context with single argument table: - it diagnoses missing argument: expect (fn ({"boolean"}, {nil})). @@ -355,6 +394,7 @@ specify std.debug: require "std.string".tostring (debugp), table.concat (require "std.list".map (mkwrap, {...}), ", ")) end + - it does nothing when _DEBUG is disabled: expect (luaproc (mkdebug (false, "nothing to see here"))). not_to_contain_error "nothing to see here" @@ -392,6 +432,7 @@ specify std.debug: require "std.string".tostring (debugp), table.concat (require "std.list".map (mkwrap, {...}), ", ")) end + - context when _DEBUG is disabled: - it does nothing when message level is not set: expect (luaproc (mksay (false, "nothing to see here"))). @@ -475,6 +516,19 @@ specify std.debug: - describe trace: + - before: + fname = "trace" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("foo", false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + - it does nothing when _DEBUG is disabled: expect (luaproc [[ _DEBUG = false From 4081aeeafdeb5f83237c16afd37e4325aa46fb20 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 12 Jun 2014 12:18:06 +0700 Subject: [PATCH 241/703] debug: improve argcheck list semantics. Following the principle of least surprise, make a new `#list` check type that has the same behaviour as `list` did previously, for orthogonality with `#table`, and add a new `list` implementation for orthogonality with `table`. * specs/debug_spec.yaml (argcheck): Specify behaviours of `list` and `#list` with new semantics. * lib/std/debug.lua (argcheck): Update LDocs. * lib/std/base.lua (argcheck): Count the elements of a `list` and ensure that number is not greater than the result of the length operator -- i.e. the table has no holes, and no non- integer keys. When building a type-mismatch error, write `empty list` where appropriate. * lib/std/debug.lua (argcheck): Make the 2nd argument a `#list`. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 29 +++++++++++++++++++++-------- lib/std/debug.lua | 5 +++-- specs/debug_spec.yaml | 36 ++++++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 5a207d8..0a909f2 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -131,9 +131,17 @@ if _ARGCHECK then ok = true end - elseif check == "list" then - if typeof (actual) == "table" and #actual > 0 then - ok = true + elseif check == "list" or check == "#list" then + if actualtype == "table" then + local len, count = #actual, 0 + local i = next (actual) + repeat + if i ~= nil then count = count + 1 end + i = next (actual, i) + until i == nil or count > len + if count == len and (check == "list" or count > 0) then + ok = true + end end elseif check == "object" then @@ -158,12 +166,17 @@ if _ARGCHECK then if not ok then if actualtype == "nil" then actualtype = "no value" - elseif actualtype == "table" and next (actual) == nil then - actualtype = "empty table" - elseif actualtype == "List" and #actual == 0 then - actualtype = "empty List" + elseif type (actual) == "table" and next (actual) == nil then + local expectedstr = "," .. table.concat (expected, ",") .. "," + if actualtype == "table" and expectedstr == ",#list," then + actualtype = "empty list" + elseif actualtype == "table" or expectedstr:match ",#" then + actualtype = "empty " .. actualtype + end end - expected = concat (expected):gsub ("#table", "non-empty table") + expected = concat (expected): + gsub ("#table", "non-empty table"): + gsub ("#list", "non-empty list") argerror (name, i, expected .. " expected, got " .. actualtype, level + 1) end end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 87738f6..e4ae307 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -175,7 +175,8 @@ export (M, "argerror (string, int, string?, int?)", base.argerror) -- file accept an open file object -- function accept a function, or object with a __call metamethod -- int accept an integer valued number --- list accept a table with a non-empty array part +-- list accept a table where all keys are a contiguous 1-based integer range +-- #list accept any non-empty list -- object accept any std.Object derived type -- :foo accept only the exact string ":foo", works for any :-prefixed string -- @@ -218,7 +219,7 @@ export (M, "argcheck (string, int, string, any?, int?)", base.argcheck) -- local function curry (f, n) -- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) -- ... -export (M, "argscheck (string, #table, table)", base.argscheck) +export (M, "argscheck (string, #list, table)", base.argscheck) for k, v in pairs (debug) do diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 1f5b255..29ed8bf 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -199,17 +199,33 @@ specify std.debug: - it diagnoses mismatched list types: expect (fn ("list", false)). to_error "list expected, got boolean" - expect (fn ("list", {})). - to_error "list expected, got empty table" expect (fn ("list", {foo=1})). to_error "list expected, got table" expect (fn ("list", Object)). to_error "list expected, got Object" expect (fn ("list", List {})). - to_error "list expected, got empty List" + to_error "list expected, got List" - it matches list types: + expect (fn ("list", {})).not_to_error () expect (fn ("list", {1})).not_to_error () - expect (fn ("list", List {1})).not_to_error () + - it diagnonses missing non-empty list types: + expect (fn ("#list", nil)). + to_error "non-empty list expected, got no value" + - it diagnoses mismatched non-empty list types: + expect (fn ("#list", false)). + to_error "non-empty list expected, got boolean" + expect (fn ("#list", {})). + to_error "non-empty list expected, got empty list" + expect (fn ("#list", {foo=1})). + to_error "non-empty list expected, got table" + expect (fn ("#list", Object)). + to_error "non-empty list expected, got empty Object" + expect (fn ("#list", List {})). + to_error "non-empty list expected, got empty List" + expect (fn ("#list", List {1})). + to_error "non-empty list expected, got List" + - it matches non-empty list types: + expect (fn ("#list", {1})).not_to_error () - it diagnoses missing object types: expect (fn ("object", nil)).to_error "object expected, got no value" expect (fn ("Object", nil)).to_error "Object expected, got no value" @@ -244,10 +260,10 @@ specify std.debug: - it diagnoses mismatched elements: expect (fn ("string|table", false)). to_error "string or table expected, got boolean" - expect (fn ("string|list|#table", {})). - to_error "string, list or non-empty table expected, got empty table" - expect (fn ("string|number|list|object", {})). - to_error "string, number, list or object expected, got empty table" + expect (fn ("string|#table", {})). + to_error "string or non-empty table expected, got empty table" + expect (fn ("string|number|#list|object", {})). + to_error "string, number, non-empty list or object expected, got empty table" - it matches any type from a list: expect (fn ("string|table", "foo")).not_to_error () expect (fn ("string|table", {})).not_to_error () @@ -315,12 +331,12 @@ specify std.debug: - it diagnoses missing arguments: expect (f ()).to_error (msg (1, "string")) - expect (f "foo").to_error (msg (2, "non-empty table")) + expect (f "foo").to_error (msg (2, "non-empty list")) expect (f ("foo", {"bar"})).to_error (msg (3, "table")) - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "string", "boolean")) expect (f ("foo", false)). - to_error (msg (2, "non-empty table", "boolean")) + to_error (msg (2, "non-empty list", "boolean")) expect (f ("foo", {"bar"}, false)).to_error (msg (3, "table", "boolean")) - it diagnoses too many arguments: expect (f ("foo", {"bar"}, {}, false)). From f466ad3d81ba53d7828054cffe6348bfdfc21bfc Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 23:22:34 +0100 Subject: [PATCH 242/703] debug: revert export of argerror, argcheck and argscheck. Annoyingly, Lua 5.1 misses an obvious tail call elimination when _DEBUG.argcheck is set, so the deep call to error gets the wrong level, and reports argument errors in the wrong functions!! Rather than uglify the code to remove the tail-calls and do a recount, or add a fudge factor when Lua 5.1 is detected, it's cleaner to remove the argchecking wrappers of the 3 affected functions -- at least until we're ready to drop Lua 5.1 support entirely. * specs/debug_spec.yaml (argerror, argcheck, argscheck): Mark the argument checking behaviours as pending. * lib/std/debug.lua (argerror, argcheck, argscheck): Comment out the argument checking wrappers, and call the bare functions. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 28 +++++++++++++++++++++++++++- specs/debug_spec.yaml | 10 +++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index e4ae307..2f55aea 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -158,7 +158,16 @@ end -- local h, err = input_handle (file) -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end -- ... +M.argerror = base.argerror + +--[[ + Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, + and this function has to count stack frames correctly and so breaks in + that case. After 5.1 support is dropped, we can enable the + following: + export (M, "argerror (string, int, string?, int?)", base.argerror) +]] --- Check the type of an argument against expected types. @@ -207,8 +216,16 @@ export (M, "argerror (string, int, string?, int?)", base.argerror) -- local function case (with, branches) -- argcheck ("std.functional.case", 2, "#table", branches) -- ... -export (M, "argcheck (string, int, string, any?, int?)", base.argcheck) +M.argcheck = base.argcheck + +--[[ + Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, + and this function has to count stack frames correctly and so breaks in + that case. After 5.1 support is dropped, we can enable the + following: +export (M, "argcheck (string, int, string, any?, int?)", base.argcheck) +]] --- Check that all arguments match specified types. -- @function argscheck @@ -219,7 +236,16 @@ export (M, "argcheck (string, int, string, any?, int?)", base.argcheck) -- local function curry (f, n) -- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) -- ... +M.argscheck = base.argscheck + +--[[ + Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, + and this function has to count stack frames correctly and so breaks in + that case. After 5.1 support is dropped, we can enable the + following: + export (M, "argscheck (string, #list, table)", base.argscheck) +]] for k, v in pairs (debug) do diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 29ed8bf..a78c3f8 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -51,9 +51,11 @@ specify std.debug: f = M[fname] - it diagnoses missing arguments: + pending "Lua 5.1 support is dropped" expect (f ()).to_error (msg (1, "string")) expect (f "foo").to_error (msg (2, "int")) - it diagnoses wrong argument types: + pending "Lua 5.1 support is dropped" expect (f (false)).to_error (msg (1, "string", "boolean")) expect (f ("foo", false)).to_error (msg (2, "int", "boolean")) expect (f ("foo", 1, false)). @@ -61,6 +63,7 @@ specify std.debug: expect (f ("foo", 1, "bar", false)). to_error (msg (4, "int or nil", "boolean")) - it diagnoses too many arguments: + pending "Lua 5.1 support is dropped" expect (f ("foo", 1, "bar", 2, false)). to_error (toomanyarg (this_module, fname, 4, 5)) @@ -105,16 +108,19 @@ specify std.debug: f = M[fname] - it diagnoses missing arguments: + pending "Lua 5.1 support is dropped" expect (f ()).to_error (msg (1, "string")) expect (f "foo").to_error (msg (2, "int")) expect (f ("foo", 1)).to_error (msg (3, "string")) - it diagnoses wrong argument types: + pending "Lua 5.1 support is dropped" expect (f (false)).to_error (msg (1, "string", "boolean")) expect (f ("foo", false)).to_error (msg (2, "int", "boolean")) expect (f ("foo", 1, false)).to_error (msg (3, "string", "boolean")) expect (f ("foo", 1, "bar", 2, false)). to_error (msg (5, "int or nil", "boolean")) - it diagnoses too many arguments: + pending "Lua 5.1 support is dropped" expect (f ("foo", 1, "bar", 2, 3, false)). to_error (toomanyarg (this_module, fname, 5, 6)) @@ -330,15 +336,17 @@ specify std.debug: f = M[fname] - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + pending "Lua 5.1 support is dropped" expect (f "foo").to_error (msg (2, "non-empty list")) expect (f ("foo", {"bar"})).to_error (msg (3, "table")) - it diagnoses wrong argument types: + pending "Lua 5.1 support is dropped" expect (f (false)).to_error (msg (1, "string", "boolean")) expect (f ("foo", false)). to_error (msg (2, "non-empty list", "boolean")) expect (f ("foo", {"bar"}, false)).to_error (msg (3, "table", "boolean")) - it diagnoses too many arguments: + pending "Lua 5.1 support is dropped" expect (f ("foo", {"bar"}, {}, false)). to_error (toomanyarg (this_module, fname, 3, 4)) From bd0944f24c9c1fd0ba2fcb228e33c09c81a111d6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 1 Jul 2014 10:20:38 +0100 Subject: [PATCH 243/703] debug: revert export of trace. Both because the argument check wrapper is not properly tail-call eliminated by Lua 5.1, and because as a core function callback the function signature is fixed already, there's no good reason to check arguments on debug.trace. * specs/debug_spec.yaml (trace): Remove argument check behaviour specifications. * lib/std/debug.lua (trace): Remove the export wrapper. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 4 ++-- specs/debug_spec.yaml | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 2f55aea..aa3453d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -111,7 +111,7 @@ local level = 0 -- @usage -- _DEBUG = { call = true } -- local debug = require "std.debug" -export (M, "trace (string)", function (event) +function M.trace (event) local t = debug.getinfo (3) local s = " >>> " .. string.rep (" ", level) if t ~= nil and t.currentline >= 0 then @@ -136,7 +136,7 @@ export (M, "trace (string)", function (event) s = s .. event .. " " .. (t.name or "(C)") .. " [" .. t.what .. "]" end io.stderr:write (s .. "\n") -end) +end -- Set hooks according to _DEBUG if type (_DEBUG) == "table" and _DEBUG.call then diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index a78c3f8..dac18fa 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -545,14 +545,6 @@ specify std.debug: msg = bind (badarg, {this_module, fname}) f = M[fname] - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("foo", false)). - to_error (toomanyarg (this_module, fname, 1, 2)) - - it does nothing when _DEBUG is disabled: expect (luaproc [[ _DEBUG = false From dcd8ead44bb4ca62e79e83779044bd3e7b41ba25 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 13 Jun 2014 11:37:40 +0700 Subject: [PATCH 244/703] refactor: use a function to export io apis. * specs/io_spec.yaml (catdir, catfile, die, monkey_patch) (process_files, readlines, shell, slurp, splitdir, warn) (writelines): Update to latest style. Add "too many argument" behaviour checks. Simplify and standardise argument error message comparisons. * lib/std/io.lua (M): Add module name at element 1. (catdir, catfile, die, monkey_patch, process_files, readlines) (shell, slurp, splitdir, warn, writelines): Upgrade to base.export declarations (for overhead free argcheck calls with _DEBUG = false) and simplify accordingly. (catdir, catfile, die, monkey_patch, process_files, readlines) (shell, slurp, splitdir, warn, writelines): Improve LDocs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 + lib/std/io.lua | 173 ++++++++++++------------------ specs/io_spec.yaml | 262 +++++++++++++++++++++++++++------------------ 3 files changed, 227 insertions(+), 211 deletions(-) diff --git a/NEWS b/NEWS index 6fa7318..1dd1588 100644 --- a/NEWS +++ b/NEWS @@ -41,6 +41,9 @@ Stdlib NEWS - User visible changes need to remove the previously ignored arguments that correspond to the fixed argument positions in the `bind` invocation. + - `io.catdir` now raises an error when called with no arguments, for + consistency with `io.catfile`. + - `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 diff --git a/lib/std/io.lua b/lib/std/io.lua index 432d8eb..89214e3 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -2,8 +2,8 @@ Additions to the core io module. The module table returned by `std.io` also contains all of the entries from - the core io table. An hygienic way to import this module, then, is simply - to override the core `io` locally: + 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" @@ -11,19 +11,17 @@ ]] -local _ARGCHECK = require "std.debug_init"._ARGCHECK - local base = require "std.base" local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } -local argcheck, argerror, leaves, split = - base.argcheck, base.argerror, base.leaves, base.split +local argerror, export, leaves, split = + base.argerror, base.export, base.leaves, base.split -local M -- forward declaration +local M = { "std.io" } @@ -52,14 +50,13 @@ end --- Slurp a file handle. --- @tparam[opt=io.input()] file|string file file handle or name +-- @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 std.io.process_files +-- @see process_files -- @usage contents = slurp (filename) -local function slurp (file) - argcheck ("std.io.slurp", 1, "file|string|nil", file) - +local slurp = export (M, "slurp (file|string|nil)", function (file) local h, err = input_handle (file) if h == nil then argerror ("std.io.slurp", 1, err, 2) end @@ -68,17 +65,17 @@ local function slurp (file) h:close () return s end -end +end) --- Read a file or file handle into a list of lines. --- @tparam[opt=io.input()] file|string file file handle or name +-- 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 --- @return list of lines +-- @treturn list lines -- @usage list = readlines "/etc/passwd" -local function readlines (file) - argcheck ("std.io.readlines", 1, "file|string|nil", file) - +export (M, "readlines (file|string|nil)", function (file) local h, err = input_handle (file) if h == nil then argerror ("std.io.readlines", 1, err, 2) end @@ -88,17 +85,18 @@ local function readlines (file) end h:close () return l -end +end) --- Write values adding a newline after each. --- @tparam[opt=io.output()] file h file handle or name +-- @function writelines +-- @tparam[opt=io.output()] file h open writable file handle; -- the file is **not** closed after writing --- @param ... values to write (as for write) +-- @tparam string|number ... values to write (as for write) -- @usage writelines (io.stdout, "first line", "next line") -local function writelines (h, ...) - argcheck ("std.io.writelines", 1, "file|string|nil", h) - +local writelines = export (M, +"writelines (file|string|number?, string|number?*)", +function (h, ...) if io.type (h) ~= "file" then io.write (h, "\n") h = io.output () @@ -106,100 +104,79 @@ local function writelines (h, ...) for v in leaves (ipairs, {...}) do h:write (v, "\n") end -end +end) --- Overwrite core methods and metamethods with `std` enhanced versions. -- --- Adds `readlines` and `writelines` metamethods to core file objects. +-- Adds @{readlines} and @{writelines} metamethods to core file objects. +-- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions --- @treturn table the module table +-- @treturn table the `std.io` module table -- @usage local io = require "std.io".monkey_patch () -local function monkey_patch (namespace) - argcheck ("std.io.monkey_patch", 1, "table", namespace) - +export (M, "monkey_patch (table?)", function (namespace) namespace = namespace or _G - assert (type (namespace) == "table", - "bad argument #1 to 'monkey_patch' (table expected, got " .. type (namespace) .. ")") - local file_metatable = getmetatable (namespace.io.stdin) - file_metatable.readlines = readlines - file_metatable.writelines = writelines + file_metatable.readlines = M.readlines + file_metatable.writelines = M.writelines return M -end +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 std.io.catdir +-- @see catdir -- @usage dir_components = splitdir (filepath) -local function splitdir (path) - argcheck ("std.io.splitdir", 1, "string", path) - +export (M, "splitdir (string)", function (path) return split (path, package.dirsep) -end +end) --- Concatenate one or more directories and a filename into a path. +-- @function catfile -- @string ... path components -- @treturn string path --- @see std.io.catdir --- @see std.io.splitdir +-- @see catdir +-- @see splitdir -- @usage filepath = catfile ("relative", "path", "filename") -local function catfile (...) - local t = {...} - if _ARGCHECK then - if #t == 0 then - argcheck ("std.io.catfile", 1, "string", nil) - end - for i, v in ipairs (t) do - argcheck ("std.io.catfile", i, "string", v) - end - end - - return table.concat (t, package.dirsep) -end +export (M, "catfile (string*)", function (...) + return table.concat ({...}, package.dirsep) +end) --- Concatenate directory names into a path. --- @param ... path components +-- @function catdir +-- @string ... path components -- @return path without trailing separator --- @see std.io.catfile +-- @see catfile -- @usage dirpath = catdir ("", "absolute", "directory") -local function catdir (...) - local t = {...} - if _ARGCHECK then - for i, v in ipairs (t) do - argcheck ("std.io.catdir", i, "string", v) - end - end - - return (string.gsub (table.concat (t, package.dirsep), "^$", package.dirsep)) -end +export (M, "catdir (string*)", function (...) + return table.concat ({...}, package.dirsep):gsub("^$", package.dirsep) +end) --- Perform a shell command and return its output. +-- @function shell -- @string c command -- @treturn string output, or nil if error --- @see std.io.slurp -- @see os.execute -- @usage users = shell [[cat /etc/passwd | awk -F: '{print $1;}']] -local function shell (c) - argcheck ("std.io.shell", 1, "string", c) - +export (M, "shell (string)", function (c) return slurp (io.popen (c)) -end +end) ------ --- Signature of `process_files` callback function. +-- Signature of @{process_files} callback function. -- @function process_files_callback -- @string[opt] filename filename -- @int[opt] i argument number of *filename* +-- @see process_files --- Process files specified on the command-line. @@ -208,16 +185,15 @@ end -- 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. --- @tparam process_files_callback fn callback function for each file +-- @function process_files +-- @tparam function fn @{process_files_callback} function for each file -- argument --- @see std.io.process_files_callback +-- @see process_files_callback -- @usage #! /usr/bin/env lua -- -- minimal cat command -- local io = require "std.io" -- io.process_files (function () io.write (io.slurp ()) end) -local function process_files (fn) - argcheck ("std.io.process_files", 1, "function", fn) - +export (M, "process_files (function)", function (fn) -- N.B. "arg" below refers to the global array of command-line args if #arg == 0 then arg[#arg + 1] = "-" @@ -230,7 +206,7 @@ local function process_files (fn) end fn (v, i) end -end +end) --- Give warning with the name of program and file (if any). @@ -240,10 +216,11 @@ end -- `opts.program` and `opts.line` if any. @{std.optparse:parse} -- returns an `opts` table that provides the required `program` -- field, as long as you assign it back to `_G.opts`. +-- @function warn -- @string msg format string -- @param ... additional arguments to plug format string specifiers -- @see std.optparse:parse --- @see std.io.die +-- @see die -- @usage -- local OptionParser = require "std.optparse" -- local parser = OptionParser "eg 0\nUsage: eg\n" @@ -251,9 +228,7 @@ end -- if not _G.opts.keep_going then -- require "std.io".warn "oh noes!" -- end -local function warn (msg, ...) - argcheck ("std.io.warn", 1, "string", msg) - +local warn = export (M, "warn (string, any?*)", function (msg, ...) local prefix = "" if (prog or {}).name then prefix = prog.name .. ":" @@ -273,39 +248,23 @@ local function warn (msg, ...) end if #prefix > 0 then prefix = prefix .. " " end writelines (io.stderr, prefix .. string.format (msg, ...)) -end +end) --- Die with error. -- This function uses the same rules to build a message prefix --- as @{std.io.warn}. +-- as @{warn}. +-- @function die -- @string msg format string -- @param ... additional arguments to plug format string specifiers --- @see std.io.warn +-- @see warn -- @usage die ("oh noes! (%s)", tostring (obj)) -local function die (msg, ...) - argcheck ("std.io.die", 1, "string", msg) - - warn (msg, ...) +export (M, "die (string, any?*)", function (...) + warn (...) error () -end +end) ---- @export -M = { - catdir = catdir, - catfile = catfile, - die = die, - monkey_patch = monkey_patch, - process_files = process_files, - readlines = readlines, - shell = shell, - slurp = slurp, - splitdir = splitdir, - warn = warn, - writelines = writelines, -} - for k, v in pairs (io) do M[k] = M[k] or v end diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index b6556c6..18f1a56 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -7,6 +7,8 @@ before: | "process_files", "readlines", "shell", "slurp", "splitdir", "warn", "writelines" } + dirsep = string.match (package.config, "^([^\n]+)\n") + M = require (this_module) specify std.io: @@ -32,63 +34,68 @@ specify std.io: - describe catdir: - - before: - dirsep = string.match (package.config, "^([^\n]+)\n") - - it diagnoses wrong argument types: | - expect (M.catdir (false)). - to_error "bad argument #1 to 'std.io.catdir' (string expected, got boolean)" - expect (M.catdir ("", false)). - to_error "bad argument #2 to 'std.io.catdir' (string expected, got boolean)" - expect (M.catdir ("", "false", false)). - to_error "bad argument #3 to 'std.io.catdir' (string expected, got boolean)" - - it treats no arguments as root directory: - expect (M.catdir ()).to_be (dirsep) + - before: | + fname = "catdir" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("", false)).to_error (msg (2, "string", "boolean")) + expect (f ("", "false", false)).to_error (msg (3, "string", "boolean")) + - it treats initial empty string as root directory: - expect (M.catdir ("")).to_be (dirsep) - expect (M.catdir ("", "")).to_be (dirsep) - expect (M.catdir ("", "root")).to_be (dirsep .. "root") + expect (f ("")).to_be (dirsep) + expect (f ("", "")).to_be (dirsep) + expect (f ("", "root")).to_be (dirsep .. "root") - it returns a single argument unchanged: - expect (M.catdir ("hello")).to_be "hello" + expect (f ("hello")).to_be "hello" - it joins multiple arguments with platform directory separator: - expect (M.catdir ("one", "two")).to_be ("one" .. dirsep .. "two") - expect (M.catdir ("1", "2", "3", "4", "5")). + 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: - dirsep = string.match (package.config, "^([^\n]+)\n") - - it diagnoses missing arguments: | - expect (M.catfile ()). - to_error "bad argument #1 to 'std.io.catfile' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.catfile (false)). - to_error "bad argument #1 to 'std.io.catfile' (string expected, got boolean)" - expect (M.catfile ("", false)). - to_error "bad argument #2 to 'std.io.catfile' (string expected, got boolean)" - expect (M.catfile ("", "false", false)). - to_error "bad argument #3 to 'std.io.catfile' (string expected, got boolean)" + fname = "catfile" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("", false)).to_error (msg (2, "string", "boolean")) + expect (f ("", "false", false)).to_error (msg (3, "string", "boolean")) + - it treats initial empty string as root directory: - expect (M.catfile ("", "")).to_be (dirsep) - expect (M.catfile ("", "root")).to_be (dirsep .. "root") + expect (f ("", "")).to_be (dirsep) + expect (f ("", "root")).to_be (dirsep .. "root") - it returns a single argument unchanged: - expect (M.catfile ("")).to_be "" - expect (M.catfile ("hello")).to_be "hello" + expect (f ("")).to_be "" + expect (f ("hello")).to_be "hello" - it joins multiple arguments with platform directory separator: - expect (M.catfile ("one", "two")).to_be ("one" .. dirsep .. "two") - expect (M.catfile ("1", "2", "3", "4", "5")). + 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: + - before: | script = [[require "std.io".die "By 'eck!"]] - - it diagnoses missing arguments: | - expect (M.die ()). - to_error "bad argument #1 to 'std.io.die' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.die (false)). - to_error "bad argument #1 to 'std.io.die' (string expected, got boolean)" + + fname = "die" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it outputs a message to stderr: expect (luaproc (script)).to_fail_with "By 'eck!\n" - it ignores `prog.line` without `prog.file` or `prog.name`: @@ -142,7 +149,6 @@ specify std.io: - describe monkey_patch: - before: - f = M.monkey_patch mt = {} t = { io = { @@ -151,20 +157,21 @@ specify std.io: stderr = setmetatable ({}, mt), }, } - - it diagnoses missing arguments: | - expect (M.monkey_patch ()). - to_error "bad argument #1 to 'std.io.monkey_patch' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.monkey_patch (false)). - to_error "bad argument #1 to 'std.io.monkey_patch' (table expected, got boolean)" + fname = "monkey_patch" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (t, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it installs readlines metamethod: f (t) expect (mt.readlines).to_be (M.readlines) - it installs writelines metamethod: f (t) expect (mt.writelines).to_be (M.writelines) - - it diagnoses non-table argument: - expect (f "bad").to_error "table expected" - describe process_files: @@ -180,16 +187,21 @@ specify std.io: catscript = [[ require "std.io".process_files (function () io.write (io.input ():read "*a") end) ]] - - it diagnoses missing arguments: | - expect (M.process_files ()). - to_error "bad argument #1 to 'std.io.process_files' (function expected, got no value)" + fname = "process_files" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) - it diagnoses wrong argument types: | - expect (M.process_files (false)). - to_error "bad argument #1 to 'std.io.process_files' (function expected, got boolean)" + expect (f (false)).to_error (msg (1, "function", "boolean")) 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 } + - it diagnoses too many arguments: + expect (f (f, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it defaults to `-` if no arguments were passed: expect (luaproc (ascript)).to_output "-\n" - it iterates over arguments with supplied function: @@ -218,40 +230,54 @@ specify std.io: h:close () defaultin = io.input () + fname = "readlines" + msg = bind (badarg, {this_module, fname}) + f = M[fname] - after: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end + - it diagnoses wrong argument types: | - expect (M.readlines (false)). - to_error "bad argument #1 to 'std.io.readlines' (file, string or nil expected, got boolean)" - expect (M.readlines "not-an-existing-file"). + expect (f (false)).to_error (msg (1, "file, string or nil", "boolean")) + expect (f "not-an-existing-file"). to_error "bad argument #1 to 'std.io.readlines' (" -- system dependent error message - - it diagnoses closed file argument: | + - it diagnoses closed file argument: closed = io.open (name, "r") closed:close () - expect (M.readlines (closed)). - to_error "bad argument #1 to 'std.io.readlines' (file, string or nil expected, got closed file)" + expect (f (closed)). + to_error (msg (1, "file, string or nil", "closed file")) + - it diagnoses too many arguments: + expect (f ("string", false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + - it closes file handle upon completion: h = io.open (name) expect (io.type (h)).not_to_be "closed file" - M.readlines (h) + f (h) expect (io.type (h)).to_be "closed file" - it reads lines from an existing named file: - expect (M.readlines (name)).to_equal (lines) + expect (f (name)).to_equal (lines) - it reads lines from an open file handle: - expect (M.readlines (io.open (name))).to_equal (lines) + expect (f (io.open (name))).to_equal (lines) - it reads from default input stream with no arguments: io.input (name) - expect (M.readlines ()).to_equal (lines) + expect (f ()).to_equal (lines) - describe shell: - - it diagnoses missing arguments: | - expect (M.shell ()). - to_error "bad argument #1 to 'std.io.shell' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.shell (false)). - to_error "bad argument #1 to 'std.io.shell' (string expected, got boolean)" + - before: + fname = "shell" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + - it returns the output from a shell command string: - expect (M.shell [[printf '%s\n' 'foo' 'bar']]).to_be "foo\nbar\n" + expect (f [[printf '%s\n' 'foo' 'bar']]).to_be "foo\nbar\n" - describe slurp: @@ -262,57 +288,76 @@ specify std.io: h:close () defaultin = io.input () + fname = "slurp" + msg = bind (badarg, {this_module, fname}) + f = M[fname] - after: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end + - it diagnoses wrong argument types: | - expect (M.slurp (false)). - to_error "bad argument #1 to 'std.io.slurp' (file, string or nil expected, got boolean)" - expect (M.slurp "not-an-existing-file"). + expect (f (false)).to_error (msg (1, "file, string or nil", "boolean")) + expect (f "not-an-existing-file"). to_error "bad argument #1 to 'std.io.slurp' (" -- system dependent error message + - it diagnoses closed file argument: + closed = io.open (name, "r") closed:close () + expect (f (closed)). + to_error (msg (1, "file, string or nil", "closed file")) + - it diagnoses too many arguments: + expect (f ("string", false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + - it reads content from an existing named file: - expect (M.slurp (name)).to_be (content) + expect (f (name)).to_be (content) - it reads content from an open file handle: - expect (M.slurp (io.open (name))).to_be (content) + 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" - M.slurp (h) + f (h) expect (io.type (h)).to_be "closed file" - it reads from default input stream with no arguments: io.input (name) - expect (M.slurp ()).to_be (content) + expect (f ()).to_be (content) - describe splitdir: - before: - dirsep = string.match (package.config, "^([^\n]+)\n") - - it diagnoses missing arguments: | - expect (M.splitdir ()). - to_error "bad argument #1 to 'std.io.splitdir' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.splitdir (false)). - to_error "bad argument #1 to 'std.io.splitdir' (string expected, got boolean)" + fname = "splitdir" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + - it returns a filename as a one element list: - expect (M.splitdir ("hello")).to_equal {"hello"} + expect (f ("hello")).to_equal {"hello"} - it splits root directory in two empty elements: - expect (M.splitdir (dirsep)).to_equal {"", ""} + expect (f (dirsep)).to_equal {"", ""} - it returns initial empty string for absolute path: - expect (M.splitdir (dirsep .. "root")).to_equal {"", "root"} + expect (f (dirsep .. "root")).to_equal {"", "root"} - it returns multiple components split at platform directory separator: - expect (M.splitdir ("one" .. dirsep .. "two")).to_equal {"one", "two"} - expect (M.splitdir (table.concat ({"1", "2", "3", "4", "5"}, dirsep))). + 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!"]] - - it diagnoses missing arguments: | - expect (M.warn ()). - to_error "bad argument #1 to 'std.io.warn' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.warn (false)). - to_error "bad argument #1 to 'std.io.warn' (string expected, got boolean)" + fname = "warn" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - 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`: @@ -368,27 +413,36 @@ specify std.io: lines = M.readlines (io.open "specs/spec_helper.lua") defaultout = io.output () + fname = "writelines" + msg = bind (badarg, {this_module, fname}) + f = M[fname] - after: if io.type (defaultout) ~= "closed file" then io.output (defaultout) end h:close () os.remove (name) + - it diagnoses wrong argument types: | - expect (M.writelines (false)). - to_error "bad argument #1 to 'std.io.writelines' (file, string or nil expected, got boolean)" + expect (f (false)). + to_error (msg (1, "file, string, number or nil", "boolean")) - it diagnoses closed file argument: | closed = io.open (name) closed:close () - expect (M.writelines (closed)). - to_error "bad argument #1 to 'std.io.writelines' (file, string or nil expected, got closed file)" + expect (f (closed)). + to_error (msg (1, "file, string, number or nil", "closed file")) + - it does not close the file handle upon completion: expect (io.type (h)).not_to_be "closed file" - M.writelines (h, "foo") + f (h, "foo") expect (io.type (h)).not_to_be "closed file" - it writes lines to an open file handle: - M.writelines (h, unpack (lines)) + 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) - M.writelines (unpack (lines)) + f (unpack (lines)) h:flush () expect (M.readlines (io.open (name))).to_equal (lines) From b4bd1a09e21e52a5ffa3bce4080795b4e11ec75a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 14 Jun 2014 00:22:12 +0700 Subject: [PATCH 245/703] specs: decouple spec_helper.lua from std.table and std.functional. * specs/spec_helper.lua.in (bind): Use our own implmentation of bind, otherwise if std.functional becomes unloadable, the whole specl suite is unusable. (totable): Likewise for std.table.totable. (set): No need to rely on std.set, when a simple table index dereference works equally well. Signed-off-by: Gary V. Vaughan --- specs/spec_helper.lua.in | 49 +++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index a146473..401cd63 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -5,16 +5,36 @@ local std = require "specl.std" package.path = std.package.normalize ("lib/?.lua", package.path) --- Error message specifications use this to shorten argument lists. -bind = require "std.functional".bind - - -- Substitute configured LUA so that hell.spawn doesn't pick up -- a different Lua binary to the one used by Specl itself. If -- we could rely on luaposix availability `posix.getenv` would -- be a nicer way to find this... local LUA = "@LUA@" + +-- Null operation function. +nop = function () 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") @@ -180,8 +200,21 @@ function show_apis (argt) end --- Not local, so that it is available in spec examples. -totable = require "std.table".totable +-- Not local, so that it is available to spec examples. +function totable (x) + local _, m = pcall (function (x) return getmetatable (x)["__totable"] end, x) + if type (m) == "function" then + return m (x) + elseif type (x) == "table" then + return x + elseif type (x) == "string" then + local t = {} + x:gsub (".", function (c) t[#t + 1] = c end) + return t + else + return nil + end +end -- Stub inprocess.capture if necessary; new in Specl 12. @@ -189,10 +222,10 @@ 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 set = require "std.set" local util = require "specl.util" local matchers = require "specl.matchers" @@ -220,7 +253,7 @@ do matchers.have_member = Matcher { function (self, actual, expect) - return set.member (actual, expect) + return actual[expect] ~= nil end, actual = "set", From e55bdf3691e67e47154d9a33d1f7addba352c04b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 14 Jun 2014 00:25:52 +0700 Subject: [PATCH 246/703] doc: correct parameter descriptions on debug.argscheck. * lib/std/debug.lua (argscheck): Improve LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index aa3453d..1fcd69d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -230,8 +230,8 @@ export (M, "argcheck (string, int, string, any?, int?)", base.argcheck) --- Check that all arguments match specified types. -- @function argscheck -- @string name function to blame in error message --- @tparam table expected a list of lists of acceptable argument types --- @tparam table actual argument value, or table of argument values +-- @tparam table expected a list of acceptable argument types +-- @tparam table actual table of argument values -- @usage -- local function curry (f, n) -- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) From 713d567dc82382f6764bbfe012308dfa49ddcffa Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 18:20:35 +0100 Subject: [PATCH 247/703] doc: improve strict LDocs. * lib/std/strict.lua: Improve LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/strict.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/std/strict.lua b/lib/std/strict.lua index 9a7590b..6d7be04 100644 --- a/lib/std/strict.lua +++ b/lib/std/strict.lua @@ -4,6 +4,9 @@ All global variables must be 'declared' through a regular assignment (even assigning `nil` will do) in a top-level chunk before being used anywhere or assigned to inside a function. + + To use this module, just require it near the start of your program. + From Lua distribution (`etc/strict.lua`). @module std.strict @@ -17,9 +20,13 @@ if mt == nil then setmetatable (_G, mt) end + +-- The set of globally declared variables. mt.__declared = {} +--- What kind of variable declaration is this? +-- @treturn string "C", "Lua" or "main" local function what () local d = getinfo (3, "S") return d and d.what or "C" @@ -28,6 +35,9 @@ end --- Detect assignment to undeclared global. -- @function __newindex +-- @tparam table t `_G` +-- @string n name of the variable being declared +-- @param v initial value of the variable mt.__newindex = function (t, n, v) if not mt.__declared[n] then local w = what () @@ -40,8 +50,10 @@ mt.__newindex = function (t, n, v) end ---- Detect derefrence of undeclared global. +--- Detect dereference of undeclared global. -- @function __index +-- @tparam table t `_G` +-- @string n name of the variable being dereferenced mt.__index = function (t, n) if not mt.__declared[n] and what () ~= "C" then error ("variable '" .. n .. "' is not declared", 2) From 1c7be6b97a0e1f0b8f326e16f75693e5920e3957 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 14 Jun 2014 00:29:29 +0700 Subject: [PATCH 248/703] base: support optional arguments in export type declarations. The algorithm is approximately to collect every possible permutation of argument type-spec list with and without any optional arguments. An optional argument in the last position must match the given type or nil, so that the permutation with the final optional removed matches, allowing an uncaught mismatched type at that position. Then we try to match the actual arguments against each permutation until one passes, otherwise diagnose the mismatch, reporting that any type at the mismatched index from all permutations is required. * specs/base_spec.yaml (export): Specify behaviours when called with a declaration containing an optional argument wrappend in square brackets. * lib/std/base.lua (match, formaterror): New functions; factored out of argcheck. (copy, match, normalize, permutations): New functions; support classification of matchable argument type-specs. (export): Use them to implement optional arguments in export type declarations to satisfy new specifications. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 291 +++++++++++++++++++++++++++++++++---------- specs/base_spec.yaml | 92 ++++++++++++-- 2 files changed, 307 insertions(+), 76 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 0a909f2..d192bdb 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -25,12 +25,199 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK -local typeof = type -local toomanyarg_fmt = "too many arguments to '%s' (no more than %d expected, got %d)" +local argcheck, argerror, argscheck, prototype -- forward declarations + + +--[[ ================= ]]-- +--[[ Helper functions. ]]-- +--[[ ================= ]]-- + + +local toomanyarg_fmt = + "too many arguments to '%s' (no more than %d expected, got %d)" + + +--- Make a shallow copy of a table. +-- @tparam table t source table +-- @treturn table shallow copy of *t* +local function copy (t) + local new = {} + for k, v in pairs (t) do new[k] = v end + return new +end + + +--- Concatenate a table of strings using ", " and " or " delimiters. +-- @tparam table alternatives a table of strings +-- @treturn string string of elements from alternatives delimited by ", " +-- and " or " +local function concat (alternatives) + if #alternatives > 1 then + local t = copy (alternatives) + local top = table.remove (t) + t[#t] = t[#t] .. " or " .. top + alternatives = t + end + return table.concat (alternatives, ", ") +end + + +--- Normalize a list of type names. +-- @tparam table list of type names, trailing "?" as required +-- @treturn table a new list with "?" stripped, "nil" appended if so, +-- and with duplicates stripped. +local function normalize (t) + local i, r, add_nil = 1, {}, false + for _, v in ipairs (t) do + local m = v:match "^(.+)%?$" + if m then + add_nil = true + r[m] = r[m] or i + i = i + 1 + elseif v then + r[v] = r[v] or i + i = i + 1 + end + end + if add_nil then + r["nil"] = r["nil"] or i + end + + -- Invert the return table. + local t = {} + for v, i in pairs (r) do t[i] = v end + return t +end + + +--- Merge |-delimited type-specs, omitting duplicates. +-- @string ... type-specs +-- @treturn table list of merged and normalized type-specs +local function merge (...) + local i, t = 1, {} + for _, v in ipairs {...} do + v:gsub ("([^|]+)", function (m) t[i] = m; i = i + 1 end) + end + return normalize (t) +end + + +--- Calculate permutations of type lists with and without [optionals]. +-- @tparam table types a list of expected types by argument position +-- @treturn table set of possible type lists +local function permutations (types) + local p, sentinel = {{}}, {"optional arg"} + for i, v in ipairs (types) do + -- Remove sentinels before appending `v` to each list. + for _, v in ipairs (p) do + if v[#v] == sentinel then table.remove (v) end + end + + local opt = v:match "%[(.+)%]" + if opt == nil then + -- Append non-optional type-spec to each permutation. + for b = 1, #p do table.insert (p[b], v) end + else + -- Duplicate all existing permutations, and add optional type-spec + -- to the unduplicated permutations. + local o = #p + for b = 1, o do + p[b + o] = copy (p[b]) + table.insert (p[b], opt) + end + + -- Leave a marker for optional argument in final position. + for _, v in ipairs (p) do + table.insert (v, sentinel) + end + end + end + + -- Replace sentinels with "nil". + for i, v in ipairs (p) do + if v[#v] == sentinel then + table.remove (v) + if #v > 0 then + v[#v] = v[#v] .. "|nil" + else + v[1] = "nil" + end + end + end + + return p +end + + +--- Return index of the first mismatch between types and args, or `nil`. +-- @tparam table types a list of expected types by argument position +-- @tparam table args a table of arguments to compare +-- @treturn int|nil position of first mismatch in *types* +local function match (types, args, allargs) + local typec, argc = #types, #args + for i = 1, typec do + local ok = pcall (argcheck, "pcall", i, types[i], args[i]) + if not ok then return i end + end + if allargs then + for i = typec + 1, argc do + local ok = pcall (argcheck, name, i, types[typec], args[i]) + if not ok then return i end + end + end +end + + +--- Format a type mismatch error. +-- @tparam table expectedtypes a table of matchable types +-- @string actual the actual argument to match with +-- @treturn string formatted *extramsg* for this mismatch for @{argerror} +local function formaterror (expectedtypes, actual) + local actualtype = prototype (actual) + + -- Tidy up actual type for display. + if actualtype == "nil" then + actualtype = "no value" + elseif actualtype == "string" and actual:sub (1, 1) == ":" then + actualtype = actual + elseif type (actual) == "table" and next (actual) == nil then + local matchstr = "," .. table.concat (expectedtypes, ",") .. "," + if actualtype == "table" and matchstr == ",#list," then + actualtype = "empty list" + elseif actualtype == "table" or matchstr:match ",#" then + actualtype = "empty " .. actualtype + end + end + + -- Tidy up expected types for display. + local t = {} + for i, v in ipairs (expectedtypes) do + if v == "func" then + t[i] = "function" + elseif v == "any" then + t[i] = "any value" + else + t[i] = v + end + end + + local expectedstr = concat (t): + gsub ("#table", "non-empty table"): + gsub ("#list", "non-empty list") + + return expectedstr .. " expected, got " .. actualtype +end + + + +--[[ ============== ]]-- +--[[ API functions. ]]-- +--[[ ============== ]]-- + -- Doc-commented in object.lua -local function prototype (o) +function prototype (o) return (getmetatable (o) or {})._type or io.type (o) or type (o) end @@ -55,49 +242,14 @@ local function split (s, sep) end -local argcheck, argerror, argscheck - if _ARGCHECK then - --- Concatenate a table of strings using ", " and " or " delimiters. - -- @tparam table alternatives a table of strings - -- @treturn string string of elements from alternatives delimited by ", " - -- and " or " - local function concat (alternatives) - local t, i = {}, 1 - while i < #alternatives do - t[i] = alternatives[i] - i = i + 1 - end - if #alternatives > 1 then - t[#t] = t[#t] .. " or " .. alternatives[#alternatives] - else - t = alternatives - end - return table.concat (t, ", ") - end - + local typeof = type -- free up `type` for use as a variable -- Doc-commented in debug.lua function argcheck (name, i, expected, actual, level) level = level or 2 - expected = split (expected, "|") - - -- Strip trailing "?" but add "nil" to expected when a "?" is found. - local add_nil = nil - for i, v in ipairs (expected) do - local m, q = v:match "^(.*)(%?)$" - if m then - expected[i] = m - if add_nil == nil and q == "?" then - add_nil = true - end - end - if m == "nil" then add_nil = false end - end - if add_nil then - expected[#expected + 1] = "nil" - end + expected = normalize (split (expected, "|")) -- Check actual has one of the types from expected local ok, actualtype = false, prototype (actual) @@ -108,7 +260,6 @@ if _ARGCHECK then end elseif check == "any" then - expected[i] = "any value" if actual ~= nil then ok = true end @@ -119,7 +270,6 @@ if _ARGCHECK then end elseif check == "function" or check == "func" then - expected[i] = "function" if actualtype == "function" or (getmetatable (actual) or {}).__call ~= nil then @@ -152,8 +302,6 @@ if _ARGCHECK then elseif typeof (check) == "string" and check:sub (1, 1) == ":" then if check == actual then ok = true - elseif actualtype == "string" and actual:sub (1, 1) == ":" then - actualtype = actual end elseif check == actualtype then @@ -164,20 +312,7 @@ if _ARGCHECK then end if not ok then - if actualtype == "nil" then - actualtype = "no value" - elseif type (actual) == "table" and next (actual) == nil then - local expectedstr = "," .. table.concat (expected, ",") .. "," - if actualtype == "table" and expectedstr == ",#list," then - actualtype = "empty list" - elseif actualtype == "table" or expectedstr:match ",#" then - actualtype = "empty " .. actualtype - end - end - expected = concat (expected): - gsub ("#table", "non-empty table"): - gsub ("#list", "non-empty list") - argerror (name, i, expected .. " expected, got " .. actualtype, level + 1) + argerror (name, i, formaterror (expected, actual), level + 1) end end @@ -293,22 +428,50 @@ local function export (M, decl, fn, ...) local name = M[1] .. "." .. name + -- If the final element of types ends with "*", then set max to a + -- sentinel value to denote type-checking of *all* remaining + -- unchecked arguments against that type-spec is required. local max, fin = #types, types[#types]:match "^(.+)%*$" if fin then max = math.huge types[#types] = fin end + -- For optional arguments wrapped in square brackets, make sure + -- type-specs allow for passing or omitting an argument of that + -- type. + local typec, type_specs = #types, permutations (types) + fn = function (...) local args = {...} - local typec, argc = #types, #args - for i = 1, typec do - argcheck (name, i, types[i], args[i]) + local argc, bestmismatch, at = #args, 0, 0 + + for i, types in ipairs (type_specs) do + local mismatch = match (types, args, max == math.huge) + if mismatch == nil then + bestmismatch = nil + break -- every argument matched its type-spec + end + + if mismatch > bestmismatch then bestmismatch, at = mismatch, i end end - if max == math.huge then - for i = typec + 1, argc do - argcheck (name, i, types[typec], args[i]) + + if bestmismatch ~= nil then + -- Report an error for all possible types at bestmismatch index. + local expected + if max == math.huge and bestmismatch >= typec then + expected = normalize (split (types[typec], "|")) + else + local tables = {} + for i, types in ipairs (type_specs) do + if types[bestmismatch] then + tables[#tables + 1] = types[bestmismatch] + end + end + expected = merge (unpack (tables)) end + local i = bestmismatch + argerror (name, i, formaterror (expected, args[i]), 2) end if argc > max then diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 17e521e..73ebe3e 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -16,7 +16,8 @@ specify std.base: end fname = "deprecate" - msg = bind (badarg, {this_module, fname}) + -- Don't depend on functional.bind for base specs! + msg = function (...) return badarg (this_module, fname, ...) end f = M[fname] - it diagnoses missing arguments: | @@ -72,8 +73,11 @@ specify std.base: ]], tostring (debugp), tostring (level)) end + MAGIC = { "unique!" } + mkmagic = function () return MAGIC end + fname = "export" - msg = bind (badarg, {this_module, fname}) + msg = function (...) return badarg (this_module, fname, ...) end f = M[fname] - it diagnoses missing arguments: @@ -87,7 +91,7 @@ specify std.base: - it diagnoses too many arguments: expect (f ({}, "", function () end, false)). to_error (toomanyarg (this_module, fname, 3, 4)) - - it diagnoses missing module name elemest: + - it diagnoses missing module name element: expect (f ({}, "", function () end)). to_error (msg (1, "module name at index 1")) - it diagnoses malformed declaration string: | @@ -102,8 +106,8 @@ specify std.base: M = { "base_spec.yaml" } expect (f (M, "expect (any)", f)).to_be (f) - it stores a function in supplied table at the specified key: - M, MAGIC = { "base_spec.yaml" }, {"unique!"} - f (M, "export (any?)", function () return MAGIC end) + M = { "base_spec.yaml" } + f (M, "export (any?)", mkmagic) expect (M.export ()).to_be (MAGIC) - it stores the passed function when _ARGCHECK is disabled: script = [[ @@ -115,10 +119,74 @@ specify std.base: os.exit (M.export == export and 0 or 1) ]] expect (luaproc (script)).to_succeed () - - it wraps the passed function with a argscheck call: - fake_module = "base_spec.yaml" - M = { fake_module } - fname = "fake_function" - f (M, "fake_function (#table)", function () end) - msg = bind (badarg, {fake_module, fname}) - expect (M[fname] {}).to_error (msg (1, "non-empty table", "empty table")) + - context when checking single argument function: + - before: + fake_module = "base_spec.yaml" + M = { fake_module } + fname = "chk_function" + msg = function (...) return badarg (fake_module, fname, ...) end + f (M, "chk_function (#table)", mkmagic) + chk = M[fname] + - it diagnoses missing arguments: + expect (chk ()).to_error (msg (1, "non-empty table")) + - it diagnoses wrong argument types: + expect (chk {}).to_error (msg (1, "non-empty table", "empty table")) + - it diagnoses too many arguments: + expect (chk ({1}, 2, nil, "", false)). + to_error (toomanyarg (fake_module, fname, 1, 5)) + - it accepts correct argument types: + expect (chk ({1})).to_equal (MAGIC) -- should be .to_be? + - context when checking multi-argument function: + - before: + fake_module = "base_spec.yaml" + M = { fake_module } + fname = "chk_function" + msg = function (...) return badarg (fake_module, fname, ...) end + f (M, "chk_function (table, function)", mkmagic) + chk = M[fname] + - it diagnoses missing arguments: + expect (chk ()).to_error (msg (1, "table")) + expect (chk ({})).to_error (msg (2, "function")) + - it diagnoses wrong argument types: + expect (chk (false)).to_error (msg (1, "table", "boolean")) + expect (chk ({}, false)).to_error (msg (2, "function", "boolean")) + - it diagnoses too many arguments: + expect (chk ({}, nop, false)). + to_error (toomanyarg (fake_module, fname, 2, 3)) + - it accepts correct argument types: + expect (chk ({}, nop)).to_equal (MAGIC) -- should be .to_be? + - context when checking optional argument function: + - before: + fake_module = "base_spec.yaml" + M = { fake_module } + fname = "chk_function" + msg = function (...) return badarg (fake_module, fname, ...) end + f (M, "chk_function ([int])", mkmagic) + chk = M[fname] + - it diagnoses wrong argument types: + expect (chk (false)).to_error (msg (1, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (chk (1, nop)). + to_error (toomanyarg (fake_module, fname, 1, 2)) + - it accepts correct argument types: + expect (chk ()).to_equal (MAGIC) -- should be .to_be? + expect (chk (1)).to_equal (MAGIC) -- should be .to_be? + - context when checking optional multi-argument function: + - before: + fake_module = "base_spec.yaml" + M = { fake_module } + fname = "chk_function" + msg = function (...) return badarg (fake_module, fname, ...) end + f (M, "chk_function ([int], string)", mkmagic) + chk = M[fname] + - it diagnoses missing arguments: + expect (chk ()).to_error (msg (1, "int or string")) + expect (chk (1)).to_error (msg (2, "string")) + - it diagnoses wrong argument types: + expect (chk (false)).to_error (msg (1, "int or string", "boolean")) + - it diagnoses too many arguments: + expect (chk (1, "two", nop)). + to_error (toomanyarg (fake_module, fname, 2, 3)) + - it accepts correct argument types: + expect (chk ("two")).to_equal (MAGIC) -- should be .to_be? + expect (chk (1, "two")).to_equal (MAGIC) -- should be .to_be? From 2a1c4f718c7083f4ef5305570afc71d2eda96b78 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 1 Jul 2014 10:58:01 +0100 Subject: [PATCH 249/703] specs: work around luajit argument counting gotcha. Luajit truncates a variadic function call's argument list at the first nil! * specs/base_spec.yaml (export): Don't pass nil part way through a variadic function call's argument list. Signed-off-by: Gary V. Vaughan --- specs/base_spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 73ebe3e..7201b9a 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -132,7 +132,7 @@ specify std.base: - it diagnoses wrong argument types: expect (chk {}).to_error (msg (1, "non-empty table", "empty table")) - it diagnoses too many arguments: - expect (chk ({1}, 2, nil, "", false)). + expect (chk ({1}, 2, nop, "", false)). to_error (toomanyarg (fake_module, fname, 1, 5)) - it accepts correct argument types: expect (chk ({1})).to_equal (MAGIC) -- should be .to_be? From 3fee22b09b9a13cf0a998f3bb8f49bfe02b7ac8d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 17:50:27 +0100 Subject: [PATCH 250/703] refactor: use a function to export package apis. * specs/package_spec.yaml (find, insert, mappath, normalize) (remove): Update to latest style. Add "too many argument" behaviour checks. Simplify and standardise argument error message comparisons. * lib/std/package.lua (M): Add module name at element 1. (find, insert, mappath, normalize, remove): Upgrade to base.export declarations (for overhead free argcheck calls with _DEBUG = false) and simplify accordingly. (package): Improve LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/package.lua | 89 ++++++------------- specs/package_spec.yaml | 191 ++++++++++++++++++++++------------------ 2 files changed, 131 insertions(+), 149 deletions(-) diff --git a/lib/std/package.lua b/lib/std/package.lua index 4f2fe94..0292432 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -2,8 +2,8 @@ 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 the core `package` locally: + 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" @@ -11,18 +11,16 @@ ]] -local _ARGCHECK = require "std.debug_init"._ARGCHECK - local base = require "std.base" local case = require "std.functional".case local catfile = require "std.io".catfile local invert = require "std.table".invert local escape_pattern = require "std.string".escape_pattern -local argcheck, argscheck, split = - base.argcheck, base.argscheck, base.split +local export, split = base.export, base.split + -local M -- forward declaration +local M = { "std.package" } @@ -55,6 +53,7 @@ end --- 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. @@ -65,11 +64,8 @@ end -- @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 .. "/]") -local function find (pathstrings, patt, init, plain) - argscheck ("std.package.find", - {"string", "string", "int?", "boolean|:plain?"}, - {pathstrings, patt, init, plain}) - +export (M, "find (string, string, int?, boolean|:plain?)", +function (pathstrings, patt, init, plain) local paths = split (pathstrings, M.pathsep) if plain then patt = escape_pattern (patt) end init = init or 1 @@ -77,7 +73,7 @@ local function find (pathstrings, patt, init, plain) for i = init, #paths do if paths[i]:find (patt) then return i, paths[i] end end -end +end) --- Normalize a path list. @@ -86,19 +82,12 @@ end -- of `pathsep` delimited elements; wherein characters are subject to -- `/` and `?` normalization, converting `/` to `dirsep` and `?` to -- `path_mark` (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) -local function normalize (...) - local t = {...} - if _ARGCHECK then - if #t < 1 then argcheck ("std.package.normalize", 1, "string") end - for i, v in ipairs (t) do - argcheck ("std.package.normalize", i, "string", v) - end - end - - local i, paths, pathstrings = 1, {}, table.concat (t, M.pathsep) +local normalize = export (M, "normalize (string*)", function (...) + local i, paths, pathstrings = 1, {}, table.concat ({...}, M.pathsep) for _, path in ipairs (split (pathstrings, M.pathsep)) do path = pathsub (path): gsub (catfile ("^[^", "]"), catfile (".", "%0")): @@ -116,7 +105,7 @@ local function normalize (...) end end return table.concat (invert (paths), M.pathsep) -end +end) ------ @@ -132,23 +121,11 @@ end local unpack = unpack or table.unpack -local function insert (pathstrings, ...) - local args, types = {pathstrings, ...} - if _ARGCHECK then - if #args == 1 then - types = {"string", "int|string"} - elseif #args == 2 then - types = {"string", "string"} - else - types = {"string", "int", "string"} - end - argscheck ("std.package.insert", types, args) - end - +export (M, "insert (string, [int], string)", function (pathstrings, ...) local paths = split (pathstrings, M.pathsep) table.insert (paths, ...) return normalize (unpack (paths)) -end +end) ------ @@ -161,55 +138,43 @@ end --- Call a function with each element of a path string. +-- @function mappath -- @string pathstrings a `package.path` like string -- @tparam mappath_callback 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) -local function mappath (pathstrings, callback, ...) - argscheck ("std.package.mappath", - {"string", "function"}, {pathstrings, callback}) - +export (M, "mappath (string, function, any?*)", +function (pathstrings, callback, ...) for _, path in ipairs (split (pathstrings, M.pathsep)) do local r = callback (path, ...) if r ~= nil then return r end end -end +end) --- 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) -local function remove (pathstrings, pos) - argscheck ("std.package.remove", {"string", "int?"}, {pathstrings, pos}) - +export (M, "remove (string, int?)", function (pathstrings, pos) local paths = split (pathstrings, M.pathsep) table.remove (paths, pos) return table.concat (paths, M.pathsep) -end - - ---- @export -M = { - find = find, - insert = insert, - mappath = mappath, - normalize = normalize, - remove = remove, -} +end) --- Make named constants for `package.config` -- (undocumented in 5.1; see luaconf.h for C equivalents). -- @table package --- @field dirsep directory separator --- @field pathsep path separator --- @field path_mark string that marks substitution points in a path template --- @field execdir (Windows only) replaced by the executable's directory in a path --- @field igmark Mark to ignore all before it when building `luaopen_` function name. +-- @string dirsep directory separator +-- @string pathsep path separator +-- @string path_mark 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. M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark = string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index eac459b..817f2d6 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -38,85 +38,96 @@ specify std.package: - describe find: - - before: path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) - - it diagnoses missing arguments: | - expect (M.find ()). - to_error "bad argument #1 to 'std.package.find' (string expected, got no value)" - expect (M.find (path)). - to_error "bad argument #2 to 'std.package.find' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.find (false)). - to_error "bad argument #1 to 'std.package.find' (string expected, got boolean)" - expect (M.find (path, false)). - to_error "bad argument #2 to 'std.package.find' (string expected, got boolean)" - expect (M.find (path, "foo", false)). - to_error "bad argument #3 to 'std.package.find' (int or nil expected, got boolean)" - expect (M.find (path, "foo", 1, 2)). - to_error "bad argument #4 to 'std.package.find' (boolean, :plain or nil expected, got number)" + - before: | + path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) + + fname = "find" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f (path)).to_error (msg (2, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (path, false)).to_error (msg (2, "string", "boolean")) + expect (f (path, "foo", false)).to_error (msg (3, "int or nil", "boolean")) + expect (f (path, "foo", 1, 2)). + to_error (msg (4, "boolean, :plain or nil", "number")) + - it diagnoses too many arguments: + expect (f (path, "foo", 1, ":plain", false)). + to_error (toomanyarg (this_module, fname, 4, 5)) + - it returns nil for unmatched element: - expect (M.find (path, "unmatchable")).to_be (nil) + expect (f (path, "unmatchable")).to_be (nil) - it returns the element index for a matched element: - expect (M.find (path, "end")).to_be (3) + expect (f (path, "end")).to_be (3) - it returns the element text for a matched element: - i, element = M.find (path, "e.*n") + i, element = f (path, "e.*n") expect ({i, element}).to_equal {1, "begin"} - it accepts a search start element argument: - i, element = M.find (path, "e.*n", 2) + i, element = f (path, "e.*n", 2) expect ({i, element}).to_equal {3, "end"} - it works with plain text search strings: - expect (M.find (path, "m%ddl.")).to_be (nil) - i, element = M.find (path, "%ddl.", 1, ":plain") + 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: - - it diagnoses missing arguments: | - expect (M.insert ()). - to_error "bad argument #1 to 'std.package.insert' (string expected, got no value)" - expect (M.insert (path)). - to_error "bad argument #2 to 'std.package.insert' (int or string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.insert (false)). - to_error "bad argument #1 to 'std.package.insert' (string expected, got boolean)" - expect (M.insert (path, false)). - to_error "bad argument #2 to 'std.package.insert' (string expected, got boolean)" - expect (M.insert (path, 1, false)). - to_error "bad argument #3 to 'std.package.insert' (string expected, got boolean)" + - before: + fname = "insert" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f (path)).to_error (msg (2, "int or string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (path, false)).to_error (msg (2, "int or string", "boolean")) + expect (f (path, 1, false)).to_error (msg (3, "string", "boolean")) + - it diagnoses too many arguments: + expect (f (path, 1, "string", false)). + to_error (toomanyarg (this_module, fname, 3, 4)) + - it appends by default: - expect (M.insert (path, "new")). + expect (f (path, "new")). to_be (M.normalize ("begin", "middle", "end", "new")) - it prepends with pos set to 1: - expect (M.insert (path, 1, "new")). + expect (f (path, 1, "new")). to_be (M.normalize ("new", "begin", "middle", "end")) - it can insert in the middle too: - expect (M.insert (path, 2, "new")). + expect (f (path, 2, "new")). to_be (M.normalize ("begin", "new", "middle", "end")) - expect (M.insert (path, 3, "new")). + 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 (M.insert (path, "new")). + expect (f (path, "new")). to_be (M.normalize ("begin", "middle", "end", "new")) - expect (M.insert (path, 1, "./x/../end")). + expect (f (path, 1, "./x/../end")). to_be (M.normalize ("end", "begin", "middle")) - describe mappath: - - before: + - before: | expected = require "std.string".split (path, M.pathsep) - - it diagnoses missing arguments: | - expect (M.mappath ()). - to_error "bad argument #1 to 'std.package.mappath' (string expected, got no value)" - expect (M.mappath ("")). - to_error "bad argument #2 to 'std.package.mappath' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.mappath (false)). - to_error "bad argument #1 to 'std.package.mappath' (string expected, got boolean)" - expect (M.mappath ("", false)). - to_error "bad argument #2 to 'std.package.mappath' (function expected, got boolean)" + + fname = "mappath" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f ("")).to_error (msg (2, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("", false)).to_error (msg (2, "function", "boolean")) + - it calls a function with each path element: t = {} - M.mappath (path, function (e) t[#t + 1] = e end) + f (path, function (e) t[#t + 1] = e end) expect (t).to_equal (expected) - it passes additional arguments through: | reversed = {} @@ -124,82 +135,88 @@ specify std.package: table.insert (reversed, expected[i]) end t = {} - M.mappath (path, function (e, pos) table.insert (t, pos, e) end, 1) + f (path, function (e, pos) table.insert (t, pos, e) end, 1) expect (t).to_equal (reversed) - describe normalize: - - it diagnoses missing arguments: | - expect (M.normalize ()). - to_error "bad argument #1 to 'std.package.normalize' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.normalize (false)). - to_error "bad argument #1 to 'std.package.normalize' (string expected, got boolean)" - expect (M.normalize ("", false)). - to_error "bad argument #2 to 'std.package.normalize' (string expected, got boolean)" + - before: + fname = "normalize" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("", false)).to_error (msg (2, "string", "boolean")) - context with a single element: - it strips redundant . directories: - expect (M.normalize "./x/./y/.").to_be (catfile (".", "x", "y")) + expect (f "./x/./y/.").to_be (catfile (".", "x", "y")) - it strips redundant .. directories: - expect (M.normalize "../x/../y/z/..").to_be (catfile ("..", "y")) + expect (f "../x/../y/z/..").to_be (catfile ("..", "y")) - it normalizes / to platform dirsep: - expect (M.normalize "/foo/bar").to_be (catfile ("", "foo", "bar")) + expect (f "/foo/bar").to_be (catfile ("", "foo", "bar")) - it normalizes ? to platform path_mark: - expect (M.normalize "?.lua"). + expect (f "?.lua"). to_be (catfile (".", M.path_mark .. ".lua")) - it strips redundant trailing /: - expect (M.normalize "/foo/bar/").to_be (catfile ("", "foo", "bar")) + expect (f "/foo/bar/").to_be (catfile ("", "foo", "bar")) - it inserts missing ./ for relative paths: for _, path in ipairs {"x", "./x"} do - expect (M.normalize (path)).to_be (catfile (".", "x")) + expect (f (path)).to_be (catfile (".", "x")) end - - context with multiple elements: - it strips redundant . directories: - expect (M.normalize ("./x/./y/.", "x")). + expect (f ("./x/./y/.", "x")). to_be (catpath (catfile (".", "x", "y"), catfile (".", "x"))) - it strips redundant .. directories: - expect (M.normalize ("../x/../y/z/..", "x")). + expect (f ("../x/../y/z/..", "x")). to_be (catpath (catfile ("..", "y"), catfile (".", "x"))) - it normalizes / to platform dirsep: - expect (M.normalize ("/foo/bar", "x")). + expect (f ("/foo/bar", "x")). to_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) - it normalizes ? to platform path_mark: - expect (M.normalize ("?.lua", "x")). + expect (f ("?.lua", "x")). to_be (catpath (catfile (".", M.path_mark .. ".lua"), catfile (".", "x"))) - it strips redundant trailing /: - expect (M.normalize ("/foo/bar/", "x")). + 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 (M.normalize (path, "a")). + expect (f (path, "a")). to_be (catpath (catfile (".", "x"), catfile (".", "a"))) end - - it eliminates all but the first equivalent elements: - expect (M.normalize (catpath ("1", "x", "2", "./x", "./2", "./x/../x"))). + expect (f (catpath ("1", "x", "2", "./x", "./2", "./x/../x"))). to_be (catpath ("./1", "./x", "./2")) - describe remove: - - it diagnoses missing arguments: | - expect (M.remove ()). - to_error "bad argument #1 to 'std.package.remove' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.remove (false)). - to_error "bad argument #1 to 'std.package.remove' (string expected, got boolean)" - expect (M.remove ("", false)). - to_error "bad argument #2 to 'std.package.remove' (int or nil expected, got boolean)" + - before: + fname = "remove" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("", false)).to_error (msg (2, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", 2, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - it removes the last item by default: - expect (M.remove (path)).to_be (M.normalize ("begin", "middle")) + expect (f (path)).to_be (M.normalize ("begin", "middle")) - it pops the first item with pos set to 1: - expect (M.remove (path, 1)).to_be (M.normalize ("middle", "end")) + expect (f (path, 1)).to_be (M.normalize ("middle", "end")) - it can remove from the middle too: - expect (M.remove (path, 2)).to_be (M.normalize ("begin", "end")) + 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 (M.remove (path)). + expect (f (path)). to_be (table.concat ({"begin", "middle"}, M.pathsep)) From 3ab56e0582d314d2cffa5c4351290447d1491bab Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 20:22:25 +0100 Subject: [PATCH 251/703] refactor: no need for the underscore in local _floor. There's no clash between M.floor and local floor now we're using `export` to declare api calls. * lib/std/math.lua (_floor): Rename from this... (floor): ...to this. Adjust all callers. Signed-off-by: Gary V. Vaughan --- lib/std/math.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/std/math.lua b/lib/std/math.lua index b12b7ba..9c31bff 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -1,7 +1,7 @@ --[[-- Additions to the core math module. - The module table returned by `std.io` also contains all of the entries from + 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: @@ -12,6 +12,7 @@ local export = require "std.base".export +local floor = math.floor local M = { "std.math" } @@ -23,14 +24,13 @@ local M = { "std.math" } -- @treturn number `n` truncated to `p` decimal places -- @usage tenths = floor (magnitude, 1) -local _floor = math.floor export (M, "floor (number, int?)", function (n, p) if p and p ~= 0 then local e = 10 ^ p - return _floor (n * e) / e + return floor (n * e) / e else - return _floor (n) + return floor (n) end end) @@ -57,7 +57,7 @@ end) -- @usage roughly = round (exactly, 2) export (M, "round (number, int?)", function (n, p) local e = 10 ^ (p or 0) - return _floor (n * e + 0.5) / e + return floor (n * e + 0.5) / e end) From 2001a9ab3abc350d02fdc8017a55f0adc2ca232c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 18:45:10 +0100 Subject: [PATCH 252/703] refactor: simplify std.string.assert. * lib/std/string.lua (assert): Simplify. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index bcbb847..f580433 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -97,14 +97,7 @@ end -- @usage assert (expected == actual, "100% unexpected!") local function assert (v, f, ...) argcheck ("std.string.assert", 2, "string?", f) - - if not v then - if f == nil then - f = "" - end - error (format (f, ...), 2) - end - return v + return v or error (format (f or "", ...), 2) end From 976b49e0a90573e968caf871f2a236b8ed6b0018 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 18:47:17 +0100 Subject: [PATCH 253/703] refactor: simplify std.string.format. * lib/std/string.lua (format): Simplify. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index f580433..e28fd27 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -81,11 +81,7 @@ end local function format (f, arg1, ...) argcheck ("std.string.format", 1, "string", f) - if arg1 == nil then - return f - else - return _format (f, arg1, ...) - end + return (arg1 ~= nil) and _format (f, arg1, ...) or f end From d340c73e49f261367ff59e5089adb3bc8a1fb0c6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 19:03:12 +0100 Subject: [PATCH 254/703] refactor: simplify std.string.tfind. * lib/std/string.lua (tpack): Factored out of tfind, rather than defining a new temporary local pack function on every invocation. (tfind): Simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index e28fd27..dc5c696 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -65,6 +65,19 @@ local function __index (s, i) end +--- Pack return arguments for `tfind`. +-- @int from start of match +-- @int to end of match +-- @param ... captures +-- @treturn int from +-- @treturn int to +-- @treturn table captures +-- @usage return tfind_pack (string.find (str, pattern)) +local function tpack (from, to, ...) + return from, to, {...} +end + + --[[ ================= ]]-- --[[ Module Functions. ]]-- @@ -111,11 +124,7 @@ local function tfind (s, pattern, init, plain) argscheck ("std.string.tfind", {"string", "string", "int?", "boolean|:plain?"}, {s, pattern, init, plain}) - - local function pack (from, to, ...) - return from, to, {...} - end - return pack (pattern.find (s, pattern, init, plain)) + return tpack (s:find (pattern, init, plain)) end From 29e6beeb20aa0eac37038000112c6b9510379aae Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 20:00:23 +0100 Subject: [PATCH 255/703] refactor: simplify std.string.require_version. * lib/std/string.lua (version_to_list, module_version): Factored out of require_version, rather than defining new temporary local functions on each invocation of require_version. (require_version): Simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index dc5c696..72299d8 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -78,6 +78,25 @@ local function tpack (from, to, ...) end +--- Return a List object by splitting version string on periods. +-- @string version a period delimited version string +-- @treturn List a list of version components +local function version_to_list (version) + return List (base_split (version, "%.")) +end + + +--- Extract a list of period delimited integer version components. +-- @tparam table module returned from a `require` call +-- @string pattern to capture version number from a string +-- (default: `"%D*([%.%d]+)"`) +-- @treturn List a list of version components +local function module_version (module, pattern) + local version = module.version or module._VERSION + return version_to_list (version:match (pattern or "%D*([%.%d]+)")) +end + + --[[ ================= ]]-- --[[ Module Functions. ]]-- @@ -195,13 +214,6 @@ local function require_version (module, min, too_big, pattern) {"string", "string?", "string?", "string?"}, {module, min, too_big, pattern}) - local function version_to_list (v) - return List (base_split (v, "%.")) - end - local function module_version (module, pattern) - return version_to_list (string.match (module.version or module._VERSION, - pattern or "%D*([%.%d]+)")) - end local m = require (module) if min then assert (module_version (m, pattern) >= version_to_list (min)) From 8de4f8ea6b72f5cbb1b2e0529f0c1697076bad98 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 30 Jun 2014 22:13:03 +0100 Subject: [PATCH 256/703] refactor: rename string.require_version to string.require. * specs/string_spec.yaml (require): A copy of the require_version specs. (require_version): Also check for deprecation warning on first use. (monkey_patch): Check that new `require` function is written into the given namespace. * specs/std_spec.yaml (barrel): Likewise. (monkey_patch): Check that the deprecated `require_version` is still written into the global namespace. * lib/std/string.lua (require_version): Rename from this... (require): ...to this. (require_version): A deprecated copy of `string.require`. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++ lib/std/string.lua | 22 +++++++--- specs/std_spec.yaml | 3 ++ specs/string_spec.yaml | 96 ++++++++++++++++++++++++++++++++++++------ 4 files changed, 107 insertions(+), 18 deletions(-) diff --git a/NEWS b/NEWS index 1dd1588..4a57e61 100644 --- a/NEWS +++ b/NEWS @@ -58,6 +58,10 @@ Stdlib NEWS - User visible changes passing a non-string will now raise an error as specified in the api documentation. + - `string.require_version` has been renamed to `string.require`, the + old name now gives a deprecation warning on first use, and will be + removed entirely in some future release. + ** Bug fixes: diff --git a/lib/std/string.lua b/lib/std/string.lua index 72299d8..cad6f52 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -24,6 +24,7 @@ local argcheck, argscheck, getmetamethod, base_split = base.argcheck, base.argscheck, base.getmetamethod, base.split local _format = string.format +local _require = require local _tostring = _G.tostring local M = {} @@ -209,12 +210,12 @@ end -- @string[opt] pattern to match version in `module.version` or -- `module._VERSION` (default: `"%D*([%.%d]+)"`) -- @usage std = require ("std", "41") -local function require_version (module, min, too_big, pattern) - argscheck ("std.string.require_version", +local function require (module, min, too_big, pattern) + argscheck ("std.string.require", {"string", "string?", "string?", "string?"}, {module, min, too_big, pattern}) - local m = require (module) + local m = _require (module) if min then assert (module_version (m, pattern) >= version_to_list (min)) end @@ -225,12 +226,17 @@ local function require_version (module, min, too_big, pattern) end +-- DEPRECATED: Remove in first release following 2015-06-30. +local require_version = base.deprecate (require, nil, + "string.require_version is deprecated, use string.require instead") + + --- Overwrite core methods and metamethods with `std` enhanced versions. -- -- Adds auto-stringification to `..` operator on core strings, and -- integer indexing of strings with `[]` dereferencing. -- --- Also replaces core `assert` and `tostring` functions with +-- Also replaces core `assert`, `require` and `tostring` functions with -- `std.string` versions. -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table @@ -239,7 +245,8 @@ local function monkey_patch (namespace) argcheck ("std.string.monkey_patch", 1, "table?", namespace) namespace = namespace or _G - namespace.assert, namespace.tostring = assert, M.tostring + namespace.assert, namespace.require, namespace.tostring = + M.assert, M.require, M.tostring local string_metatable = getmetatable "" string_metatable.__concat = __concat @@ -706,7 +713,7 @@ M = { pickle = pickle, prettytostring = prettytostring, render = render, - require_version = require_version, + require = require, rtrim = rtrim, split = split, tfind = tfind, @@ -715,6 +722,9 @@ M = { wrap = wrap, } +-- Deprecated and undocumented. +M.require_version = require_version + for k, v in pairs (string) do M[k] = M[k] or v end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index e3aa45b..6ea968f 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -45,6 +45,7 @@ specify std: expect (mt.__concat).to_be (std.string.__concat) expect (mt.__index).to_be (std.string.__index) expect (t.assert).to_be (std.string.assert) + expect (t.require).to_be (std.string.require) expect (t.tostring).to_be (std.string.tostring) - it installs std.table monkey patches: expect (t.table.sort).to_be (std.table.sort) @@ -74,6 +75,7 @@ specify std: pickle = std.string.pickle, prettytostring = std.string.prettytostring, render = std.string.render, + require = std.string.require, require_version = std.string.require_version, ripairs = std.table.ripairs, table = t.table, @@ -111,6 +113,7 @@ specify std: expect (mt.__concat).to_be (std.string.__concat) expect (mt.__index).to_be (std.string.__index) expect (t.assert).to_be (std.string.assert) + expect (t.require).to_be (std.string.require) expect (t.tostring).to_be (std.string.tostring) - it installs std.table monkey patches: expect (t.table.sort).to_be (std.table.sort) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 84b8207..aca9309 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -8,8 +8,8 @@ before: | "escape_shell", "finds", "format", "ltrim", "monkey_patch", "numbertosi", "ordinal_suffix", "pad", "pickle", "prettytostring", "render", - "require_version", "rtrim", "split", "tfind", - "tostring", "trim", "wrap" } + "require", "require_version", "rtrim", "split", + "tfind", "tostring", "trim", "wrap" } M = require (this_module) getmetatable ("").__concat = M.__concat @@ -301,6 +301,8 @@ specify std.string: expect (mt.__index).to_be (M.__index) - it installs the assert function: expect (t.assert).to_be (M.assert) + - it installs the require function: + expect (t.require).to_be (M.require) - it installs the tostring function: expect (t.tostring).to_be (M.tostring) @@ -527,21 +529,21 @@ specify std.string: to_be ('Array ("any", {a, b, ' .. tostring (a[3]) .. ', e})') -- describe require_version: +- describe require: - before: - f = M.require_version + f = M.require - it diagnoses missing arguments: | expect (f ()). - to_error "bad argument #1 to 'std.string.require_version' (string expected, got no value)" + to_error "bad argument #1 to 'std.string.require' (string expected, got no value)" - it diagnoses wrong argument types: | expect (f (false)). - to_error "bad argument #1 to 'std.string.require_version' (string expected, got boolean)" + to_error "bad argument #1 to 'std.string.require' (string expected, got boolean)" expect (f ("string", false)). - to_error "bad argument #2 to 'std.string.require_version' (string or nil expected, got boolean)" + to_error "bad argument #2 to 'std.string.require' (string or nil expected, got boolean)" expect (f ("string", "string", false)). - to_error "bad argument #3 to 'std.string.require_version' (string or nil expected, got boolean)" + to_error "bad argument #3 to 'std.string.require' (string or nil expected, got boolean)" expect (f ("string", "string", "string", false)). - to_error "bad argument #4 to 'std.string.require_version' (string or nil expected, got boolean)" + to_error "bad argument #4 to 'std.string.require' (string or nil expected, got boolean)" - it diagnoses non-existent module: expect (f ("module-not-exists", "", "")).to_error "module-not-exists" - it diagnoses module too old: @@ -561,24 +563,94 @@ specify std.string: expect (f ("std", "41", "9999")).to_be (require "std") std._VERSION, std.version = std.version, std._VERSION - context with semantic versioning: - - it diagnoses module too old: | + - 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_error () expect (f ("std", "1.3")).to_error () expect (f ("std", "2.1.2")).to_error () expect (f ("std", "2")).to_error () expect (f ("std", "1.2.10")).to_error () - it diagnoses module too new: | - std = require "std" - std.version = "1.2.3" expect (f ("std", nil, "1.2.2")).to_error () expect (f ("std", nil, "1.1")).to_error () expect (f ("std", nil, "1.1.2")).to_error () expect (f ("std", nil, "1")).to_error () - 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) + + +# DEPRECATED: Remove in first release following 2015-06-30. +- describe require_version: + - before: + f = M.require_version + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {"std.string"}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "require_version is deprecated" + end + _, err = capture (f, {"std.string"}) + expect (err).to_be (nil) + - it diagnoses missing arguments: | + expect (f ()). + to_error "bad argument #1 to 'std.string.require' (string expected, got no value)" + - it diagnoses wrong argument types: | + expect (f (false)). + to_error "bad argument #1 to 'std.string.require' (string expected, got boolean)" + expect (f ("string", false)). + to_error "bad argument #2 to 'std.string.require' (string or nil expected, got boolean)" + expect (f ("string", "string", false)). + to_error "bad argument #3 to 'std.string.require' (string or nil expected, got boolean)" + expect (f ("string", "string", "string", false)). + to_error "bad argument #4 to 'std.string.require' (string or nil expected, got boolean)" + - it diagnoses non-existent module: + expect (f ("module-not-exists", "", "")).to_error "module-not-exists" + - it diagnoses module too old: + expect (f ("std", "9999", "9999")).to_error () + - it diagnoses module too new: + expect (f ("std", "0", "0")).to_error () + - 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", "41")).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: std = require "std" + std._VERSION, std.version = std.version, std._VERSION + expect (f ("std", "41", "9999")).to_be (require "std") + std._VERSION, std.version = std.version, std._VERSION + - 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_error () + expect (f ("std", "1.3")).to_error () + expect (f ("std", "2.1.2")).to_error () + expect (f ("std", "2")).to_error () + expect (f ("std", "1.2.10")).to_error () + - it diagnoses module too new: | + expect (f ("std", nil, "1.2.2")).to_error () + expect (f ("std", nil, "1.1")).to_error () + expect (f ("std", nil, "1.1.2")).to_error () + expect (f ("std", nil, "1")).to_error () + - 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) From 869b4955fa7fc1f1c7dddaab33d81681d72597d4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 1 Jul 2014 11:19:13 +0100 Subject: [PATCH 257/703] refactor: simplify caps, chomp, escape_pattern and escape_shell. * lib/std/string.lua (caps, chomp, escape_pattern, escape_shell): Remove extraneous parens around return argument. Use Lua :-method call sugar to shorten gsub invocations. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index cad6f52..3cff6bd 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -263,10 +263,7 @@ end local function caps (s) argcheck ("std.string.caps", 1, "string", s) - return (string.gsub (s, "(%w)([%w]*)", - function (l, ls) - return string.upper (l) .. ls - end)) + return s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper() .. ls end) end @@ -277,7 +274,7 @@ end local function chomp (s) argcheck ("std.string.chomp", 1, "string", s) - return (string.gsub (s, "\n$", "")) + return s:gsub ("\n$", "") end @@ -288,7 +285,7 @@ end local function escape_pattern (s) argcheck ("std.string.escape_pattern", 1, "string", s) - return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) + return s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") end @@ -301,7 +298,7 @@ end local function escape_shell (s) argcheck ("std.string.escape_shell", 1, "string", s) - return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) + return s:gsub ("([ %(%)%\\%[%]\"'])", "\\%1") end From 67e4335c169c4ca591b270457fc0704e0f701b09 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 1 Jul 2014 14:23:36 +0100 Subject: [PATCH 258/703] refactor: use a function to export string apis. * specs/string_spec.yaml (__concat, __index, assert, caps, chomp) (escape_pattern, escape_shell, finds, format, ltrim, monkey_patch) (numbertosi, ordinal_suffix, pad, pickle, prettytostring, render) (require, require_version, rtrim, split, tfind, tostring, trim) (wrap): Update to latest style: Add "too many argument" behaviour checks. Simplify and standardise argument error message comparisons. * lib/std/string.lua (M): Add module name at element 1. (__concat, __index, assert, caps, chomp, escape_pattern) (escape_shell, finds, format, ltrim, monkey_patch, numbertosi) (ordinal_suffix, pad, pickle, prettytostring, render, require) (require_version, rtrim, split, tfind, tostring, trim): Upgrade to base.export declarations (for overhead free argcheck calls with _DEBUG = false) and simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 309 ++++++++++-------------- specs/string_spec.yaml | 520 ++++++++++++++++++++++++----------------- 2 files changed, 429 insertions(+), 400 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index 3cff6bd..06dbf17 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -10,8 +10,6 @@ @module std.string ]] -local _ARGCHECK = require "std.debug_init"._ARGCHECK - local base = require "std.base" local list = require "std.list" local strbuf = require "std.strbuf" @@ -20,50 +18,21 @@ local table = require "std.table" local List = list {} local StrBuf = strbuf {} -local argcheck, argscheck, getmetamethod, base_split = - base.argcheck, base.argscheck, base.getmetamethod, base.split +local export, getmetamethod, split = + base.export, base.getmetamethod, base.split local _format = string.format -local _require = require local _tostring = _G.tostring -local M = {} - +local M = { "std.string" } - ---[[ ============ ]]-- ---[[ Metamethods. ]]-- ---[[ ============ ]]-- +local render -- forward declaration ---- String concatenation operation. --- @string s initial string --- @param o object to stringify and concatenate --- @return s .. tostring (o) --- @usage --- local string = require "std.string".monkey_patch () --- concatenated = "foo" .. {"bar"} -local function __concat (s, o) - return M.tostring (s) .. M.tostring (o) -end - ---- String subscript operation. --- @string s string --- @tparam int|string i index or method name --- @return `s:sub (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] -local function __index (s, i) - if type (i) == "number" then - return s:sub (i, i) - else - -- Fall back to module metamethods - return M[i] - end -end +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- --- Pack return arguments for `tfind`. @@ -73,7 +42,7 @@ end -- @treturn int from -- @treturn int to -- @treturn table captures --- @usage return tfind_pack (string.find (str, pattern)) +-- @usage return tfind_pack (string.find local function tpack (from, to, ...) return from, to, {...} end @@ -83,7 +52,7 @@ end -- @string version a period delimited version string -- @treturn List a list of version components local function version_to_list (version) - return List (base_split (version, "%.")) + return List (split (version, "%.")) end @@ -99,6 +68,42 @@ end +--[[ ============ ]]-- +--[[ Metamethods. ]]-- +--[[ ============ ]]-- + + +--- String concatenation operation. +-- @string s initial string +-- @param o object to stringify and concatenate +-- @return s .. tostring (o) +-- @usage +-- local string = require "std.string".monkey_patch () +-- concatenated = "foo" .. {"bar"} +function M.__concat (s, o) + return M.tostring (s) .. M.tostring (o) +end + + +--- String subscript operation. +-- @string s string +-- @tparam int|string i index or method name +-- @return `s:sub (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] +function M.__index (s, i) + if type (i) == "number" then + return s:sub (i, i) + else + -- Fall back to module metamethods + return M[i] + end +end + + + --[[ ================= ]]-- --[[ Module Functions. ]]-- --[[ ================= ]]-- @@ -111,26 +116,25 @@ end -- @param[opt] ... arguments to format -- @return formatted string -- @usage print (format "100% stdlib!") -local function format (f, arg1, ...) - argcheck ("std.string.format", 1, "string", f) - +local format = export (M, "format (string, any?*)", function (f, arg1, ...) return (arg1 ~= nil) and _format (f, arg1, ...) or f -end +end) --- Extend to allow formatted arguments. --- @param v value to assert +-- @function assert +-- @param expect expression, expected to be *truthy* -- @string[opt=""] f format string -- @param[opt] ... arguments to format --- @return value +-- @return value of *expect*, if *truthy* -- @usage assert (expected == actual, "100% unexpected!") -local function assert (v, f, ...) - argcheck ("std.string.assert", 2, "string?", f) - return v or error (format (f or "", ...), 2) -end +export (M, "assert (any?, string?, any?*)", function (expect, f, ...) + return expect or error (format (f or "", ...), 2) +end) --- 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 @@ -140,15 +144,13 @@ end -- @treturn table list of captured strings -- @see std.string.finds -- @usage b, e, captures = tfind ("the target string", "%s", 10) -local function tfind (s, pattern, init, plain) - argscheck ("std.string.tfind", - {"string", "string", "int?", "boolean|:plain?"}, - {s, pattern, init, plain}) - return tpack (s:find (pattern, init, plain)) -end +local tfind = export (M, "tfind (string, string, int?, boolean|:plain?)", function (s, ...) + return tpack (s:find (...)) +end) --- 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 @@ -159,23 +161,19 @@ end -- for t in list.elems (finds ("the target string", "%S+")) do -- print (tostring (t.capt)) -- end -local function finds (s, pattern, init, plain) - argscheck ("std.string.finds", - {"string", "string", "int?", "boolean|:plain?"}, - {s, pattern, init, plain}) - - init = init or 1 +export (M, "finds (string, string, int?, boolean|:plain?)", function (s, p, i, ...) + i = i or 1 local l = {} local from, to, r repeat - from, to, r = tfind (s, pattern, init, plain) + from, to, r = tfind (s, p, i, ...) if from ~= nil then l[#l + 1] = {from, to, capt = r} - init = to + 1 + i = to + 1 end until not from return l -end +end) --- Split a string at a given separator. @@ -186,36 +184,20 @@ end -- @string[opt="%s+"] sep separator pattern -- @return list of strings -- @usage words = split "a very short sentence" -local split - -if _ARGCHECK then - - split = function (s, sep) - argscheck ("std.string.split", {"string", "string?"}, {s, sep}) - - return base_split (s, sep) - end - -else - - split = base_split - -end +export (M, "split (string, string?)", split) --- Require a module with a particular version. +-- @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 std = require ("std", "41") -local function require (module, min, too_big, pattern) - argscheck ("std.string.require", - {"string", "string?", "string?", "string?"}, - {module, min, too_big, pattern}) - - local m = _require (module) +export (M, "require (string, string?, string?, string?)", +function (module, min, too_big, pattern) + local m = require (module) if min then assert (module_version (m, pattern) >= version_to_list (min)) end @@ -223,11 +205,11 @@ local function require (module, min, too_big, pattern) assert (module_version (m, pattern) < version_to_list (too_big)) end return m -end +end) -- DEPRECATED: Remove in first release following 2015-06-30. -local require_version = base.deprecate (require, nil, +M.require_version = base.deprecate (M.require, nil, "string.require_version is deprecated, use string.require instead") @@ -238,79 +220,73 @@ local require_version = base.deprecate (require, nil, -- -- Also replaces core `assert`, `require` and `tostring` functions with -- `std.string` versions. +-- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local string = require "std.string".monkey_patch () -local function monkey_patch (namespace) - argcheck ("std.string.monkey_patch", 1, "table?", namespace) - +export (M, "monkey_patch (table?)", function (namespace) namespace = namespace or _G namespace.assert, namespace.require, namespace.tostring = M.assert, M.require, M.tostring local string_metatable = getmetatable "" - string_metatable.__concat = __concat - string_metatable.__index = __index + string_metatable.__concat = M.__concat + string_metatable.__index = M.__index return M -end +end) --- Capitalise each word in a string. +-- @function caps -- @string s any string -- @treturn string *s* with each word capitalized -- @usage userfullname = caps (input_string) -local function caps (s) - argcheck ("std.string.caps", 1, "string", s) - - return s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper() .. ls end) -end +export (M, "caps (string)", function (s) + return s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end) +end) --- 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) -local function chomp (s) - argcheck ("std.string.chomp", 1, "string", s) - +export (M, "chomp (string)", function (s) return s:gsub ("\n$", "") -end +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 = inputstr:match (escape_pattern (literal)) -local function escape_pattern (s) - argcheck ("std.string.escape_pattern", 1, "string", s) - +export (M, "escape_pattern (string)", function (s) return s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") -end +end) --- 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)) -local function escape_shell (s) - argcheck ("std.string.escape_shell", 1, "string", s) - - return s:gsub ("([ %(%)%\\%[%]\"'])", "\\%1") -end +export (M, "escape_shell (string)", function (s) + return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) +end) --- 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)) -local function ordinal_suffix (n) - argcheck ("std.string.ordinal_suffix", 1, "int|string", n) - +export (M, "ordinal_suffix (int|string)", function (n) n = math.abs (n) % 100 local d = n % 10 if d == 1 and n ~= 11 then @@ -322,30 +298,30 @@ local function ordinal_suffix (n) else return "th" end -end +end) --- 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") -local function pad (s, w, p) - argscheck ("std.string.pad", {"string", "int", "string?"}, {s, w, p}) - +export (M, "pad (string, int, string?)", function (s, w, p) p = string.rep (p or " ", math.abs (w)) if w < 0 then return string.sub (p .. s, w) end return string.sub (s .. p, 1, w) -end +end) --- 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 @@ -353,10 +329,7 @@ end -- @treturn string *s* wrapped to *w* columns -- @usage -- print (wrap (copyright, 72, 4)) -local function wrap (s, w, ind, ind1) - argscheck ("std.string.wrap", {"string", "int?", "int?", "int?"}, - {s, w, ind, ind1}) - +export (M, "wrap (string, int?, int?, int?)", function (s, w, ind, ind1) w = w or 78 ind = ind or 0 ind1 = ind1 or ind @@ -381,17 +354,16 @@ local function wrap (s, w, ind, ind1) end end return r:tostring () -end +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") -local function numbertosi (n) - argcheck ("std.string.numbertosi", 1, "number|string", n) - +export (M, "numbertosi (number|string)", function (n) local SIprefix = { [-8] = "y", [-7] = "z", [-6] = "a", [-5] = "f", [-4] = "p", [-3] = "n", [-2] = "mu", [-1] = "m", @@ -407,46 +379,43 @@ local function numbertosi (n) local s = SIprefix[siexp] or "e" .. tostring (siexp) man = man * (10 ^ shift) return tostring (man) .. s -end +end) --- 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)) -local function ltrim (s, r) - argscheck ("std.string.ltrim", {"string", "string?"}, {s, r}) - +export (M, "ltrim (string, string?)", function (s, r) r = r or "%s+" return s:gsub ("^" .. r, "") -end +end) --- 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)) -local function rtrim (s, r) - argscheck ("std.string.rtrim", {"string", "string?"}, {s, r}) - +export (M, "rtrim (string, string?)", function (s, r) r = r or "%s+" return s:gsub (r .. "$", "") -end +end) --- 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: " .. rtrim (userinput)) -local function trim (s, r) - argscheck ("std.string.trim", {"string", "string?"}, {s, r}) - +-- @usage print ("got: " .. trim (userinput)) +export (M, "trim (string, string?)", function (s, r) r = r or "%s+" return s:gsub ("^" .. r, ""):gsub (r .. "$", "") -end +end) --- Stringification Functions @@ -480,6 +449,7 @@ end --- Turn tables into strings with recursion detection. -- N.B. Functions calling render should not recurse, or recursion -- detection will not work. +-- @function render -- @param x object to convert to string -- @tparam render_open_table open open table rendering function -- @tparam render_close_table close close table rendering function @@ -494,11 +464,8 @@ end -- function (_, _, _, i, v) return i .. "=" .. v end, -- mkterminal ",") -- end -local function render (x, open, close, elem, pair, sep, roots) - argscheck ("std.string.render", - {"any?", "func", "func", "func", "func", "func", "table?"}, - {x, open, close, elem, pair, sep, roots}) - +render = export (M, "render (any?, func, func, func, func, func, table?)", +function (x, open, close, elem, pair, sep, roots) local function stop_roots (x) return roots[x] or render (x, open, close, elem, pair, sep, table.clone (roots)) end @@ -525,7 +492,7 @@ local function render (x, open, close, elem, pair, sep, roots) s = s .. sep (x, i, v, nil, nil) .. close (x) return s:tostring () end -end +end) --- Signature of render open table callback. @@ -575,12 +542,13 @@ end --- Extend `tostring` to render table contents as a string. +-- @function tostring -- @param x object to convert to string -- @treturn string compact string rendering of *x* -- @usage -- local tostring = require "std.string".tostring -- print {foo="bar","baz"} --> {1=baz,foo=bar} -local function tostring (x) +function M.tostring (x) return render (x, function () return "{" end, function () return "}" end, @@ -603,10 +571,8 @@ end -- @string[opt=""] spacing space before every line -- @treturn string pretty string rendering of *x* -- @usage print (prettytostring (std, " ")) -local function prettytostring (x, indent, spacing) - argscheck ("std.string.prettytostring", {"any?", "string?", "string?"}, - {x, indent, spacing}) - +export (M, "prettytostring (any?, string?, string?)", +function (x, indent, spacing) indent = indent or "\t" spacing = spacing or "" return render (x, @@ -657,7 +623,7 @@ local function prettytostring (x, indent, spacing) end return s end) -end +end) --- Convert a value to a string. @@ -668,7 +634,7 @@ end -- @see functional.eval -- @usage -- function slow_identity (x) return functional.eval (pickle (x)) end -local function pickle (x) +function M.pickle (x) if type (x) == "string" then return format ("%q", x) elseif type (x) == "number" or type (x) == "boolean" or @@ -679,7 +645,7 @@ local function pickle (x) if type (x) == "table" then local s, sep = "{", "" for i, v in pairs (x) do - s = s .. sep .. "[" .. pickle (i) .. "]=" .. pickle (v) + s = s .. sep .. "[" .. M.pickle (i) .. "]=" .. M.pickle (v) sep = "," end s = s .. "}" @@ -691,37 +657,6 @@ local function pickle (x) end ---- @export -M = { - __concat = __concat, - __index = __index, - assert = assert, - caps = caps, - chomp = chomp, - escape_pattern = escape_pattern, - escape_shell = escape_shell, - finds = finds, - format = format, - ltrim = ltrim, - monkey_patch = monkey_patch, - numbertosi = numbertosi, - ordinal_suffix = ordinal_suffix, - pad = pad, - pickle = pickle, - prettytostring = prettytostring, - render = render, - require = require, - rtrim = rtrim, - split = split, - tfind = tfind, - tostring = tostring, - trim = trim, - wrap = wrap, -} - --- Deprecated and undocumented. -M.require_version = require_version - for k, v in pairs (string) do M[k] = M[k] or v end diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index aca9309..28f146b 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -1,4 +1,4 @@ -before: | +before: base_module = "string" this_module = "std.string" global_table = "_G" @@ -59,10 +59,13 @@ specify std.string: - describe assert: - before: - f = M.assert - - it diagnoses wrong argument types: | - expect (f (false, false)). - to_error "bad argument #2 to 'std.string.assert' (string or nil expected, got boolean)" + fname = "assert" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses wrong argument types: + expect (f (false, false)).to_error (msg (2, "string or nil", "boolean")) + - context when it does not trigger: - it has a truthy initial argument: expect (f (1)).not_to_error () @@ -88,13 +91,17 @@ specify std.string: - describe caps: - before: - f = M.caps - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.caps' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.caps' (string expected, got boolean)" + fname = "caps" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it capitalises words of a string: target = "A String \n\n" expect (f (subject)).to_be (target) @@ -110,14 +117,19 @@ specify std.string: - describe chomp: - before: - f = M.chomp target = "a string \n" - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.chomp' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.chomp' (string expected, got boolean)" + + fname = "chomp" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - 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: @@ -132,21 +144,24 @@ specify std.string: - describe escape_pattern: - - before: | - f = M.escape_pattern - + - before: magic = {} meta = "^$()%.[]*+-?" for i = 1, string.len (meta) do magic[meta:sub (i, i)] = true end - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.escape_pattern' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.escape_pattern' (string expected, got boolean)" + fname = "escape_pattern" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - context with each printable ASCII char: - before: subject, target = "", "" @@ -168,13 +183,17 @@ specify std.string: - describe escape_shell: - before: - f = M.escape_shell - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.escape_shell' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.escape_shell' (string expected, got boolean)" + fname = "escape_shell" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - context with each printable ASCII char: - before: subject, target = "", "" @@ -200,21 +219,25 @@ specify std.string: - describe finds: - before: subject = "abcd" - f = M.finds - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.finds' (string expected, got no value)" - expect (f ("string")). - to_error "bad argument #2 to 'std.string.finds' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.finds' (string expected, got boolean)" - expect (f ("string", false)). - to_error "bad argument #2 to 'std.string.finds' (string expected, got boolean)" + + fname = "finds" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f ("string")).to_error (msg (2, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("string", false)).to_error (msg (2, "string", "boolean")) expect (f ("string", "pattern", false)). - to_error "bad argument #3 to 'std.string.finds' (int or nil expected, got boolean)" + to_error (msg (3, "int or nil", "boolean")) expect (f ("string", "pattern", nil, "plain")). - to_error "bad argument #4 to 'std.string.finds' (boolean, :plain or nil expected, got string)" + to_error (msg (4, "boolean, :plain or nil", "string")) + - it diagnoses too many arguments: + expect (f ("string", "pattern", nil, false, nop)). + to_error (toomanyarg (this_module, fname, 4, 5)) + - context given a complex nested list: - before: target = { { 1, 2; capt = { "a", "b" } }, { 3, 4; capt = { "c", "d" } } } @@ -237,17 +260,19 @@ specify std.string: expect (subject).to_be (original) -# FIXME: This looks like a misfeature to me, let's remove it! - describe format: - - before: | - subject = "string: %s, number: %d" - f = M.format - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.format' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.format' (string expected, got boolean)" + - before: + subject = "string=%s, number=%d" + + fname = "format" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it returns a single argument without attempting formatting: expect (f (subject)).to_be (subject) - it is available as a string metamethod: @@ -261,13 +286,21 @@ specify std.string: - describe ltrim: - before: subject = " \t\r\n a short string \t\r\n " - f = M.ltrim - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.ltrim' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.ltrim' (string expected, got boolean)" + + fname = "ltrim" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("string", false)). + to_error (msg (2, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", "pattern", false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - it removes whitespace from the start of a string: target = "a short string \t\r\n " expect (f (subject)).to_equal (target) @@ -285,12 +318,18 @@ specify std.string: - describe monkey_patch: - before: - f = M.monkey_patch + fname = "monkey_patch" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + t = {} f (t) - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.monkey_patch' (table or nil expected, got boolean)" + + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (t, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it installs concat metamethod: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" @@ -309,13 +348,17 @@ specify std.string: - describe numbertosi: - before: - f = M.numbertosi - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.numbertosi' (number or string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.numbertosi' (number or string expected, got boolean)" + fname = "numbertosi" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "number or string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "number or string", "boolean")) + - it diagnoses too many arguments: + expect (f (1, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - 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"} @@ -331,13 +374,17 @@ specify std.string: - describe ordinal_suffix: - before: - f = M.ordinal_suffix - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.ordinal_suffix' (int or string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.ordinal_suffix' (int or string expected, got boolean)" + fname = "ordinal_suffix" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "int or string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "int or string", "boolean")) + - it diagnoses too many arguments: + expect (f (1, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it returns the English suffix for a number: subject, target = {}, {} for n = -120, 120 do @@ -358,20 +405,22 @@ specify std.string: - describe pad: - before: width = 20 - f = M.pad - - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.pad' (string expected, got no value)" - expect (f ("string")). - to_error "bad argument #2 to 'std.string.pad' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.pad' (string expected, got boolean)" - expect (f ("string", false)). - to_error "bad argument #2 to 'std.string.pad' (int expected, got boolean)" + + fname = "pad" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f ("string")).to_error (msg (2, "int")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("string", false)).to_error (msg (2, "int", "boolean")) expect (f ("string", 42, false)). - to_error "bad argument #3 to 'std.string.pad' (string or nil expected, got boolean)" + to_error (msg (3, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", 42, "\t", false)). + to_error (toomanyarg (this_module, fname, 3, 4)) - context when string is shorter than given width: - before: @@ -436,12 +485,18 @@ specify std.string: - describe prettytostring: - before: - f = M.prettytostring - - it diagnoses wrong argument types: | - expect (f (false, false)). - to_error "bad argument #2 to 'std.string.prettytostring' (string or nil expected, got boolean)" - expect (f (false, "string", false)). - to_error "bad argument #3 to 'std.string.prettytostring' (string or nil expected, got boolean)" + fname = "prettytostring" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses wrong argument types: + expect (f (true, false)).to_error (msg (2, "string or nil", "boolean")) + expect (f (true, "indent", false)). + to_error (msg (3, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (true, "indent", "spacing", false)). + to_error (toomanyarg (this_module, fname, 3, 4)) + - it renders nil exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) - it renders booleans exactly like system tostring: @@ -481,69 +536,73 @@ specify std.string: pair = function (_, _, _, i, v) return i .. "=" .. v end sep = function (_, i, _, j) return (i and j) and "," or "" end t = {1, {{2, 3}, 4, {5}}} - f = function (x) + r = function (x) return M.render (x, term "{", term "}", tostring, pair, sep) end - - it diagnoses missing arguments: | - expect (M.render (nil)). - to_error "bad argument #2 to 'std.string.render' (function expected, got no value)" - expect (M.render (nil, f)). - to_error "bad argument #3 to 'std.string.render' (function expected, got no value)" - expect (M.render (nil, f, f)). - to_error "bad argument #4 to 'std.string.render' (function expected, got no value)" - expect (M.render (nil, f, f, f)). - to_error "bad argument #5 to 'std.string.render' (function expected, got no value)" - expect (M.render (nil, f, f, f, f)). - to_error "bad argument #6 to 'std.string.render' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (M.render (nil, false)). - to_error "bad argument #2 to 'std.string.render' (function expected, got boolean)" - expect (M.render (nil, f, false)). - to_error "bad argument #3 to 'std.string.render' (function expected, got boolean)" - expect (M.render (nil, f, f, false)). - to_error "bad argument #4 to 'std.string.render' (function expected, got boolean)" - expect (M.render (nil, f, f, f, false)). - to_error "bad argument #5 to 'std.string.render' (function expected, got boolean)" - expect (M.render (nil, f, f, f, f, false)). - to_error "bad argument #6 to 'std.string.render' (function expected, got boolean)" - expect (M.render (nil, f, f, f, f, f, false)). - to_error "bad argument #7 to 'std.string.render' (table or nil expected, got boolean)" + + fname = "render" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f (true)).to_error (msg (2, "function")) + expect (f (true, f)).to_error (msg (3, "function")) + expect (f (true, f, f)).to_error (msg (4, "function")) + expect (f (true, f, f, f)).to_error (msg (5, "function")) + expect (f (true, f, f, f, f)).to_error (msg (6, "function")) + - it diagnoses wrong argument types: + expect (f (true, false)).to_error (msg (2, "function", "boolean")) + expect (f (true, f, false)).to_error (msg (3, "function", "boolean")) + expect (f (true, f, f, false)).to_error (msg (4, "function", "boolean")) + expect (f (true, f, f, f, false)).to_error (msg (5, "function", "boolean")) + expect (f (true, f, f, f, f, false)). + to_error (msg (6, "function", "boolean")) + expect (f (true, f, f, f, f, f, false)). + to_error (msg (7, "table or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (true, f, f, f, f, f, {}, false)). + to_error (toomanyarg (this_module, fname, 7, 8)) + - it converts a primitive to a representative string: - expect (f (nil)).to_be "nil" - expect (f (false)).to_be "false" - expect (f (42)).to_be "42" - expect (f ("string")).to_be "string" + expect (r (nil)).to_be "nil" + expect (r (false)).to_be "false" + expect (r (42)).to_be "42" + expect (r ("string")).to_be "string" - it converts a table to a representative string: - expect (f ({"table", 42})). + expect (r ({"table", 42})). to_be '{1=table,2=42}' - it converts a nested table to a representative string: - expect (f (t)). + expect (r (t)). to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}" - it converts a recursive table to a representative string: t[1] = t - expect (f (t)). + expect (r (t)). to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") - it converts an Array to a representative string: a = require "std.array" {"a", "b", {{"c"},"d"},"e"} - expect (f (a)). + expect (r (a)). to_be ('Array ("any", {a, b, ' .. tostring (a[3]) .. ', e})') - describe require: - before: - f = M.require - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.require' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.require' (string expected, got boolean)" - expect (f ("string", false)). - to_error "bad argument #2 to 'std.string.require' (string or nil expected, got boolean)" - expect (f ("string", "string", false)). - to_error "bad argument #3 to 'std.string.require' (string or nil expected, got boolean)" - expect (f ("string", "string", "string", false)). - to_error "bad argument #4 to 'std.string.require' (string or nil expected, got boolean)" + fname = "require" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("module", false)).to_error (msg (2, "string or nil", "boolean")) + expect (f ("module", "min", false)). + to_error (msg (3, "string or nil", "boolean")) + expect (f ("module", "min", "too_big", false)). + to_error (msg (4, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("module", "min", "too_big", "pattern", false)). + to_error (toomanyarg (this_module, fname, 4, 5)) + - it diagnoses non-existent module: expect (f ("module-not-exists", "", "")).to_error "module-not-exists" - it diagnoses module too old: @@ -569,18 +628,18 @@ specify std.string: std.version = "1.2.3" - after: std.version = ver - - it diagnoses module too old: | + - it diagnoses module too old: expect (f ("std", "1.2.4")).to_error () expect (f ("std", "1.3")).to_error () expect (f ("std", "2.1.2")).to_error () expect (f ("std", "2")).to_error () expect (f ("std", "1.2.10")).to_error () - - it diagnoses module too new: | + - it diagnoses module too new: expect (f ("std", nil, "1.2.2")).to_error () expect (f ("std", nil, "1.1")).to_error () expect (f ("std", nil, "1.1.2")).to_error () expect (f ("std", nil, "1")).to_error () - - it returns modules with version in range: | + - 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) @@ -593,7 +652,10 @@ specify std.string: # DEPRECATED: Remove in first release following 2015-06-30. - describe require_version: - before: - f = M.require_version + fname = "require_version" + msg = bind (badarg, {this_module, "require"}) + f = M[fname] + - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {"std.string"}) if err ~= nil then @@ -602,18 +664,20 @@ specify std.string: end _, err = capture (f, {"std.string"}) expect (err).to_be (nil) - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.require' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.require' (string expected, got boolean)" - expect (f ("string", false)). - to_error "bad argument #2 to 'std.string.require' (string or nil expected, got boolean)" - expect (f ("string", "string", false)). - to_error "bad argument #3 to 'std.string.require' (string or nil expected, got boolean)" - expect (f ("string", "string", "string", false)). - to_error "bad argument #4 to 'std.string.require' (string or nil expected, got boolean)" + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("module", false)).to_error (msg (2, "string or nil", "boolean")) + expect (f ("module", "min", false)). + to_error (msg (3, "string or nil", "boolean")) + expect (f ("module", "min", "too_big", false)). + to_error (msg (4, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("module", "min", "too_big", "pattern", false)). + to_error (toomanyarg (this_module, "require", 4, 5)) + - it diagnoses non-existent module: expect (f ("module-not-exists", "", "")).to_error "module-not-exists" - it diagnoses module too old: @@ -639,18 +703,18 @@ specify std.string: std.version = "1.2.3" - after: std.version = ver - - it diagnoses module too old: | + - it diagnoses module too old: expect (f ("std", "1.2.4")).to_error () expect (f ("std", "1.3")).to_error () expect (f ("std", "2.1.2")).to_error () expect (f ("std", "2")).to_error () expect (f ("std", "1.2.10")).to_error () - - it diagnoses module too new: | + - it diagnoses module too new: expect (f ("std", nil, "1.2.2")).to_error () expect (f ("std", nil, "1.1")).to_error () expect (f ("std", nil, "1.1.2")).to_error () expect (f ("std", nil, "1")).to_error () - - it returns modules with version in range: | + - 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) @@ -663,13 +727,20 @@ specify std.string: - describe rtrim: - before: subject = " \t\r\n a short string \t\r\n " - f = M.rtrim - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.rtrim' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.rtrim' (string expected, got boolean)" + + fname = "rtrim" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("string", false)).to_error (msg (2, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", "pattern", false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - it removes whitespace from the end of a string: target = " \t\r\n a short string" expect (f (subject)).to_equal (target) @@ -689,15 +760,21 @@ specify std.string: - before: target = { "first", "the second one", "final entry" } subject = table.concat (target, ", ") - f = M.split - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.split' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.split' (string expected, got boolean)" + + fname = "split" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) expect (f ("string", false)). - to_error "bad argument #2 to 'std.string.split' (string or nil expected, got boolean)" + to_error (msg (2, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", "pattern", false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - it falls back to "%s+" when no pattern is given: expect (f (subject)). to_equal {"first,", "the", "second", "one,", "final", "entry"} @@ -735,21 +812,25 @@ specify std.string: - describe tfind: - before: subject = "abc" - f = M.tfind - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.tfind' (string expected, got no value)" - expect (f ("string")). - to_error "bad argument #2 to 'std.string.tfind' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.tfind' (string expected, got boolean)" - expect (f ("string", false)). - to_error "bad argument #2 to 'std.string.tfind' (string expected, got boolean)" + + fname = "tfind" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f ("string")).to_error (msg (2, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("string", false)).to_error (msg (2, "string", "boolean")) expect (f ("string", "pattern", false)). - to_error "bad argument #3 to 'std.string.tfind' (int or nil expected, got boolean)" + to_error (msg (3, "int or nil", "boolean")) expect (f ("string", "pattern", 1, "plain")). - to_error "bad argument #4 to 'std.string.tfind' (boolean, :plain or nil expected, got string)" + to_error (msg (4, "boolean, :plain or nil", "string")) + - it diagnoses too many arguments: + expect (f ("string", "pattern", 1, true, false)). + to_error (toomanyarg (this_module, fname, 4, 5)) + - it creates a list of pattern captures: target = { 1, 3, { "a", "b", "c" } } expect ({f (subject, "(.)(.)(.)")}).to_equal (target) @@ -799,13 +880,21 @@ specify std.string: - describe trim: - before: subject = " \t\r\n a short string \t\r\n " - f = M.trim - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.trim' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.trim' (string expected, got boolean)" + + fname = "trim" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("string", false)). + to_error (msg (2, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", "pattern", false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - it removes whitespace from each end of a string: target = "a short string" expect (f (subject)).to_equal (target) @@ -828,19 +917,24 @@ specify std.string: "-2014 (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 - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.string.wrap' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.string.wrap' (string expected, got boolean)" - expect (f ("string", false)). - to_error "bad argument #2 to 'std.string.wrap' (int or nil expected, got boolean)" + + fname = "wrap" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("string", false)).to_error (msg (2, "int or nil", "boolean")) expect (f ("string", 72, false)). - to_error "bad argument #3 to 'std.string.wrap' (int or nil expected, got boolean)" + to_error (msg (3, "int or nil", "boolean")) expect (f ("string", 72, 4, false)). - to_error "bad argument #4 to 'std.string.wrap' (int or nil expected, got boolean)" + to_error (msg (4, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("string", 72, 4, 8, false)). + to_error (toomanyarg (this_module, fname, 4, 5)) + - 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" .. From 7c75e745a009414d8f8579a477480ee7cba27533 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 2 Jul 2014 16:28:50 +0100 Subject: [PATCH 259/703] debug: support nil arguments in functions declared with export. * lib/std/base.lua (opairs): Like ipairs, but does not stop at the first nil value. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index d192bdb..921556d 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -91,12 +91,32 @@ local function normalize (t) end +--- Ordered iterator for integer keyed values. +-- Like ipairs, but does not stop at the first nil value. +-- @tparam table t a table +-- @treturn function iterator function +-- @treturn table t +-- @usage +-- for i,v in opairs {"one", nil, "three"} do print (i, v) end +local function opairs (t) + local i, max = 0, 0 + for k in pairs (t) do + if type (k) == "number" and k > max then max = k end + end + return function (t) + i = i + 1 + if i <= max then return i, t[i] end + end, + t, true +end + + --- Merge |-delimited type-specs, omitting duplicates. -- @string ... type-specs -- @treturn table list of merged and normalized type-specs local function merge (...) local i, t = 1, {} - for _, v in ipairs {...} do + for _, v in opairs {...} do v:gsub ("([^|]+)", function (m) t[i] = m; i = i + 1 end) end return normalize (t) From 441b30f08c1318ea6608c2f18d2b275441c2b598 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 2 Jul 2014 17:08:24 +0100 Subject: [PATCH 260/703] debug: finish support for nil arguments in exported functions. Because luajit (legitimately) stops counting on the first nil value in a list, where Lua 5.1 and 5.2 keep counting, we have to do our own size calculations on argument vectors to be consistent. * lib/std/base.lua (olen): Return the largest integer key from a table. (match, export): Use it to ignore `nil` elements in the argument list when counting the number of arguments. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 921556d..32ff0c3 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -91,6 +91,20 @@ local function normalize (t) end +--- Ordered length. +-- Like #table, but does not stop at the first nil value. +-- @tparam table t a table +-- @treturn int largest integer key in *t* +-- @usage tmax = olen (t) +local function olen (t) + local len = 0 + for k in pairs (t) do + if type (k) == "number" and k > len then len = k end + end + return len +end + + --- Ordered iterator for integer keyed values. -- Like ipairs, but does not stop at the first nil value. -- @tparam table t a table @@ -175,7 +189,7 @@ end -- @tparam table args a table of arguments to compare -- @treturn int|nil position of first mismatch in *types* local function match (types, args, allargs) - local typec, argc = #types, #args + local typec, argc = #types, olen (args) for i = 1, typec do local ok = pcall (argcheck, "pcall", i, types[i], args[i]) if not ok then return i end @@ -434,8 +448,8 @@ local function export (M, decl, fn, ...) else name = decl:match "([%w_][%d%w_]*)" end - if #args > 3 then - error (string.format (toomanyarg_fmt, fname, 3, #args), 2) + if olen (args) > 3 then + error (string.format (toomanyarg_fmt, fname, 3, olen (args)), 2) elseif type (M[1]) ~= "string" then argerror (fname, 1, "module name at index 1 expected, got no value") elseif name == nil then @@ -464,7 +478,7 @@ local function export (M, decl, fn, ...) fn = function (...) local args = {...} - local argc, bestmismatch, at = #args, 0, 0 + local argc, bestmismatch, at = olen (args), 0, 0 for i, types in ipairs (type_specs) do local mismatch = match (types, args, max == math.huge) From b836ea3520b1d146a5b3d6ebb0eec1f385d5e175 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 2 Jul 2014 17:26:00 +0100 Subject: [PATCH 261/703] maint: don't flag `Lua` strings as invalid errors in spec-files. * build-aux/sanity-cfg.mk (exclude_file_name_regexp): Add specs/debug_spec.yaml. Signed-off-by: Gary V. Vaughan --- build-aux/sanity-cfg.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-aux/sanity-cfg.mk b/build-aux/sanity-cfg.mk index 6bbf697..893fead 100644 --- a/build-aux/sanity-cfg.mk +++ b/build-aux/sanity-cfg.mk @@ -1,3 +1,3 @@ -exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua$$ +exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua|specs/debug_spec.yaml$$ EXTRA_DIST += build-aux/sanity-cfg.mk From 1462751c9bdb9787b6308c3163ab4a5c812b9ead Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 3 Jul 2014 14:06:39 +0100 Subject: [PATCH 262/703] maint: settle on calling std api calls `Module Functions`. * lib/std/debug.lua, lib/std/io.lua, lib/std/package.lua, lib/std/table.lua, lib/std/tree.lua: Consolidate comment section header as "Module Functions.". Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 6 +++--- lib/std/io.lua | 6 +++--- lib/std/package.lua | 6 +++--- lib/std/table.lua | 6 +++--- lib/std/tree.lua | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 1fcd69d..f8e276f 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -57,9 +57,9 @@ local tabify = functional.compose ( ---[[ ============== ]]-- ---[[ API Functions. ]]-- ---[[ ============== ]]-- +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- --- Control std.debug function behaviour. diff --git a/lib/std/io.lua b/lib/std/io.lua index 89214e3..0edbe3c 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -44,9 +44,9 @@ end ---[[ ============== ]]-- ---[[ API Functions. ]]-- ---[[ ============== ]]-- +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- --- Slurp a file handle. diff --git a/lib/std/package.lua b/lib/std/package.lua index 0292432..dfad1d5 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -47,9 +47,9 @@ end ---[[ ============== ]]-- ---[[ API Functions. ]]-- ---[[ ============== ]]-- +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- --- Look for a path segment match of `patt` in `pathstrings`. diff --git a/lib/std/table.lua b/lib/std/table.lua index 3f53de6..01defac 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -74,9 +74,9 @@ end ---[[ ============== ]]-- ---[[ API Functions. ]]-- ---[[ ============== ]]-- +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- --- Make a shallow copy of a table, including any metatable. diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 8840a66..2e1b7cb 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -55,9 +55,9 @@ end ---[[ ============== ]]-- ---[[ API Functions. ]]-- ---[[ ============== ]]-- +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- --- Tree iterator which returns just numbered leaves, in order. From f08ec8f003d5cc65d7a034a7f59277db14dc2c80 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 3 Jul 2014 16:52:55 +0100 Subject: [PATCH 263/703] refactor: use a function to export table apis. * specs/table_spec.yaml (clone, clone_select, elems, empty) (ielems, invert, keys, merge, merge_select, metamethod) (monkey_patch, new, pack, ripairs, size, totable, values): Update to latest style: Add "too many argument" behaviour checks. Simplify and standardise argument error message comparisons. * lib/std/table.lua (M): Add module name at element 1. (clone, clone_select, elems, empty, ielems, invert, keys) (merge, merge_select, metamethod, monkey_patch, new, pack) (ripairs, size, totable, values): Upgrade to base export declarations (for overhead free argcheck calls with _DEBUG=false) and simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 291 ++++++++++++++---------------------------- specs/table_spec.yaml | 69 ++++++---- 2 files changed, 141 insertions(+), 219 deletions(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index 01defac..c140daa 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -1,7 +1,7 @@ --[[-- Extensions to the core table module. - The module table returned by `std.io` also contains all of the entries from + 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: @@ -11,15 +11,13 @@ ]] -local _ARGCHECK = require "std.debug_init"._ARGCHECK - local base = require "std.base" -local argcheck, argscheck, getmetamethod, base_ielems = - base.argcheck, base.argscheck, base.getmetamethod, base.ielems +local export, getmetamethod, ielems = + base.export, base.getmetamethod, base.ielems -local M -- forward declaration +local M = { "std.table" } @@ -32,8 +30,8 @@ local M -- forward declaration -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` --- @tparam boolean nometa if non-nil don't copy metatable --- @return table *t* with fields from *u* merged in +-- @param nometa if non-nil don't copy metatable +-- @treturn table *t* with fields from *u* merged in local function merge_allfields (t, u, map, nometa) map = map or {} if type (map) ~= "table" then @@ -54,8 +52,8 @@ end -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table keys list of keys to copy --- @tparam boolean nometa if non-nil don't copy metatable --- @return copy of fields in *selection* from *t*, also sharing *t*'s +-- @param nometa if non-nil don't copy metatable +-- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s -- metatable unless *nometa* local function merge_namedfields (t, u, keys, nometa) keys = keys or {} @@ -66,7 +64,7 @@ local function merge_namedfields (t, u, keys, nometa) if not nometa then setmetatable (t, getmetatable (u)) end - for k in base_ielems (keys) do + for k in ielems (keys) do t[k] = u[k] end return t @@ -81,27 +79,19 @@ end --- Make a shallow copy of a table, including any metatable. -- --- To make deep copies, use @{std.tree.clone}. +-- To make deep copies, use @{tree.clone}. +-- @function clone -- @tparam table t source table -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` --- @tparam[opt] boolean nometa if non-nil don't copy metatable +-- @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 std.table.merge --- @see std.table.clone_select +-- @see merge +-- @see clone_select -- @usage -- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") -local function clone (t, map, nometa) - if _ARGCHECK then - local types = {"table", "table", "boolean|:nometa?"} - if type (map) ~= "table" then - types = {"table", "table|boolean|:nometa?"} - end - argscheck ("std.table.clone", types, {t, map, nometa}) - end - - return merge_allfields ({}, t, map, nometa) -end +local clone = export (M, "clone (table, [table], boolean|:nometa?)", + function (...) return merge_allfields ({}, ...) end) -- DEPRECATED: Remove in first release following 2015-04-15. @@ -109,15 +99,15 @@ end -- @function clone_rename -- @tparam table map table `{old_key=new_key, ...}` -- @tparam table t source table --- @return copy of *table* -local clone_rename = base.deprecate (function (map, t) - local r = clone (t) - for i, v in pairs (map) do - r[v] = t[i] - r[i] = nil - end - return r - end, nil, +-- @treturn table copy of *t* +M.clone_rename = base.deprecate (function (map, t) + local r = clone (t) + for i, v in pairs (map) do + r[v] = t[i] + r[i] = nil + end + return r + end, nil, "table.clone_rename is deprecated, use the new `map` argument to table.clone instead.") @@ -127,35 +117,25 @@ local clone_rename = base.deprecate (function (map, t) -- @function clone_select -- @tparam table t source table -- @tparam[opt={}] table keys list of keys to copy --- @tparam[opt] boolean nometa if non-nil don't copy metatable --- @return copy of fields in *selection* from *t*, also sharing *t*'s +-- @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 std.table.clone --- @see std.table.merge_select +-- @see clone +-- @see merge_select -- @usage -- partialcopy = clone_select (original, {"this", "and_this"}, true) -local function clone_select (t, keys, nometa) - if _ARGCHECK then - local types = {"table", "table", "boolean|:nometa?"} - if type (keys) ~= "table" then - types = {"table", "table|boolean|:nometa?"} - end - argscheck ("std.table.clone_select", types, {t, keys, nometa}) - end - - return merge_namedfields ({}, t, keys, nometa) -end +export (M, "clone_select (table, [table], boolean|:nometa?)", + function (...) return merge_namedfields ({}, ...) end) --- An iterator over all values of a table. +-- @function elems -- @tparam table t a table -- @treturn function iterator function --- @treturn *t* --- @return `true` +-- @treturn table *t* +-- @treturn boolean `true` -- @usage for func in elems (_G) do ... end -local function elems (t) - argcheck ("std.table.elems", 1, "table", t) - +export (M, "elems (table)", function (t) local k, v = nil return function (t) k, v = next (t, k) @@ -164,169 +144,127 @@ local function elems (t) end end, t, true -end +end) --- Return whether table is empty. +-- @function empty -- @tparam table t any table --- @return `true` if *t* is empty, otherwise `false` +-- @treturn boolean `true` if *t* is empty, otherwise `false` -- @usage if empty (t) then error "ohnoes" end -local function empty (t) - argcheck ("std.table.empty", 1, "table", t) - +export (M, "empty (table)", function (t) return not next (t) -end +end) --- An iterator over the integer keyed elements of a table. +-- @function ielems -- @tparam table t a table -- @treturn function iterator function --- @treturn *t* --- @return `true` +-- @treturn table *t* +-- @treturn boolean `true` -- @usage for value in ielems {"a", "b", "c"} do ... end -local function ielems (t) - argcheck ("std.table.ielems", 1, "table", t) - - return base_ielems (t) -end +export (M, "ielems (table)", ielems) --- Invert a table. +-- @function invert -- @tparam table t a table with `{k=v, ...}` -- @treturn table inverted table `{v=k, ...}` -- @usage values = invert (t) -local function invert (t) - argcheck ("std.table.invert", 1, "table", t) - +export (M, "invert (table)", function (t) local i = {} for k, v in pairs (t) do i[v] = k end return i -end +end) --- Make the list of keys in table. --- @tparam table t any table --- @treturn table list of keys --- @see std.table.values +-- @function keys +-- @tparam table t a table +-- @treturn table list of keys from *t* +-- @see values -- @usage globals = keys (_G) -local function keys (t) - argcheck ("std.table.keys", 1, "table", t) - +export (M, "keys (table)", function (t) local l = {} for k, _ in pairs (t) do l[#l + 1] = k end return l -end +end) --- Destructively merge another 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, ...}` --- @tparam[opt] boolean nometa if non-nil don't copy metatable --- @return table *t* with fields from *u* merged in --- @see std.table.clone --- @see std.table.merge_select +-- @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") -local function merge (t, u, map, nometa) - if _ARGCHECK then - local types = {"table", "table", "table", "boolean|:nometa?"} - if type (map) ~= "table" then - types = {"table", "table", "table|boolean|:nometa?"} - end - argscheck ("std.table.merge", types, {t, u, map, nometa}) - end - - return merge_allfields (t, u, map, nometa) -end +export (M, "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 --- @tparam[opt] boolean nometa if non-nil don't copy metatable --- @return copy of fields in *selection* from *t*, also sharing *t*'s +-- @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 std.table.merge --- @see std.table.clone_select +-- @see merge +-- @see clone_select -- @usage merge_select (_G, require "std.debug", {"say"}, false) -local function merge_select (t, u, keys, nometa) - if _ARGCHECK then - local types = {"table", "table", "table", "boolean|:nometa?"} - if type (keys) ~= "table" then - types = {"table", "table", "table|boolean|:nometa?"} - end - argscheck ("std.table.merge_select", types, {t, u, keys, nometa}) - end - - return merge_namedfields (t, u, keys, nometa) -end +export (M, "merge_select (table, table, [table], boolean|:nometa?)", + merge_namedfields) --- Return given metamethod, if any, or nil. -- @function metamethod --- @tparam std.object x object to get metamethod of +-- @tparam object x object to get metamethod of -- @string n name of metamethod to get --- @treturn function|nil metamethod function or `nil` if no metamethod or --- not a function +-- @treturn function|nil metamethod function, or `nil` if no metamethod -- @usage lookup = metamethod (require "std.object", "__index") -local metamethod - -if _ARGCHECK then - - metamethod = function (x, n) - argscheck ("std.table.metamethod", {"object|table", "string"}, {x, n}) - - return getmetamethod (x, n) - end - -else - - -- Save a stack frame and a comparison on each call when not checking - -- arguments. - metamethod = getmetamethod - -end +export (M, "metamethod (object|table, string)", getmetamethod) --- 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 +-- @treturn table table whose unset elements are *x* -- @usage t = new (0) -local function new (x, t) - argcheck ("std.table.new", 2, "table?", t) - +export (M, "new (any?, table?)", function (x, t) return setmetatable (t or {}, {__index = function (t, i) return x end}) -end +end) --- Turn a tuple into a list. -- @param ... tuple -- @return list -local function pack (...) +function M.pack (...) return {...} end --- An iterator like ipairs, but in reverse. +-- @function ripairs -- @tparam table t any table -- @treturn function iterator function --- @treturn table the table, *t* +-- @treturn table *t* -- @treturn number `#t + 1` -- @usage for i, v = ripairs (t) do ... end -local function ripairs (t) - argcheck ("std.table.ripairs", 1, "table", t) - +export (M, "ripairs (table)", function (t) return function (t, n) n = n - 1 if n > 0 then @@ -334,63 +272,58 @@ local function ripairs (t) end end, t, #t + 1 -end +end) --- Find the number of elements in a table. +-- @function size -- @tparam table t any table --- @return number of non-nil values in *t* +-- @treturn int number of non-nil values in *t* -- @usage count = size {foo = true, bar = true, baz = false} -local function size (t) - argcheck ("std.table.size", 1, "table", t) - +export (M, "size (table)", function (t) local n = 0 for _ in pairs (t) do n = n + 1 end return n -end +end) -- Preserve core table sort function. local _sort = table.sort --- Make table.sort return its result. +-- @function sort -- @tparam table t unsorted table --- @tparam[opt] function c comparator function if passed, otherwise standard +-- @func[opt] c comparator function if passed, otherwise standard -- lua `<` operator -- @return *t* with keys sorted accordind to *c* -- @usage table.concat (sort (object)) -local function sort (t, c) - argscheck ("std.table.sort", {"table", "function"}, {t, c}) - +export (M, "sort (table, function?)", function (t, c) _sort (t, c) return t -end +end) --- Overwrite core methods with `std` enhanced versions. -- -- Replaces core `table.sort` with `std.table` version. +-- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local table = require "std.table".monkey_patch () -local function monkey_patch (namespace) - argcheck ("std.table.monkey_patch", 1, "table?", namespace) - namespace = namespace or _G - - namespace.table.sort = sort +export (M, "monkey_patch (table?)", function (namespace) + namespace.table.sort = M.sort return M -end +end) --- Turn an object into a table according to `__totable` metamethod. --- @tparam std.object x object to turn into a table +-- @function totable +-- @tparam object|table|string x object to turn into a table -- @treturn table resulting table or `nil` -- @usage print (table.concat (totable (object))) -local function totable (x) - argcheck ("std.table.totable", 1, "object|table|string", x) - +export (M, "totable (object|table|string)", function (x) local m = getmetamethod (x, "__totable") if m then return m (x) @@ -403,48 +336,22 @@ local function totable (x) else return nil end -end +end) --- Make the list of values of a table. +-- @function values -- @tparam table t any table --- @treturn table list of values --- @see std.table.keys -local function values (t) - argcheck ("std.table.values", 1, "table", t) - +-- @treturn table list of values in *t* +-- @see keys +export (M, "values (table)", function (t) local l = {} for _, v in pairs (t) do l[#l + 1] = v end return l -end - +end) ---- @export -M = { - clone = clone, - clone_select = clone_select, - elems = elems, - empty = empty, - ielems = ielems, - invert = invert, - keys = keys, - merge = merge, - merge_select = merge_select, - metamethod = metamethod, - monkey_patch = monkey_patch, - new = new, - pack = pack, - ripairs = ripairs, - size = size, - sort = sort, - totable = totable, - values = values, -} - --- Deprecated and undocumented. -M.clone_rename = clone_rename for k, v in pairs (table) do M[k] = M[k] or v diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 254bd19..e0b8a4a 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -35,17 +35,23 @@ specify std.table: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) - f = M.clone - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.clone' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.clone' (table expected, got boolean)" + + fname = "clone" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) expect (f ({}, "nometa")). - to_error "bad argument #2 to 'std.table.clone' (table, boolean, :nometa or nil expected, got string)" + to_error (msg (2, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, "nometa")). - to_error "bad argument #3 to 'std.table.clone' (boolean, :nometa or nil expected, got string)" + to_error (msg (3, "boolean, :nometa or nil", "string")) + - it diagnoses too many arguments: + expect (f ({}, {}, nil, false)). + to_error (toomanyarg (this_module, fname, 3, 4)) + - it does not just return the subject: expect (f (subject)).not_to_be (subject) - it does copy the subject: @@ -79,7 +85,8 @@ specify std.table: - describe clone_rename: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } - f = M.clone_rename + f = M.clone_rename + - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {{}, subject}) if err ~= nil then @@ -88,6 +95,7 @@ specify std.table: end _, err = capture (f, {{}, subject}) expect (err).to_be (nil) + - it copies the subject: expect (f ({}, subject)).to_copy (subject) - it only makes a shallow copy: @@ -110,18 +118,23 @@ specify std.table: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) - f = M.clone_select - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.clone_select' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.clone_select' (table expected, got boolean)" + fname = "clone_select" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) expect (f ({}, "nometa")). - to_error "bad argument #2 to 'std.table.clone_select' (table, boolean, :nometa or nil expected, got string)" + to_error (msg (2, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, "nometa")). - to_error "bad argument #3 to 'std.table.clone_select' (boolean, :nometa or nil expected, got string)" + to_error (msg (3, "boolean, :nometa or nil", "string")) + - it diagnoses too many arguments: + expect (f ({}, {}, nil, false)). + to_error (toomanyarg (this_module, fname, 3, 4)) + - it does not just return the subject: expect (f (subject)).not_to_be (subject) - it copies the keys selected: @@ -149,14 +162,16 @@ specify std.table: - describe elems: - before: - f = M.elems + fname = "elems" + msg = bind (badarg, {this_module, fname}) + f = M[fname] - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.elems' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.elems' (table expected, got boolean)" + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) - it is an iterator over table values: t = {} @@ -523,7 +538,7 @@ specify std.table: expect (f (false)). to_error "bad argument #1 to 'std.table.sort' (table expected, got boolean)" expect (f ({}, false)). - to_error "bad argument #2 to 'std.table.sort' (function expected, got boolean)" + to_error "bad argument #2 to 'std.table.sort' (function or nil expected, got boolean)" - it sorts elements in place: f (subject, cmp) expect (subject).to_equal (target) From a2e725b91664dfaf6782edbe18be7290d4035dd5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 3 Jul 2014 17:32:06 +0100 Subject: [PATCH 264/703] maint: rename array to vector. In mathematics "array" suggests the possibility of multiple dimensions, and while one can simulate that with a std.array of std.arrays, the name "vector" is a better fit for what this class supports. * lib/std/array.lua, specs/array_spec.yaml: Move from here... * lib/std/vector.lua, specs/vector_spec.yaml: ...to here. Rename symbols accordingly. * build-aux/config.ld.in (file): Adjust accordingly. * local.mk (dist_luastd_DATA, dist_classes_DATA): Likewise. * specs/specs.mk (specl_SPECS): Likewise. * specs/string_spec.yaml (render): Adjust Array using example to Vector. Signed-off-by: Gary V. Vaughan --- build-aux/config.ld.in | 2 +- lib/std/{array.lua => vector.lua} | 146 +++--- local.mk | 4 +- specs/array_spec.yaml | 739 ------------------------------ specs/specs.mk | 2 +- specs/string_spec.yaml | 6 +- specs/vector_spec.yaml | 739 ++++++++++++++++++++++++++++++ 7 files changed, 819 insertions(+), 819 deletions(-) rename lib/std/{array.lua => vector.lua} (78%) delete mode 100644 specs/array_spec.yaml create mode 100644 specs/vector_spec.yaml diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index b394643..0fe0a0c 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -7,7 +7,6 @@ dir = "." file = { -- Modules "../lib/std.lua", - "../lib/std/array.lua", "../lib/std/debug.lua", "../lib/std/functional.lua", "../lib/std/io.lua", @@ -25,6 +24,7 @@ file = { "../lib/std/optparse.lua", "../lib/std/set.lua", "../lib/std/strbuf.lua", + "../lib/std/vector.lua", } format = "markdown" diff --git a/lib/std/array.lua b/lib/std/vector.lua similarity index 78% rename from lib/std/array.lua rename to lib/std/vector.lua index 5e50d5f..e1ab107 100644 --- a/lib/std/array.lua +++ b/lib/std/vector.lua @@ -1,33 +1,33 @@ --[[-- - Array of homogenous objects. + Vector of homogenous objects. - An array is usually a block of contiguous memory, divided into equal + A vector is usually a block of contiguous memory, divided into equal sized elements that can be indexed quickly. - Create a new array with: + Create a new vector with: - > array = require "std.array" - > Array = array () - > a = Array ("int", {0xdead, 0xbeef, 0xfeed}) + > vector = require "std.vector" + > Vector = vector () + > a = Vector ("int", {0xdead, 0xbeef, 0xfeed}) > =a[1], a[2], a[3], a[-3], a[-4] 57005 48879 65261 57005 nil - All the indices passed to array methods use 1-based counting. + All the indices passed to vector methods use 1-based counting. If the Lua alien module is installed, and the `type` argument passed - when cloning a new array object is suitable (i.e. the name of a numeric - C type that `alien.sizeof` understands), then the array contents are + when cloning a new vector object is suitable (i.e. the name of a numeric + C type that `alien.sizeof` understands), then the vector contents are managed in an `alien.buffer`. If alien is not installed, or does not understand the `type` argument given when cloning, then a much slower (but API compatible) Lua table is transparently used to manage elements instead. - In either case, `std.array` provides a means for managing collections - of homogenous Lua objects with a vector-like, stack-like or queue-like + In either case, `std.vector` provides a means for managing collections + of homogenous Lua objects with an array-like, stack-like or queue-like API. - @classmod std.array + @classmod std.vector ]] @@ -67,9 +67,9 @@ local function sizeof (type) end ---- Convert an array element index into a pointer. --- @tparam std.array self an array --- @int i[opt=1] an index into array +--- Convert a vector element index into a pointer. +-- @tparam std.vector self a vector +-- @int i[opt=1] an index into vector -- @treturn alien.buffer.pointer suitable for memmove or memset local function topointer (self, i) i = i or 1 @@ -77,8 +77,8 @@ local function topointer (self, i) end ---- Fast zeroing of a contiguous block of array elements for `alien.buffer`s. --- @tparam std.array self an array +--- Fast zeroing of a contiguous block of vector elements for `alien.buffer`s. +-- @tparam std.vector self a vector -- @int from index of first element to zero out -- @int n number of elements to zero out local function setzero (self, from, n) @@ -92,7 +92,7 @@ end --[[ ================== ]]-- --- Initial array prototype object, plus any derived object containing +-- Initial vector prototype object, plus any derived object containing -- elements that don't fit in alien buffers use `core_functions` to -- find object methods and `core_metatable` for metamethods. @@ -103,7 +103,7 @@ local core_functions = { --- Remove the right-most element. -- @function pop -- @return the right-most element - -- @usage removed = anarray:pop () + -- @usage removed = anvector:pop () pop = function (self) self.length = math.max (self.length - 1, 0) return table.remove (self.buffer) @@ -114,7 +114,7 @@ local core_functions = { -- @function push -- @param elem new element to be pushed -- @return elem - -- @usage added = anarray:push (anelement) + -- @usage added = anvector:push (anelement) push = function (self, elem) local length = self.length + 1 self.buffer[length] = elem @@ -126,8 +126,8 @@ local core_functions = { --- Change the number of elements allocated to be at least `n`. -- @function realloc -- @int n the number of elements required - -- @treturn std.array the array - -- @usage anarray = anarray:realloc (anarray.length) + -- @treturn std.vector the vector + -- @usage anvector = anvector:realloc (anvector.length) realloc = function (self, n) argcheck ("realloc", 2, "int", n) @@ -146,10 +146,10 @@ local core_functions = { -- @int from index of first element to set -- @param v value to store -- @int n number of elements to set - -- @treturn std.array the array - -- @usage anarray:realloc (anarray.length):set (1, -1, anarray.length) + -- @treturn std.vector the vector + -- @usage anvector:realloc (anvector.length):set (1, -1, anvector.length) set = function (self, from, v, n) - argscheck ("set", {"Array", "int", "any", "int"}, + argscheck ("set", {"Vector", "int", "any", "int"}, {self, from, v, n}) local length = self.length @@ -164,22 +164,22 @@ local core_functions = { end, - --- Shift the whole array to the left by removing the left-most element. - -- This makes the array 1 element shorter than it was before the shift. + --- Shift the whole vector to the left by removing the left-most element. + -- This makes the vector 1 element shorter than it was before the shift. -- @function shift -- @return the removed element. - -- @usage removed = anarray:shift () + -- @usage removed = anvector:shift () shift = function (self) self.length = math.max (self.length - 1, 0) return table.remove (self.buffer, 1) end, - --- Shift the whole array to the right by inserting a new left-most element. + --- Shift the whole vector to the right by inserting a new left-most element. -- @function unshift -- @param elem new element to be pushed -- @treturn elem - -- @usage added = anarray:unshift (anelement) + -- @usage added = anvector:unshift (anelement) unshift = function (self, elem) self.length = self.length + 1 table.insert (self.buffer, 1, elem) @@ -189,30 +189,30 @@ local core_functions = { core_metatable = { - _type = "Array", + _type = "Vector", - --- Instantiate a newly cloned array. - -- If not specified, `type` will be the same as the prototype array being + --- Instantiate a newly cloned vector. + -- If not specified, `type` will be the same as the prototype vector being -- cloned; otherwise, it can be any string. Only a type name accepted by -- `alien.sizeof` will use the fast `alien.buffer` managed memory buffer - -- for array contents; otherwise, a much slower Lua emulation is used. + -- for vector contents; otherwise, a much slower Lua emulation is used. -- @function __call -- @string type element type name -- @tparam[opt] int|table init initial size or list of initial elements - -- @treturn std.array a new array object + -- @treturn std.vector a new vector object -- @usage - -- local Array = require "std.array" {} -- not a typo! - -- local new = Array ("int", {1, 2, 3}) + -- local Vector = require "std.vector" {} -- not a typo! + -- local new = Vector ("int", {1, 2, 3}) __call = function (self, type, init) if _ARGCHECK then if init ~= nil then -- When called with 2 arguments: - argcheck ("Array", 1, "string", type) - argcheck ("Array", 2, "int|table", init) + argcheck ("Vector", 1, "string", type) + argcheck ("Vector", 2, "int|table", init) elseif type ~= nil then -- When called with 1 argument: - argcheck ("Array", 1, "int|string|table", type) + argcheck ("Vector", 1, "int|string|table", type) end end @@ -222,7 +222,7 @@ core_metatable = { type = type or self.type init = init or self.length - -- This will become the cloned array object. + -- This will become the cloned vector object. local obj = {} for k, v in pairs (self) do @@ -295,10 +295,10 @@ core_metatable = { end, - --- Iterate consecutively over all elements with `ipairs (array)`. + --- Iterate consecutively over all elements with `ipairs (vector)`. -- @function __ipairs -- @treturn function iterator function - -- @usage for index, anelement in ipairs (anarray) do ... end + -- @usage for index, anelement in ipairs (anvector) do ... end __ipairs = function (self) local i, n = 0, self.length return function () @@ -310,11 +310,11 @@ core_metatable = { end, - --- Return the `n`th character in this array. + --- Return the `n`th element in this vector. -- @function __index -- @int n 1-based index, or negative to index starting from the right -- @treturn string the element at index `n` - -- @usage rightmost = anarray[anarray.length] + -- @usage rightmost = anvector[anvector.length] __index = function (self, n) argcheck ("__index", 2, "int|string", n) @@ -329,19 +329,19 @@ core_metatable = { end, - --- Set the `n`th element of this array to `elem`. + --- Set the `n`th element of this vector to `elem`. -- @function __newindex -- @int n 1-based index -- @param elem value to store at index n - -- @treturn std.array the array - -- @usage anarray[1] = newvalue + -- @treturn std.vector the vector + -- @usage anvector[1] = newvalue __newindex = function (self, n, elem) argcheck ("__newindex", 2, "int", n) if typeof (n) == "number" then local used = self.length if n == 0 or math.abs (n) > used then - error ("array access " .. n .. " out of bounds: 0 < abs (n) <= " .. + error ("vector access " .. n .. " out of bounds: 0 < abs (n) <= " .. tostring (self.length), 2) end if n < 0 then n = n + used + 1 end @@ -353,26 +353,26 @@ core_metatable = { end, - --- Return the number of elements in this array. + --- Return the number of elements in this vector. -- -- Beware that Lua 5.1 does not respect this metamethod; use - -- `array.length` if you care about portability. + -- `vector.length` if you care about portability. -- @function __len -- @treturn int number of elements - -- @usage length = #anarray + -- @usage length = #anvector __len = function (self) - argcheck ("__len", 1, "Array", self) + argcheck ("__len", 1, "Vector", self) return self.length end, - --- Return a string representation of the contents of this array. + --- Return a string representation of the contents of this vector. -- @function __tostring -- @treturn string string representation - -- @usage print (anarray) + -- @usage print (anvector) __tostring = function (self) - argcheck ("__tostring", 1, "Array", self) + argcheck ("__tostring", 1, "Vector", self) local t = {} for i = 1, self.length do @@ -390,7 +390,7 @@ core_metatable = { --[[ ===================== ]]-- --- Cloned array objects with elements managed by an alien buffer use +-- Cloned vector objects with elements managed by an alien buffer use -- `alien_functions` to find object methods and `alien_metatable` -- for metamethods. @@ -438,7 +438,7 @@ local alien_functions = { set = function (self, from, v, n) - argscheck ("set", {"Array", "int", "number", "int"}, + argscheck ("set", {"Vector", "int", "number", "int"}, {self, from, v, n}) local used = self.length @@ -479,7 +479,7 @@ local alien_functions = { alien_metatable = { - _type = "Array", + _type = "Vector", __ipairs = function (self) local i, n = 0, self.length @@ -511,7 +511,7 @@ alien_metatable = { if typeof (n) == "number" then local used = self.length if n == 0 or math.abs (n) > used then - error ("array access " .. n .. " out of bounds: 0 < n <= " .. tostring (self.length), 2) + error ("vector access " .. n .. " out of bounds: 0 < n <= " .. tostring (self.length), 2) end if n < 0 then n = n + used + 1 end self.buffer:set ((n - 1) * self.size + 1, elem, self.type) @@ -534,36 +534,36 @@ alien_metatable = { --- Return a function that dispatches to a virtual function table. --- The __call metamethod ensures that cloned array objects are assigned +-- The __call metamethod ensures that cloned vector objects are assigned -- a metatable and method table optimised for the element storage method --- (either alien buffer, or Lua table element containers), but the array +-- (either alien buffer, or Lua table element containers), but the vector -- prototype returned by this module needs to dispatch to the correct -- function according to the element type at run-time, because we want -- to support passing either object as an argument to a module function. -- @string name method name to dispatch -- @treturn function call `alien_function[name]` or -- `core_function[name]` --- as appropriate to the element manager of array +-- as appropriate to the element manager of vector local function dispatch (name) - return function (array, ...) - argcheck (name, 1, "Array", array) - local vfns = array.size > 0 and alien_functions or core_functions - return vfns[name] (array, ...) + return function (vector, ...) + argcheck (name, 1, "Vector", vector) + local vfns = vector.size > 0 and alien_functions or core_functions + return vfns[name] (vector, ...) end end ------ --- An efficient array of homogenous objects. --- @table std.array +-- An efficient vector of homogenous objects. +-- @table std.vector -- @int allocated number of allocated element slots, for `alien.buffer` -- managed elements -- @tfield alien.buffer|table buffer a block of indexable memory -- @int length number of elements currently stored -- @int size length of each stored element, or 0 when `alien.buffer` is --- not managing this array +-- not managing this vector -- @string type type name for elements -local Array = Container { - _type = "Array", +local Vector = Container { + _type = "Vector", -- Prototype initial values. @@ -593,4 +593,4 @@ local Array = Container { } -return Array +return Vector diff --git a/local.mk b/local.mk index d58f83a..547dadb 100644 --- a/local.mk +++ b/local.mk @@ -62,7 +62,6 @@ dist_lua_DATA += \ luastddir = $(luadir)/std dist_luastd_DATA = \ - lib/std/array.lua \ lib/std/base.lua \ lib/std/container.lua \ lib/std/debug.lua \ @@ -80,6 +79,7 @@ dist_luastd_DATA = \ lib/std/string.lua \ lib/std/table.lua \ lib/std/tree.lua \ + lib/std/vector.lua \ $(NOTHING_ELSE) @@ -116,7 +116,6 @@ dist_doc_DATA += \ $(srcdir)/doc/ldoc.css dist_classes_DATA += \ - $(srcdir)/doc/classes/std.array.html \ $(srcdir)/doc/classes/std.container.html \ $(srcdir)/doc/classes/std.list.html \ $(srcdir)/doc/classes/std.object.html \ @@ -124,6 +123,7 @@ dist_classes_DATA += \ $(srcdir)/doc/classes/std.set.html \ $(srcdir)/doc/classes/std.strbuf.html \ $(srcdir)/doc/classes/std.tree.html \ + $(srcdir)/doc/classes/std.vector.html \ $(NOTHING_ELSE) dist_modules_DATA += \ diff --git a/specs/array_spec.yaml b/specs/array_spec.yaml deleted file mode 100644 index 6bae193..0000000 --- a/specs/array_spec.yaml +++ /dev/null @@ -1,739 +0,0 @@ -before: - Object = require "std.object" - Array = require "std.array" - - prototype = Object.prototype - -specify Array: -- before: | - array = Array ("long", {1, 1}) - -- append fibonacci numbers until long word overflows - repeat - array:push (array[-1] + array[-2]) - until array.length > 50 or array[-1] < array[-2] - -- discard overflowed element - array:pop () - - local aliens = Array ("int", {42}) - have_alien = aliens.allocated > 0 - -- describe __call: - - it diagnoses wrong argument types: | - expect (Array (1, 2)). - to_error "bad argument #1 to 'Array' (string expected, got number)" - expect (Array (function () end)). - to_error "bad argument #1 to 'Array' (int, string or table expected, got function)" - expect (Array ("int", function () end)). - to_error "bad argument #2 to 'Array' (int or table expected, got function)" - - context with inherited element type: - - it constructs an empty array: - array = Array () - expect (array.length).to_be (0) - expect (array.type).to_be (Array.type) - - it constructs a sized array: - array = Array (100) - expect (array.length).to_be (100) - expect (array.type).to_be (Array.type) - - it sets uninitialised elements to zero: - array = Array (100) - for i = 1, 100 do - expect (array[i]).to_be (0) - end - expect (array.type).to_be (Array.type) - - it initialises values from a table: - array = Array {1, 4, 9, 16, 25, 36, 49, 64, 81} - expect (array.length).to_be (9) - for i = 1, array.length do - expect (array[i]).to_be (i * i) - end - expect (array.type).to_be (Array.type) - - it contains values from prototype array: - a = array () - for i = 3, array.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - expect (a.type).to_be (array.type) - - it truncates copied prototype values: - c = math.floor (array.length / 2) - a = array (c) - expect (a.length).to_be (c) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - expect (a.type).to_be (array.type) - - it zero pads copied prototype values: - a = array (array.length * 2) - expect (a.length).to_be (array.length * 2) - for i = 3, array.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - for i = array.length + 1, a.length do - expect (a[i]).to_be (0) - end - expect (a.type).to_be (array.type) - - context with specified element type: - - it constructs an alien managed array when possible: - if have_alien then - a = array ("int", {1, 1, 2, 3, 5}) - expect (a.allocated).not_to_be (0) - b = a ("int", {1, 4, 9, 16, 25}) - expect (b.allocated).not_to_be (0) - end - - it constructs a tabular array when necessary: - aliens = Array ("int", {1, 1, 2, 3, 5}) - a = aliens ("table", {1, 2, 5}) - expect (a.allocated).to_be (0) - b = a ("table", {1, 4, 9, 16, 25}) - expect (a.allocated).to_be (0) - - it constructs an empty array: - array = Array "double" - expect (array.length).to_be (0) - expect (array.type).to_be "double" - - it constructs a sized array: - array = Array ("double", 100) - expect (array.length).to_be (100) - expect (array.type).to_be "double" - - it sets uninitialised elements to zero: - array = Array ("double", 100) - for i = 1, 100 do - expect (array[i]).to_be (0) - end - expect (array.type).to_be "double" - - it initialises values from a table: - array = Array ("double", {1, 4, 9, 16, 25, 36, 49, 64, 81}) - expect (array.length).to_be (9) - for i = 1, array.length do - expect (array[i]).to_be (i * i) - end - expect (array.type).to_be "double" - - it contains values from prototype array: - a = array "double" - for i = 3, array.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - expect (a.type).to_be "double" - - it truncates copied prototype values: - c = math.floor (array.length / 2) - a = array ("double", c) - expect (a.length).to_be (c) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - expect (a.type).to_be "double" - - it zero pads copied prototype values: - a = array ("double", array.length * 2) - expect (a.length).to_be (array.length * 2) - for i = 3, array.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - for i = array.length + 1, a.length do - expect (a[i]).to_be (0) - end - expect (a.type).to_be "double" - - context with non-alien element type: - - before: - array = Array ("table", { - {v=1}, {v=1}, {v=2}, {v=3}, {v=5}, {v=8}, {v=13}, {v=21}, {v=34} - }) - - it constructs an alien managed array when possible: - if have_alien then - a = array ("int", {1, 1, 2, 3, 5}) - expect (a.allocated).not_to_be (0) - b = array ("int", {1, 4, 9, 16, 25}) - expect (a.allocated).not_to_be (0) - end - - it constructs a tabular array when necessary: - aliens = Array ("int", {1, 1, 2, 3, 5}) - a = aliens ("table", {1, 2, 5}) - expect (a.allocated).to_be (0) - b = a ("table", {1, 4, 9, 16, 25}) - expect (a.allocated).to_be (0) - - it constructs an empty array: - array = Array "table" - expect (array.length).to_be (0) - expect (array.allocated).to_be (0) - - it constructs a sized array: - array = Array ("table", 100) - expect (array.length).to_be (100) - expect (array.allocated).to_be (0) - - it sets uninitialised elements to zero: - array = Array ("table", 100) - for i = 1, 100 do - expect (array[i]).to_be (0) - end - expect (array.allocated).to_be (0) - - it initialises values from a table: - array = Array ("table", {{v=1}, {v=4}, {v=9}, {v=16}, {v=25}}) - expect (array.length).to_be (5) - for i = 1, array.length do - expect (array[i].v).to_be (i * i) - end - expect (array.allocated).to_be (0) - - it contains values from prototype array: - a = array () - for i = 3, array.length do - expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) - end - expect (a.allocated).to_be (0) - - it truncates copied prototype values: - c = math.floor (array.length / 2) - a = array ("table", c) - expect (a.length).to_be (c) - for i = 3, a.length do - expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) - end - expect (a.allocated).to_be (0) - - it zero pads copied prototype values: - a = array ("table", array.length * 2) - expect (a.length).to_be (array.length * 2) - for i = 3, array.length do - expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) - end - for i = array.length + 1, a.length do - expect (a[i]).to_be (0) - end - expect (a.allocated).to_be (0) - - -- describe __ipairs: - - before: - -- Some luajit builds, and PUC RIO 5.1 don't respect __ipairs - local t = setmetatable ({}, { - __ipairs = function (self) - local i = 0 - return function () - i = i + 1 - if i == 1 then return 1, 42 end - end - end, - }) - meta__ipairs = false - for _ in ipairs (t) do meta__ipairs = true end - - context with alien buffer elements: - - it iterates over elements: - elements = {1, 4, 9, 16, 25} - array = Array ("int", elements) - if meta__ipairs then - local t = {} - for i, v in ipairs (array) do t[i] = v end - expect (t).to_equal (elements) - end - - context with table elements: - - it iterates over elements: - elements = {"a", "b", {{"c"}, "d"}, "e"} - array = Array ("any", elements) - if meta__ipairs then - local t = {} - for i, v in ipairs (array) do t[i] = v end - expect (t).to_equal (elements) - end - - -- describe __len: - - before: | - -- Some luajit releases, and PUC RIO 5.1 don't respect __len - -- metamethod for # operator. - local t = setmetatable ({}, {__len = function () return 42 end }) - meta__len = #t == 42 - - it returns the number of elements stored: | - empty = Array "char" - trio = Array ("short", {1, 2, 3}) - if meta__len then - -- __len metamethod support available - expect (#empty).to_be (0) - expect (#trio).to_be (3) - else - -- have to get the length explicitly - expect (empty.length).to_be (0) - expect (trio.length).to_be (3) - end - -- describe __index: - - it returns nil for an empty array: - array = Array "int" - expect (array[1]).to_be (nil) - expect (array[-1]).to_be (nil) - - it retrieves a value stored at that index: - expect (array[1]).to_be (1) - expect (array[2]).to_be (1) - expect (array[3]).to_be (2) - expect (array[4]).to_be (3) - expect (array[5]).to_be (5) - - it retrieves negative indices counting from the right: - expect (array[-1]).to_be (array[array.length]) - expect (array[-2]).to_be (array[array.length - 1]) - expect (array[-(array.length - 1)]).to_be (array[2]) - expect (array[-(array.length)]).to_be (array[1]) - - it returns nil for out of bounds indices: - expect (array[-(array.length * 2)]).to_be (nil) - expect (array[-(array.length + 1)]).to_be (nil) - expect (array[0]).to_be (nil) - expect (array[array.length + 1]).to_be (nil) - expect (array[array.length * 2]).to_be (nil) - - it retrieves method names: - expect (type (array.push)).to_be "function" - expect (type (array.pop)).to_be "function" - - it diagnoses undefined methods: - expect (array.notamethod ()).to_error "attempt to call field 'notamethod'" - -- describe __newindex: - - it sets a new value at that index: - array[2] = 2 - expect (array[2]).to_be (2) - - it sets negative indexed elements counting from the right: - for i = 1, array.length do array[-i] = array.length - i + 1 end - for i = 1, array.length do - expect (array[i]).to_be (i) - end - - it diagnoses out of bounds indices: - for _, i in ipairs {array.length * -2, -1 - array.length, 0, - array.length + 1, array.length * 2} do - expect ((function () array[i] = i end) ()). - to_error "out of bounds" - end - -- describe __tostring: - - it renders all elements of the array: - array = Array ("char", {1, 4, 9, 16, 25}) - expect (tostring (array)).to_be 'Array ("char", {1, 4, 9, 16, 25})' - expect (tostring (Array "char")).to_be 'Array ("char", {})' - -- describe pop: - - context when called as a module function: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short array for this example. - array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - expect (Array.pop ()). - to_error "bad argument #1 to 'pop' (Array expected, got no value)" - - it returns nil for an empty array: - array = Array "char" - expect (Array.pop (array)).to_be (nil) - - it removes an element from the array: - count = array.length - repeat - expect (array.length).to_be (count) - count = count - 1 - until Array.pop (array) == nil - expect (array.length).to_be (0) - - it returns the removed element: - while array.length > 2 do - expect (Array.pop (array)).to_be (array[-1] + array[-2]) - end - - it does not perturb existing elements: - Array.pop (array) - for i = 3, array.length do - expect (array[i]).to_be (array[i -1] + array[i - 2]) - end - - context when called as an object method: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short array for this example. - array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it returns nil for an empty array: - array = Array "char" - expect (array:pop ()).to_be (nil) - - it removes an element from the array: - count = array.length - repeat - expect (array.length).to_be (count) - count = count - 1 - until array:pop () == nil - expect (array.length).to_be (0) - - it returns the removed element: - while array.length > 2 do - expect (array:pop ()).to_be (array[-1] + array[-2]) - end - - it does not perturb existing elements: - array:pop () - for i = 3, array.length do - expect (array[i]).to_be (array[i -1] + array[i - 2]) - end - -- describe push: - - context when called as a module function: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short array for this example. - array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - expect (Array.push ()). - to_error "bad argument #1 to 'push' (Array expected, got no value)" - if array.allocated > 0 then - -- non-alien managed arrays don't require number valued argument #2 - expect (Array.push (array)). - to_error "bad argument #2 to 'push' (number expected, got no value)" - end - - it diagnoses wrong argument types: | - expect (Array.push (1234)). - to_error "bad argument #1 to 'push' (Array expected, got number)" - if array.allocated > 0 then - expect (Array.push (array, "short")). - to_error "bad argument #2 to 'push' (number expected, got string)" - end - - it adds a single element to an empty array: - array = Array "int" - Array.push (array, 42) - expect (array[1]).to_be (array[-1]) - - it adds an element to an array: - count = array.length - Array.push (array, 42) - expect (array[-1]).to_be (42) - expect (array.length).to_be (count + 1) - Array.push (array, -273) - expect (array[-1]).to_be (-273) - expect (array.length).to_be (count + 2) - - it does not perturb existing elements: - Array.push (array, 42) - for i = 3, array.length - 1 do - expect (array[i]).to_be (array[i - 1] + array[i - 2]) - end - - context when called as an object method: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short array for this example. - array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - if array.allocated > 0 then - expect (array:push ()). - to_error "bad argument #2 to 'push' (number expected, got no value)" - end - - it diagnoses wrong argument type: | - if array.allocated > 0 then - expect (array:push ("short")). - to_error "bad argument #2 to 'push' (number expected, got string)" - end - - it adds a single element to an empty array: - array = Array "int" - array:push (42) - expect (array[1]).to_be (array[-1]) - - it adds an element to an array: - count = array.length - array:push (42) - expect (array[-1]).to_be (42) - expect (array.length).to_be (count + 1) - array:push (-273) - expect (array[-1]).to_be (-273) - expect (array.length).to_be (count + 2) - - it returns pushed value: - expect (array:push (42)).to_be (42) - expect (array:push (-273)).to_be (-273) - - it does not perturb existing elements: - array:push (42) - for i = 3, array.length - 1 do - expect (array[i]).to_be (array[i - 1] + array[i -2]) - end - -- describe realloc: - - context when called as a module function: - - it diagnoses missing arguments: | - expect (Array.realloc ()). - to_error "bad argument #1 to 'realloc' (Array expected, got no value)" - expect (Array.realloc (array)). - to_error "bad argument #2 to 'realloc' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (Array.realloc (1234)). - to_error "bad argument #1 to 'realloc' (Array expected, got number)" - expect (Array.realloc (array, "string")). - to_error "bad argument #2 to 'realloc' (int expected, got string)" - - it reduces the number of usable elements: - array = Array (100) - Array.realloc (array, 50) - expect (array.length).to_be (50) - - it truncates existing elements when reducing size: - a = array (100) - Array.realloc (a, 50) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it increases the number of usable elements: - array = Array (50) - Array.realloc (array, 100) - expect (array.length).to_be (100) - - it does not perturb existing element values: - a = array (50) - Array.realloc (a, 100) - for i = 3, 50 do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it sets new elements to zero: - a = array (50) - Array.realloc (a, 100) - for i = 51, a.length do - expect (a[i]).to_be (0) - end - - context when called as an object method: - - it diagnoses missing arguments: | - expect (array:realloc ()). - to_error "bad argument #2 to 'realloc' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (array:realloc "string"). - to_error "bad argument #2 to 'realloc' (int expected, got string)" - - it reduces the number of usable elements: - array = Array (100):realloc (50) - expect (array.length).to_be (50) - - it truncates existing elements when reducing size: - a = array (100):realloc (50) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it increases the number of usable elements: - array = Array (50):realloc (100) - expect (array.length).to_be (100) - - it does not perturb existing element values: - a = array (50):realloc (100) - for i = 3, 50 do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it sets new elements to zero: - a = array (50):realloc (100) - for i = 51, a.length do - expect (a[i]).to_be (0) - end - -- describe set: - - context when called as a module function: - - it diagnoses missing arguments: | - expect (Array.set ()). - to_error "bad argument #1 to 'set' (Array expected, got no value)" - expect (Array.set (array)). - to_error "bad argument #2 to 'set' (int expected, got no value)" - if array.allocated > 0 then - expect (Array.set (array, 1)). - to_error "bad argument #3 to 'set' (number expected, got no value)" - end - expect (Array.set (array, 1, 0)). - to_error "bad argument #4 to 'set' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (Array.set (100)). - to_error "bad argument #1 to 'set' (Array expected, got number)" - expect (Array.set (array, "bogus")). - to_error "bad argument #2 to 'set' (int expected, got string)" - if array.allocated > 0 then - expect (Array.set (array, 1, {0})). - to_error "bad argument #3 to 'set' (number expected, got table)" - end - expect (Array.set (array, 1, 0, function () end)). - to_error "bad argument #4 to 'set' (int expected, got function)" - - it changes the value of a subsequence of elements: - array = Array (100) - Array.set (array, 25, 1, 50) - for i = 1, array.length do - if i >= 25 and i < 75 then - expect (array[i]).to_be (1) - else - expect (array[i]).to_be (0) - end - end - - it understands negative from index: - array = Array (100) - Array.set (array, -50, 1, 50) - for i = 1, array.length do - if i <= 50 then - expect (array[i]).to_be (0) - else - expect (array[i]).to_be (1) - end - end - - it does not affect the prototype array elements: - a = array (100) - Array.set (a, 25, 1, 50) - for i = 3, array.length do - expect (array[i]).to_be (array[i - 1] + array[i - 2]) - end - - it does not affect elements outside range being set: - a = array (100) - Array.set (a, 25, 1, 50) - for i = 1, a.length do - if i >= 25 and i < 75 then - expect (a[i]).to_be (1) - elseif i <= array.length then - expect (a[i]).to_be (array[i]) - else - expect (a[i]).to_be (0) - end - end - - context when called as an object method: - - it diagnoses missing arguments: | - expect (array:set ()). - to_error "bad argument #2 to 'set' (int expected, got no value)" - if array.allocated > 0 then - expect (array:set (1)). - to_error "bad argument #3 to 'set' (number expected, got no value)" - end - expect (array:set (1, 0)). - to_error "bad argument #4 to 'set' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (array:set "bogus"). - to_error "bad argument #2 to 'set' (int expected, got string)" - if array.allocated > 0 then - expect (array:set (1, {0})). - to_error "bad argument #3 to 'set' (number expected, got table)" - end - expect (array:set (1, 0, function () end)). - to_error "bad argument #4 to 'set' (int expected, got function)" - - it changes the value of a subsequence of elements: - array = Array (100):set (25, 1, 50) - for i = 1, array.length do - if i >= 25 and i < 75 then - expect (array[i]).to_be (1) - else - expect (array[i]).to_be (0) - end - end - - it understands negative from index: - array = Array (100):set (-50, 1, 50) - for i = 1, array.length do - if i <= 50 then - expect (array[i]).to_be (0) - else - expect (array[i]).to_be (1) - end - end - - it does not affect the prototype array elements: - a = array (100):set (25, 1, 50) - for i = 3, array.length do - expect (array[i]).to_be (array[i - 1] + array[i - 2]) - end - - it does not affect elements outside range being set: - a = array (100):set (25, 1, 50) - for i = 1, a.length do - if i >= 25 and i < 75 then - expect (a[i]).to_be (1) - elseif i <= array.length then - expect (a[i]).to_be (array[i]) - else - expect (a[i]).to_be (0) - end - end - -- describe shift: - - context when called as a module function: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short array for this example. - array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - expect (Array.shift ()). - to_error "bad argument #1 to 'shift' (Array expected, got no value)" - - it returns nil for an empty array: - array = Array "char" - expect (Array.shift (array)).to_be (nil) - - it removes an element from the array: - count = array.length - repeat - expect (array.length).to_be (count) - count = count - 1 - until Array.shift (array) == nil - expect (array.length).to_be (0) - - it returns the removed element: - while array.length > 2 do - expect (Array.shift (array)).to_be (array[2] - array[1]) - end - - it shifts existing elements one position left: - shiftme = array () - Array.shift (shiftme) - for i = 1, shiftme.length do - expect (shiftme[i]).to_be (array[i + 1]) - end - - context when called as an object method: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short array for this example. - array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it returns nil for an empty array: - array = Array "char" - expect (array:shift ()).to_be (nil) - - it removes an element from the array: - count = array.length - repeat - expect (array.length).to_be (count) - count = count - 1 - until array:shift () == nil - expect (array.length).to_be (0) - - it returns the removed element: - while array.length > 2 do - expect (array:shift ()).to_be (array[2] - array[1]) - end - - it shifts existing elements one position left: - shiftme = array () - shiftme:shift () - for i = 1, shiftme.length do - expect (shiftme[i]).to_be (array[i + 1]) - end - -- describe unshift: - - context when called as a module function: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short array for this example. - array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - expect (Array.unshift ()). - to_error "bad argument #1 to 'unshift' (Array expected, got no value)" - if array.allocated > 0 then - expect (Array.unshift (array)). - to_error "bad argument #2 to 'unshift' (number expected, got no value)" - end - - it diagnoses wrong argument types: | - expect (Array.unshift (1234)). - to_error "bad argument #1 to 'unshift' (Array expected, got number)" - if array.allocated > 0 then - expect (Array.unshift (array, "short")). - to_error "bad argument #2 to 'unshift' (number expected, got string)" - end - - it adds a single element to an empty array: - array = Array "int" - Array.unshift (array, 42) - expect (array[1]).to_be (array[-1]) - - it inserts an element into an array: - count = array.length - Array.unshift (array, 42) - expect (array[1]).to_be (42) - expect (array.length).to_be (count + 1) - Array.unshift (array, -273) - expect (array[1]).to_be (-273) - expect (array.length).to_be (count + 2) - - it shifts existing elements one position right: - unshiftme = array () - Array.unshift (unshiftme, 42) - for i = 1, array.length do - expect (unshiftme[i + 1]).to_be (array[i]) - end - - context when called as an object method: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short array for this example. - array = Array ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - if array.allocated > 0 then - expect (array:unshift ()). - to_error "bad argument #2 to 'unshift' (number expected, got no value)" - end - - it diagnoses wrong argument type: | - if array.allocated > 0 then - expect (array:unshift ("short")). - to_error "bad argument #2 to 'unshift' (number expected, got string)" - end - - it adds a single element to an empty array: - array = Array "int" - array:unshift (42) - expect (array[1]).to_be (array[-1]) - - it adds an element to an array: - count = array.length - array:unshift (42) - expect (array[1]).to_be (42) - expect (array.length).to_be (count + 1) - array:unshift (-273) - expect (array[1]).to_be (-273) - expect (array.length).to_be (count + 2) - - it returns unshifted value: - expect (array:unshift (42)).to_be (42) - expect (array:unshift (-273)).to_be (-273) - - it shifts existing elements one position right: - unshiftme = array () - unshiftme:unshift (42) - for i = 1, array.length do - expect (unshiftme[i + 1]).to_be (array[i]) - end diff --git a/specs/specs.mk b/specs/specs.mk index 5653006..221b2e3 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -24,7 +24,6 @@ SPECL_OPTS = --unicode ## affected. specl_SPECS = \ - $(srcdir)/specs/array_spec.yaml \ $(srcdir)/specs/base_spec.yaml \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ @@ -40,6 +39,7 @@ specl_SPECS = \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ + $(srcdir)/specs/vector_spec.yaml \ $(srcdir)/specs/std_spec.yaml \ $(NOTHING_ELSE) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 28f146b..fdb17cb 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -578,10 +578,10 @@ specify std.string: t[1] = t expect (r (t)). to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") - - it converts an Array to a representative string: - a = require "std.array" {"a", "b", {{"c"},"d"},"e"} + - it converts a Vector to a representative string: + a = require "std.vector" {"a", "b", {{"c"},"d"},"e"} expect (r (a)). - to_be ('Array ("any", {a, b, ' .. tostring (a[3]) .. ', e})') + to_be ('Vector ("any", {a, b, ' .. tostring (a[3]) .. ', e})') - describe require: diff --git a/specs/vector_spec.yaml b/specs/vector_spec.yaml new file mode 100644 index 0000000..6d2e57a --- /dev/null +++ b/specs/vector_spec.yaml @@ -0,0 +1,739 @@ +before: + Object = require "std.object" + Vector = require "std.vector" + + prototype = Object.prototype + +specify Vector: +- before: | + vector = Vector ("long", {1, 1}) + -- append fibonacci numbers until long word overflows + repeat + vector:push (vector[-1] + vector[-2]) + until vector.length > 50 or vector[-1] < vector[-2] + -- discard overflowed element + vector:pop () + + local aliens = Vector ("int", {42}) + have_alien = aliens.allocated > 0 + +- describe __call: + - it diagnoses wrong argument types: | + expect (Vector (1, 2)). + to_error "bad argument #1 to 'Vector' (string expected, got number)" + expect (Vector (function () end)). + to_error "bad argument #1 to 'Vector' (int, string or table expected, got function)" + expect (Vector ("int", function () end)). + to_error "bad argument #2 to 'Vector' (int or table expected, got function)" + - context with inherited element type: + - it constructs an empty vector: + vector = Vector () + expect (vector.length).to_be (0) + expect (vector.type).to_be (Vector.type) + - it constructs a sized vector: + vector = Vector (100) + expect (vector.length).to_be (100) + expect (vector.type).to_be (Vector.type) + - it sets uninitialised elements to zero: + vector = Vector (100) + for i = 1, 100 do + expect (vector[i]).to_be (0) + end + expect (vector.type).to_be (Vector.type) + - it initialises values from a table: + vector = Vector {1, 4, 9, 16, 25, 36, 49, 64, 81} + expect (vector.length).to_be (9) + for i = 1, vector.length do + expect (vector[i]).to_be (i * i) + end + expect (vector.type).to_be (Vector.type) + - it contains values from prototype vector: + a = vector () + for i = 3, vector.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + expect (a.type).to_be (vector.type) + - it truncates copied prototype values: + c = math.floor (vector.length / 2) + a = vector (c) + expect (a.length).to_be (c) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + expect (a.type).to_be (vector.type) + - it zero pads copied prototype values: + a = vector (vector.length * 2) + expect (a.length).to_be (vector.length * 2) + for i = 3, vector.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + for i = vector.length + 1, a.length do + expect (a[i]).to_be (0) + end + expect (a.type).to_be (vector.type) + - context with specified element type: + - it constructs an alien managed vector when possible: + if have_alien then + a = vector ("int", {1, 1, 2, 3, 5}) + expect (a.allocated).not_to_be (0) + b = a ("int", {1, 4, 9, 16, 25}) + expect (b.allocated).not_to_be (0) + end + - it constructs a tabular vector when necessary: + aliens = Vector ("int", {1, 1, 2, 3, 5}) + a = aliens ("table", {1, 2, 5}) + expect (a.allocated).to_be (0) + b = a ("table", {1, 4, 9, 16, 25}) + expect (a.allocated).to_be (0) + - it constructs an empty vector: + vector = Vector "double" + expect (vector.length).to_be (0) + expect (vector.type).to_be "double" + - it constructs a sized vector: + vector = Vector ("double", 100) + expect (vector.length).to_be (100) + expect (vector.type).to_be "double" + - it sets uninitialised elements to zero: + vector = Vector ("double", 100) + for i = 1, 100 do + expect (vector[i]).to_be (0) + end + expect (vector.type).to_be "double" + - it initialises values from a table: + vector = Vector ("double", {1, 4, 9, 16, 25, 36, 49, 64, 81}) + expect (vector.length).to_be (9) + for i = 1, vector.length do + expect (vector[i]).to_be (i * i) + end + expect (vector.type).to_be "double" + - it contains values from prototype vector: + a = vector "double" + for i = 3, vector.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + expect (a.type).to_be "double" + - it truncates copied prototype values: + c = math.floor (vector.length / 2) + a = vector ("double", c) + expect (a.length).to_be (c) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + expect (a.type).to_be "double" + - it zero pads copied prototype values: + a = vector ("double", vector.length * 2) + expect (a.length).to_be (vector.length * 2) + for i = 3, vector.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + for i = vector.length + 1, a.length do + expect (a[i]).to_be (0) + end + expect (a.type).to_be "double" + - context with non-alien element type: + - before: + vector = Vector ("table", { + {v=1}, {v=1}, {v=2}, {v=3}, {v=5}, {v=8}, {v=13}, {v=21}, {v=34} + }) + - it constructs an alien managed vector when possible: + if have_alien then + a = vector ("int", {1, 1, 2, 3, 5}) + expect (a.allocated).not_to_be (0) + b = vector ("int", {1, 4, 9, 16, 25}) + expect (a.allocated).not_to_be (0) + end + - it constructs a tabular vector when necessary: + aliens = Vector ("int", {1, 1, 2, 3, 5}) + a = aliens ("table", {1, 2, 5}) + expect (a.allocated).to_be (0) + b = a ("table", {1, 4, 9, 16, 25}) + expect (a.allocated).to_be (0) + - it constructs an empty vector: + vector = Vector "table" + expect (vector.length).to_be (0) + expect (vector.allocated).to_be (0) + - it constructs a sized vector: + vector = Vector ("table", 100) + expect (vector.length).to_be (100) + expect (vector.allocated).to_be (0) + - it sets uninitialised elements to zero: + vector = Vector ("table", 100) + for i = 1, 100 do + expect (vector[i]).to_be (0) + end + expect (vector.allocated).to_be (0) + - it initialises values from a table: + vector = Vector ("table", {{v=1}, {v=4}, {v=9}, {v=16}, {v=25}}) + expect (vector.length).to_be (5) + for i = 1, vector.length do + expect (vector[i].v).to_be (i * i) + end + expect (vector.allocated).to_be (0) + - it contains values from prototype vector: + a = vector () + for i = 3, vector.length do + expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) + end + expect (a.allocated).to_be (0) + - it truncates copied prototype values: + c = math.floor (vector.length / 2) + a = vector ("table", c) + expect (a.length).to_be (c) + for i = 3, a.length do + expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) + end + expect (a.allocated).to_be (0) + - it zero pads copied prototype values: + a = vector ("table", vector.length * 2) + expect (a.length).to_be (vector.length * 2) + for i = 3, vector.length do + expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) + end + for i = vector.length + 1, a.length do + expect (a[i]).to_be (0) + end + expect (a.allocated).to_be (0) + + +- describe __ipairs: + - before: + -- Some luajit builds, and PUC RIO 5.1 don't respect __ipairs + local t = setmetatable ({}, { + __ipairs = function (self) + local i = 0 + return function () + i = i + 1 + if i == 1 then return 1, 42 end + end + end, + }) + meta__ipairs = false + for _ in ipairs (t) do meta__ipairs = true end + - context with alien buffer elements: + - it iterates over elements: + elements = {1, 4, 9, 16, 25} + vector = Vector ("int", elements) + if meta__ipairs then + local t = {} + for i, v in ipairs (vector) do t[i] = v end + expect (t).to_equal (elements) + end + - context with table elements: + - it iterates over elements: + elements = {"a", "b", {{"c"}, "d"}, "e"} + vector = Vector ("any", elements) + if meta__ipairs then + local t = {} + for i, v in ipairs (vector) do t[i] = v end + expect (t).to_equal (elements) + end + + +- describe __len: + - before: | + -- Some luajit releases, and PUC RIO 5.1 don't respect __len + -- metamethod for # operator. + local t = setmetatable ({}, {__len = function () return 42 end }) + meta__len = #t == 42 + - it returns the number of elements stored: | + empty = Vector "char" + trio = Vector ("short", {1, 2, 3}) + if meta__len then + -- __len metamethod support available + expect (#empty).to_be (0) + expect (#trio).to_be (3) + else + -- have to get the length explicitly + expect (empty.length).to_be (0) + expect (trio.length).to_be (3) + end + +- describe __index: + - it returns nil for an empty vector: + vector = Vector "int" + expect (vector[1]).to_be (nil) + expect (vector[-1]).to_be (nil) + - it retrieves a value stored at that index: + expect (vector[1]).to_be (1) + expect (vector[2]).to_be (1) + expect (vector[3]).to_be (2) + expect (vector[4]).to_be (3) + expect (vector[5]).to_be (5) + - it retrieves negative indices counting from the right: + expect (vector[-1]).to_be (vector[vector.length]) + expect (vector[-2]).to_be (vector[vector.length - 1]) + expect (vector[-(vector.length - 1)]).to_be (vector[2]) + expect (vector[-(vector.length)]).to_be (vector[1]) + - it returns nil for out of bounds indices: + expect (vector[-(vector.length * 2)]).to_be (nil) + expect (vector[-(vector.length + 1)]).to_be (nil) + expect (vector[0]).to_be (nil) + expect (vector[vector.length + 1]).to_be (nil) + expect (vector[vector.length * 2]).to_be (nil) + - it retrieves method names: + expect (type (vector.push)).to_be "function" + expect (type (vector.pop)).to_be "function" + - it diagnoses undefined methods: + expect (vector.notamethod ()).to_error "attempt to call field 'notamethod'" + +- describe __newindex: + - it sets a new value at that index: + vector[2] = 2 + expect (vector[2]).to_be (2) + - it sets negative indexed elements counting from the right: + for i = 1, vector.length do vector[-i] = vector.length - i + 1 end + for i = 1, vector.length do + expect (vector[i]).to_be (i) + end + - it diagnoses out of bounds indices: + for _, i in ipairs {vector.length * -2, -1 - vector.length, 0, + vector.length + 1, vector.length * 2} do + expect ((function () vector[i] = i end) ()). + to_error "out of bounds" + end + +- describe __tostring: + - it renders all elements of the vector: + vector = Vector ("char", {1, 4, 9, 16, 25}) + expect (tostring (vector)).to_be 'Vector ("char", {1, 4, 9, 16, 25})' + expect (tostring (Vector "char")).to_be 'Vector ("char", {})' + +- describe pop: + - context when called as a module function: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short vector for this example. + vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (Vector.pop ()). + to_error "bad argument #1 to 'pop' (Vector expected, got no value)" + - it returns nil for an empty vector: + vector = Vector "char" + expect (Vector.pop (vector)).to_be (nil) + - it removes an element from the vector: + count = vector.length + repeat + expect (vector.length).to_be (count) + count = count - 1 + until Vector.pop (vector) == nil + expect (vector.length).to_be (0) + - it returns the removed element: + while vector.length > 2 do + expect (Vector.pop (vector)).to_be (vector[-1] + vector[-2]) + end + - it does not perturb existing elements: + Vector.pop (vector) + for i = 3, vector.length do + expect (vector[i]).to_be (vector[i -1] + vector[i - 2]) + end + - context when called as an object method: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short vector for this example. + vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it returns nil for an empty vector: + vector = Vector "char" + expect (vector:pop ()).to_be (nil) + - it removes an element from the vector: + count = vector.length + repeat + expect (vector.length).to_be (count) + count = count - 1 + until vector:pop () == nil + expect (vector.length).to_be (0) + - it returns the removed element: + while vector.length > 2 do + expect (vector:pop ()).to_be (vector[-1] + vector[-2]) + end + - it does not perturb existing elements: + vector:pop () + for i = 3, vector.length do + expect (vector[i]).to_be (vector[i -1] + vector[i - 2]) + end + +- describe push: + - context when called as a module function: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short vector for this example. + vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (Vector.push ()). + to_error "bad argument #1 to 'push' (Vector expected, got no value)" + if vector.allocated > 0 then + -- non-alien managed vectors don't require number valued argument #2 + expect (Vector.push (vector)). + to_error "bad argument #2 to 'push' (number expected, got no value)" + end + - it diagnoses wrong argument types: | + expect (Vector.push (1234)). + to_error "bad argument #1 to 'push' (Vector expected, got number)" + if vector.allocated > 0 then + expect (Vector.push (vector, "short")). + to_error "bad argument #2 to 'push' (number expected, got string)" + end + - it adds a single element to an empty vector: + vector = Vector "int" + Vector.push (vector, 42) + expect (vector[1]).to_be (vector[-1]) + - it adds an element to an vector: + count = vector.length + Vector.push (vector, 42) + expect (vector[-1]).to_be (42) + expect (vector.length).to_be (count + 1) + Vector.push (vector, -273) + expect (vector[-1]).to_be (-273) + expect (vector.length).to_be (count + 2) + - it does not perturb existing elements: + Vector.push (vector, 42) + for i = 3, vector.length - 1 do + expect (vector[i]).to_be (vector[i - 1] + vector[i - 2]) + end + - context when called as an object method: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short vector for this example. + vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + if vector.allocated > 0 then + expect (vector:push ()). + to_error "bad argument #2 to 'push' (number expected, got no value)" + end + - it diagnoses wrong argument type: | + if vector.allocated > 0 then + expect (vector:push ("short")). + to_error "bad argument #2 to 'push' (number expected, got string)" + end + - it adds a single element to an empty vector: + vector = Vector "int" + vector:push (42) + expect (vector[1]).to_be (vector[-1]) + - it adds an element to an vector: + count = vector.length + vector:push (42) + expect (vector[-1]).to_be (42) + expect (vector.length).to_be (count + 1) + vector:push (-273) + expect (vector[-1]).to_be (-273) + expect (vector.length).to_be (count + 2) + - it returns pushed value: + expect (vector:push (42)).to_be (42) + expect (vector:push (-273)).to_be (-273) + - it does not perturb existing elements: + vector:push (42) + for i = 3, vector.length - 1 do + expect (vector[i]).to_be (vector[i - 1] + vector[i -2]) + end + +- describe realloc: + - context when called as a module function: + - it diagnoses missing arguments: | + expect (Vector.realloc ()). + to_error "bad argument #1 to 'realloc' (Vector expected, got no value)" + expect (Vector.realloc (vector)). + to_error "bad argument #2 to 'realloc' (int expected, got no value)" + - it diagnoses wrong argument types: | + expect (Vector.realloc (1234)). + to_error "bad argument #1 to 'realloc' (Vector expected, got number)" + expect (Vector.realloc (vector, "string")). + to_error "bad argument #2 to 'realloc' (int expected, got string)" + - it reduces the number of usable elements: + vector = Vector (100) + Vector.realloc (vector, 50) + expect (vector.length).to_be (50) + - it truncates existing elements when reducing size: + a = vector (100) + Vector.realloc (a, 50) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it increases the number of usable elements: + vector = Vector (50) + Vector.realloc (vector, 100) + expect (vector.length).to_be (100) + - it does not perturb existing element values: + a = vector (50) + Vector.realloc (a, 100) + for i = 3, 50 do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it sets new elements to zero: + a = vector (50) + Vector.realloc (a, 100) + for i = 51, a.length do + expect (a[i]).to_be (0) + end + - context when called as an object method: + - it diagnoses missing arguments: | + expect (vector:realloc ()). + to_error "bad argument #2 to 'realloc' (int expected, got no value)" + - it diagnoses wrong argument types: | + expect (vector:realloc "string"). + to_error "bad argument #2 to 'realloc' (int expected, got string)" + - it reduces the number of usable elements: + vector = Vector (100):realloc (50) + expect (vector.length).to_be (50) + - it truncates existing elements when reducing size: + a = vector (100):realloc (50) + for i = 3, a.length do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it increases the number of usable elements: + vector = Vector (50):realloc (100) + expect (vector.length).to_be (100) + - it does not perturb existing element values: + a = vector (50):realloc (100) + for i = 3, 50 do + expect (a[i]).to_be (a[i - 1] + a[i - 2]) + end + - it sets new elements to zero: + a = vector (50):realloc (100) + for i = 51, a.length do + expect (a[i]).to_be (0) + end + +- describe set: + - context when called as a module function: + - it diagnoses missing arguments: | + expect (Vector.set ()). + to_error "bad argument #1 to 'set' (Vector expected, got no value)" + expect (Vector.set (vector)). + to_error "bad argument #2 to 'set' (int expected, got no value)" + if vector.allocated > 0 then + expect (Vector.set (vector, 1)). + to_error "bad argument #3 to 'set' (number expected, got no value)" + end + expect (Vector.set (vector, 1, 0)). + to_error "bad argument #4 to 'set' (int expected, got no value)" + - it diagnoses wrong argument types: | + expect (Vector.set (100)). + to_error "bad argument #1 to 'set' (Vector expected, got number)" + expect (Vector.set (vector, "bogus")). + to_error "bad argument #2 to 'set' (int expected, got string)" + if vector.allocated > 0 then + expect (Vector.set (vector, 1, {0})). + to_error "bad argument #3 to 'set' (number expected, got table)" + end + expect (Vector.set (vector, 1, 0, function () end)). + to_error "bad argument #4 to 'set' (int expected, got function)" + - it changes the value of a subsequence of elements: + vector = Vector (100) + Vector.set (vector, 25, 1, 50) + for i = 1, vector.length do + if i >= 25 and i < 75 then + expect (vector[i]).to_be (1) + else + expect (vector[i]).to_be (0) + end + end + - it understands negative from index: + vector = Vector (100) + Vector.set (vector, -50, 1, 50) + for i = 1, vector.length do + if i <= 50 then + expect (vector[i]).to_be (0) + else + expect (vector[i]).to_be (1) + end + end + - it does not affect the prototype vector elements: + a = vector (100) + Vector.set (a, 25, 1, 50) + for i = 3, vector.length do + expect (vector[i]).to_be (vector[i - 1] + vector[i - 2]) + end + - it does not affect elements outside range being set: + a = vector (100) + Vector.set (a, 25, 1, 50) + for i = 1, a.length do + if i >= 25 and i < 75 then + expect (a[i]).to_be (1) + elseif i <= vector.length then + expect (a[i]).to_be (vector[i]) + else + expect (a[i]).to_be (0) + end + end + - context when called as an object method: + - it diagnoses missing arguments: | + expect (vector:set ()). + to_error "bad argument #2 to 'set' (int expected, got no value)" + if vector.allocated > 0 then + expect (vector:set (1)). + to_error "bad argument #3 to 'set' (number expected, got no value)" + end + expect (vector:set (1, 0)). + to_error "bad argument #4 to 'set' (int expected, got no value)" + - it diagnoses wrong argument types: | + expect (vector:set "bogus"). + to_error "bad argument #2 to 'set' (int expected, got string)" + if vector.allocated > 0 then + expect (vector:set (1, {0})). + to_error "bad argument #3 to 'set' (number expected, got table)" + end + expect (vector:set (1, 0, function () end)). + to_error "bad argument #4 to 'set' (int expected, got function)" + - it changes the value of a subsequence of elements: + vector = Vector (100):set (25, 1, 50) + for i = 1, vector.length do + if i >= 25 and i < 75 then + expect (vector[i]).to_be (1) + else + expect (vector[i]).to_be (0) + end + end + - it understands negative from index: + vector = Vector (100):set (-50, 1, 50) + for i = 1, vector.length do + if i <= 50 then + expect (vector[i]).to_be (0) + else + expect (vector[i]).to_be (1) + end + end + - it does not affect the prototype vector elements: + a = vector (100):set (25, 1, 50) + for i = 3, vector.length do + expect (vector[i]).to_be (vector[i - 1] + vector[i - 2]) + end + - it does not affect elements outside range being set: + a = vector (100):set (25, 1, 50) + for i = 1, a.length do + if i >= 25 and i < 75 then + expect (a[i]).to_be (1) + elseif i <= vector.length then + expect (a[i]).to_be (vector[i]) + else + expect (a[i]).to_be (0) + end + end + +- describe shift: + - context when called as a module function: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short vector for this example. + vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (Vector.shift ()). + to_error "bad argument #1 to 'shift' (Vector expected, got no value)" + - it returns nil for an empty vector: + vector = Vector "char" + expect (Vector.shift (vector)).to_be (nil) + - it removes an element from the vector: + count = vector.length + repeat + expect (vector.length).to_be (count) + count = count - 1 + until Vector.shift (vector) == nil + expect (vector.length).to_be (0) + - it returns the removed element: + while vector.length > 2 do + expect (Vector.shift (vector)).to_be (vector[2] - vector[1]) + end + - it shifts existing elements one position left: + shiftme = vector () + Vector.shift (shiftme) + for i = 1, shiftme.length do + expect (shiftme[i]).to_be (vector[i + 1]) + end + - context when called as an object method: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short vector for this example. + vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it returns nil for an empty vector: + vector = Vector "char" + expect (vector:shift ()).to_be (nil) + - it removes an element from the vector: + count = vector.length + repeat + expect (vector.length).to_be (count) + count = count - 1 + until vector:shift () == nil + expect (vector.length).to_be (0) + - it returns the removed element: + while vector.length > 2 do + expect (vector:shift ()).to_be (vector[2] - vector[1]) + end + - it shifts existing elements one position left: + shiftme = vector () + shiftme:shift () + for i = 1, shiftme.length do + expect (shiftme[i]).to_be (vector[i + 1]) + end + +- describe unshift: + - context when called as a module function: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short vector for this example. + vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + expect (Vector.unshift ()). + to_error "bad argument #1 to 'unshift' (Vector expected, got no value)" + if vector.allocated > 0 then + expect (Vector.unshift (vector)). + to_error "bad argument #2 to 'unshift' (number expected, got no value)" + end + - it diagnoses wrong argument types: | + expect (Vector.unshift (1234)). + to_error "bad argument #1 to 'unshift' (Vector expected, got number)" + if vector.allocated > 0 then + expect (Vector.unshift (vector, "short")). + to_error "bad argument #2 to 'unshift' (number expected, got string)" + end + - it adds a single element to an empty vector: + vector = Vector "int" + Vector.unshift (vector, 42) + expect (vector[1]).to_be (vector[-1]) + - it inserts an element into an vector: + count = vector.length + Vector.unshift (vector, 42) + expect (vector[1]).to_be (42) + expect (vector.length).to_be (count + 1) + Vector.unshift (vector, -273) + expect (vector[1]).to_be (-273) + expect (vector.length).to_be (count + 2) + - it shifts existing elements one position right: + unshiftme = vector () + Vector.unshift (unshiftme, 42) + for i = 1, vector.length do + expect (unshiftme[i + 1]).to_be (vector[i]) + end + - context when called as an object method: + - before: + # Rounding impedance mismatch between Lua double and alien long, so we + # use an intentionally short vector for this example. + vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) + - it diagnoses missing arguments: | + if vector.allocated > 0 then + expect (vector:unshift ()). + to_error "bad argument #2 to 'unshift' (number expected, got no value)" + end + - it diagnoses wrong argument type: | + if vector.allocated > 0 then + expect (vector:unshift ("short")). + to_error "bad argument #2 to 'unshift' (number expected, got string)" + end + - it adds a single element to an empty vector: + vector = Vector "int" + vector:unshift (42) + expect (vector[1]).to_be (vector[-1]) + - it adds an element to an vector: + count = vector.length + vector:unshift (42) + expect (vector[1]).to_be (42) + expect (vector.length).to_be (count + 1) + vector:unshift (-273) + expect (vector[1]).to_be (-273) + expect (vector.length).to_be (count + 2) + - it returns unshifted value: + expect (vector:unshift (42)).to_be (42) + expect (vector:unshift (-273)).to_be (-273) + - it shifts existing elements one position right: + unshiftme = vector () + unshiftme:unshift (42) + for i = 1, vector.length do + expect (unshiftme[i + 1]).to_be (vector[i]) + end From 02c0384718070f2d415da5fe3d72111e18e4930b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 4 Jul 2014 13:31:51 +0100 Subject: [PATCH 265/703] refactor: use a function to export container module methods. * specs/spec_helper.lua.in (badarg, toomanyarg): Format appropriately when module name is not given. * specs/container_spec.yaml (construction) Update to latest style: Add argcheck behaviour examples. * lib/std/base.lua (olen): Rename from this... (arglen): ...to this. (M): Export arglen and toomanyarg_fmt. * lib/std/container.lua (M): Add module name at element 1. (__tostring, __totable): Reformat these... (M.__tostring, M.__totable): ...as local table function declarations. (M.__call): When _ARGCHECK is not disabled, diagnose argument type errors in table _init styl objects, to satisfy newly specified behaviours. (mapfields): Upgrade to base export declaration (for overhead free argcheck calls with _DEBUG=false) and simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 36 +++--- lib/std/container.lua | 262 +++++++++++++++++++++----------------- specs/container_spec.yaml | 15 ++- specs/spec_helper.lua.in | 12 +- 4 files changed, 183 insertions(+), 142 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 32ff0c3..467cc69 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -91,12 +91,12 @@ local function normalize (t) end ---- Ordered length. +--- Argument list length. -- Like #table, but does not stop at the first nil value. -- @tparam table t a table -- @treturn int largest integer key in *t* --- @usage tmax = olen (t) -local function olen (t) +-- @usage tmax = arglen (t) +local function arglen (t) local len = 0 for k in pairs (t) do if type (k) == "number" and k > len then len = k end @@ -189,7 +189,7 @@ end -- @tparam table args a table of arguments to compare -- @treturn int|nil position of first mismatch in *types* local function match (types, args, allargs) - local typec, argc = #types, olen (args) + local typec, argc = #types, arglen (args) for i = 1, typec do local ok = pcall (argcheck, "pcall", i, types[i], args[i]) if not ok then return i end @@ -448,8 +448,8 @@ local function export (M, decl, fn, ...) else name = decl:match "([%w_][%d%w_]*)" end - if olen (args) > 3 then - error (string.format (toomanyarg_fmt, fname, 3, olen (args)), 2) + if arglen (args) > 3 then + error (string.format (toomanyarg_fmt, fname, 3, arglen (args)), 2) elseif type (M[1]) ~= "string" then argerror (fname, 1, "module name at index 1 expected, got no value") elseif name == nil then @@ -478,7 +478,7 @@ local function export (M, decl, fn, ...) fn = function (...) local args = {...} - local argc, bestmismatch, at = olen (args), 0, 0 + local argc, bestmismatch, at = arglen (args), 0, 0 for i, types in ipairs (type_specs) do local mismatch = match (types, args, max == math.huge) @@ -577,16 +577,18 @@ end local M = { - argcheck = argcheck, - argerror = argerror, - argscheck = argscheck, - deprecate = deprecate, - export = export, - getmetamethod = getmetamethod, - ielems = ielems, - leaves = leaves, - prototype = prototype, - split = split, + argcheck = argcheck, + argerror = argerror, + arglen = arglen, + argscheck = argscheck, + deprecate = deprecate, + export = export, + getmetamethod = getmetamethod, + ielems = ielems, + leaves = leaves, + prototype = prototype, + split = split, + toomanyarg_fmt = toomanyarg_fmt, } diff --git a/lib/std/container.lua b/lib/std/container.lua index ba77c6e..9001a71 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -4,13 +4,13 @@ A container is a @{std.object} with no methods. It's functionality is instead defined by its *meta*methods. - Where an Object uses the `\_\_index` metatable entry to hold object - methods, a Container stores its contents using `\_\_index`, preventing + Where an Object uses the `__index` metatable entry to hold object + methods, a Container stores its contents using `__index`, preventing it from having methods in there too. Although there are no actual methods, Containers are free to use - metamethods (`\_\_index`, `\_\_sub`, etc) and, like Objects, can supply - module functions by listing them in `\_functions`. Also, since a + metamethods (`__index`, `__sub`, etc) and, like Objects, can supply + module functions by listing them in `_functions`. Also, since a @{std.container} is a @{std.object}, it can be passed to the @{std.object} module functions, or anywhere else a @{std.object} is expected. @@ -65,9 +65,14 @@ ]] +local _ARGCHECK = require "std.debug_init"._ARGCHECK + local base = require "std.base" -local argcheck, argscheck = base.argcheck, base.argscheck +local argcheck, export, prototype = + base.argcheck, base.export, base.prototype + +local M = { "std.container" } @@ -126,18 +131,17 @@ end --- Return `obj` with references to the fields of `src` merged in. +-- @function mapfields -- @static -- @tparam table obj destination object -- @tparam table src fields to copy into clone -- @tparam[opt={}] table map `{old_key=new_key, ...}` --- @treturn table `obj` with non-private fields from `src` merged, and +-- @treturn table *obj* with non-private fields from *src* merged, and -- a metatable with private fields (if any) merged, both sets of keys --- renamed according to `map` +-- renamed according to *map* -- @see std.object.mapfields -local function mapfields (obj, src, map) - argscheck ("std.container.mapfields", {"table", "table|object", "table?"}, - {obj, src, map}) - +local mapfields = export (M, "mapfields (table, table|object, table?)", +function (obj, src, map) local mt = getmetatable (obj) or {} -- Map key pairs. @@ -173,140 +177,158 @@ local function mapfields (obj, src, map) setmetatable (obj, mt) end return obj -end +end) + + +--- Return a clone of this container. +-- @function __call +-- @param x a table if prototype `_init` is a table, otherwise first +-- argument for a function type `_init` +-- @param ... any additional arguments for `_init` +-- @treturn std.container a clone of the called container. +-- @see std.object:__call +-- @usage +-- local Container = require "std.container" {} -- not a typo! +-- local new = Container {"init", {"elements"}, 2, "insert"} +local function __call (self, x, ...) + local mt = getmetatable (self) + local obj_mt = mt + local obj = {} + + -- This is the slowest part of cloning for any objects that have + -- a lot of fields to test and copy. If you need to clone a lot of + -- objects from a prototype with several module functions, it's much + -- faster to clone objects from each other than the prototype! + for k, v in pairs (self) do + if type (v) ~= "table" or v._type ~= "modulefunction" then + obj[k] = v + end + end + if type (mt._init) == "function" then + obj = mt._init (obj, x, ...) + else + obj = (self.mapfields or mapfields) (obj, x, mt._init) + end --- Type of an object. --- @static --- @tparam std.object obj an object --- @treturn string type of the object --- @see std.object.prototype -local prototype = base.prototype + -- If a metatable was set, then merge our fields and use it. + if next (getmetatable (obj) or {}) then + obj_mt = instantiate (mt, getmetatable (obj)) + -- Merge object methods. + if type (obj_mt.__index) == "table" and + type ((mt or {}).__index) == "table" + then + obj_mt.__index = instantiate (mt.__index, obj_mt.__index) + end + end ---- Container prototype. --- @table std.container --- @string[opt="Container"] _type type of Container, returned by --- @{std.object.prototype} --- @tfield table|function _init a table of field names, or --- initialisation function, used by @{__call} --- @tfield nil|table _functions a table of module functions not copied --- by @{std.object.__call} -local metatable = { - _type = "Container", + return setmetatable (obj, obj_mt) +end - --- Return a clone of this container. - -- @function __call - -- @param x a table if prototype `_init` is a table, otherwise first - -- argument for a function type `_init` - -- @param ... any additional arguments for `_init` - -- @treturn std.container a clone of the called container. - -- @see std.object:__call - -- @usage - -- local Container = require "std.container" {} -- not a typo! - -- local new = Container {"init", {"elements"}, 2, "insert"} - __call = function (self, x, ...) - argcheck ("std.container.__call", 1, "object", self) - - local mt = getmetatable (self) - local obj_mt = mt - local obj = {} - - -- This is the slowest part of cloning for any objects that have - -- a lot of fields to test and copy. If you need to clone a lot of - -- objects from a prototype with several module functions, it's much - -- faster to clone objects from each other than the prototype! - for k, v in pairs (self) do - if type (v) ~= "table" or v._type ~= "modulefunction" then - obj[k] = v - end - end - if type (mt._init) == "function" then - obj = mt._init (obj, x, ...) - else - obj = (self.mapfields or mapfields) (obj, x, mt._init) - end +if _ARGCHECK then - -- If a metatable was set, then merge our fields and use it. - if next (getmetatable (obj) or {}) then - obj_mt = instantiate (mt, getmetatable (obj)) + local arglen, toomanyarg_fmt = base.arglen, base.toomanyarg_fmt - -- Merge object methods. - if type (obj_mt.__index) == "table" and - type ((mt or {}).__index) == "table" - then - obj_mt.__index = instantiate (mt.__index, obj_mt.__index) + M.__call = function (self, x, ...) + local mt = getmetatable (self) + + -- A function initialised object can be passed arguments of any + -- type, so only argcheck non-function initialised objects. + if type (mt._init) ~= "function" then + local name, argt = mt._type, {...} + -- Don't count `self` as an argument for error messages, because + -- it just refers back to the object being called: `Container {"x"}. + argcheck (name, 1, "table", x) + if next (argt) then + error (string.format (toomanyarg_fmt, name, 1, 1 + arglen (argt)), 2) end end - return setmetatable (obj, obj_mt) - end, + return __call (self, x, ...) + end +else - --- Return a string representation of this container. - -- @function __tostring - -- @treturn string stringified container representation - -- @see std.object.__tostring - -- @usage print (acontainer) - __tostring = function (self) - argcheck ("std.container.__tostring", 1, "object", self) + M.__call = __call - local totable = getmetatable (self).__totable - local array = instantiate (totable (self)) - local other = instantiate (array) - local s = "" - if #other > 0 then - for i in ipairs (other) do other[i] = nil end - end - for k in pairs (other) do array[k] = nil end - for i, v in ipairs (array) do array[i] = tostring (v) end - - local keys, dict = {}, {} - for k in pairs (other) do keys[#keys + 1] = k end - table.sort (keys, function (a, b) return tostring (a) < tostring (b) end) - for _, k in ipairs (keys) do - dict[#dict + 1] = tostring (k) .. "=" .. tostring (other[k]) - end +end - if #array > 0 then - s = s .. table.concat (array, ", ") - if next (dict) ~= nil then s = s .. "; " end - end - if #dict > 0 then - s = s .. table.concat (dict, ", ") - end - return prototype (self) .. " {" .. s .. "}" - end, +--- Return a string representation of this container. +-- @function __tostring +-- @treturn string stringified container representation +-- @see std.object.__tostring +-- @usage print (acontainer) +function M.__tostring (self) + local totable = getmetatable (self).__totable + local array = instantiate (totable (self)) + local other = instantiate (array) + local s = "" + if #other > 0 then + for i in ipairs (other) do other[i] = nil end + end + for k in pairs (other) do array[k] = nil end + for i, v in ipairs (array) do array[i] = tostring (v) end + + local keys, dict = {}, {} + for k in pairs (other) do keys[#keys + 1] = k end + table.sort (keys, function (a, b) return tostring (a) < tostring (b) end) + for _, k in ipairs (keys) do + dict[#dict + 1] = tostring (k) .. "=" .. tostring (other[k]) + end + if #array > 0 then + s = s .. table.concat (array, ", ") + if next (dict) ~= nil then s = s .. "; " end + end + if #dict > 0 then + s = s .. table.concat (dict, ", ") + end - --- Return a table representation of this container. - -- @function __totable - -- @treturn table a shallow copy of non-private container fields - -- @see std.object:__totable - -- @usage - -- local tostring = require "std.string".tostring - -- print (totable (acontainer)) - __totable = function (self) - argcheck ("std.container.__totable", 1, "object", self) + return prototype (self) .. " {" .. s .. "}" +end - local t = {} - for k, v in pairs (self) do - if type (k) ~= "string" or k:sub (1, 1) ~= "_" then - t[k] = v - end + +--- Return a table representation of this container. +-- @function __totable +-- @treturn table a shallow copy of non-private container fields +-- @see std.object:__totable +-- @usage +-- local tostring = require "std.string".tostring +-- print (totable (acontainer)) +function M.__totable (self) + local t = {} + for k, v in pairs (self) do + if type (k) ~= "string" or k:sub (1, 1) ~= "_" then + t[k] = v end - return t - end, -} + end + return t +end + +--- Container prototype. +-- @table std.container +-- @string[opt="Container"] _type type of Container, returned by +-- @{std.object.prototype} +-- @tfield table|function _init a table of field names, or +-- initialisation function, used by @{__call} +-- @tfield nil|table _functions a table of module functions not copied +-- by @{std.object.__call} return setmetatable ({ -- Normally, these are set and wrapped automatically during cloning. -- But, we have to bootstrap the first object, so in this one instance -- it has to be done manually. - mapfields = modulefunction (mapfields), + mapfields = modulefunction (M.mapfields), prototype = modulefunction (prototype), -}, metatable) +}, { + _type = "Container", + + __call = M.__call, + __tostring = M.__tostring, + __totable = M.__totable, +}) diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index faf7804..d806c3b 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -1,5 +1,5 @@ before: - Container = require "std.container" + Container = require "std.container" {} prototype = require "std.object".prototype specify std.container: @@ -10,6 +10,19 @@ specify std.container: to_equal {} - describe construction: + - context with table _init: + - it diagnoses missing arguments: + expect (Container ()).to_error (badarg (nil, "Container", 1, "table")) + - it diagnoses too many arguments: + expect (Container ({}, false)).to_error (toomanyarg (nil, "Container", 1, 2)) + - context with function _init: + - before: + Thing = Container { _type = "Thing", _init = function (obj) return obj end } + - it doesn't diagnose missing arguments: + expect (Thing ()).not_to_error () + - it doesn't diagnose too many args: + expect (Thing ({}, false)).not_to_error () + - context from Container prototype: - before: things = Container {"foo", "bar", baz="quux"} diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 401cd63..2ef0852 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -98,8 +98,10 @@ end -- @usage -- expect (f ()).to_error (badarg (module, name, 1, "function")) function badarg (mname, fname, i, want, got) - return string.format ("bad argument #%d to '%s.%s' (%s expected, got %s)", - i, mname, fname, want, got or "no value") + local name = fname + if mname then name = mname .. "." .. name end + return string.format ("bad argument #%d to '%s' (%s expected, got %s)", + i, name, want, got or "no value") end @@ -110,8 +112,10 @@ end -- @usage -- expect (f (1, 2)).to_error (toomanyarg (module, name, 1, 2)) function toomanyarg (mname, fname, want, got) - return string.format ("too many arguments to '%s.%s' (no more than %d expected, got %d)", - mname, fname, want, got) + local name = fname + if mname then name = mname .. "." .. name end + return string.format ("too many arguments to '%s' (no more than %d expected, got %d)", + name, want, got) end From a6fa882db05e864345f76899397e4a93a9f68b4f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 4 Jul 2014 14:48:59 +0100 Subject: [PATCH 266/703] specs: don't pass a Tree to object constructor. * specs/tree_spec.yaml (construction): Don't pass a Tree to an object constructor. Signed-off-by: Gary V. Vaughan --- specs/tree_spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 0d9da21..04d99c1 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -28,7 +28,7 @@ specify std.tree: expect (tr).not_to_be (Tree) expect (prototype (tr)).to_be "Tree" - it turns a table argument into a tree: - expect (prototype (Tree (tr))).to_be "Tree" + expect (prototype (Tree (t))).to_be "Tree" - it does not turn table argument values into sub-Trees: expect (prototype (tr["fnord"])).to_be "table" - it understands branched nodes: From 8e500ff81b110d4aa3d609ef52e11f579b989f28 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 4 Jul 2014 15:05:35 +0100 Subject: [PATCH 267/703] reformat: order functional module functions asciibetically. * lib/std/functional.lua: Reorder module functions asciibetically. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 204 ++++++++++++++++++++--------------------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index a1a7b18..a452fe3 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -9,15 +9,6 @@ local export = require "std.base".export local M = { "std.functional" } ---- Identity function. --- @function id --- @param ... --- @return the arguments passed to the function -function M.id (...) - return ... -end - - --- Partially apply a function. -- @function bind -- @func f function to apply partially @@ -67,25 +58,22 @@ export (M, "case (any?, #table)", function (with, branches) end) ---- Curry a function. --- @function curry --- @func f function to curry --- @int n number of arguments --- @treturn function curried version of *f* +--- Collect the results of an iterator. +-- @function collect +-- @func i iterator +-- @param ... iterator arguments +-- @return results of running the iterator on *arguments* +-- @see filter +-- @see map -- @usage --- > add = curry (function (x, y) return x + y end, 2) --- > incr, decr = add (1), add (-1) --- > =incr (99), decr (99) --- 100 98 -local curry -curry = export (M, "curry (func, int)", function (f, n) - if n <= 1 then - return f - else - return function (x) - return curry (bind (f, x), n - 1) - end +-- > =collect (std.list.relems, List {"a", "b", "c"}) +-- {"c", "b", "a"} +export (M, "collect (func, any*)", function (i, ...) + local t = {} + for e in i (...) do + t[#t + 1] = e end + return t end) @@ -118,43 +106,25 @@ export (M, "compose (func*)", function (...) end) ---- Signature of memoize `normalize` functions. --- @function memoize_normalize --- @param ... arguments --- @treturn string normalized arguments - - ---- Memoize a function, by wrapping it in a functable. --- --- To ensure that memoize always returns the same object for the same --- arguments, it passes arguments to `normalize` (std.string.tostring --- by default). You may need a more sophisticated function if memoize --- should handle complicated argument equivalencies. --- @function memoize --- @func fn function that returns a single result --- @func normalize[opt] function to normalize arguments --- @treturn functable memoized function +--- Curry a function. +-- @function curry +-- @func f function to curry +-- @int n number of arguments +-- @treturn function curried version of *f* -- @usage --- local fast = memoize (function (...) --[[ slow code ]] end) -export (M, "memoize (func, func?)", function (fn, normalize) - if normalize == nil then - -- Call require here, to avoid pulling in all of 'std.string' - -- even when memoize is never called. - local stringify = require "std.string".tostring - normalize = function (...) return stringify {...} end +-- > add = curry (function (x, y) return x + y end, 2) +-- > incr, decr = add (1), add (-1) +-- > =incr (99), decr (99) +-- 100 98 +local curry +curry = export (M, "curry (func, int)", function (f, n) + if n <= 1 then + return f + else + return function (x) + return curry (bind (f, x), n - 1) + end end - - return setmetatable ({}, { - __call = function (self, ...) - local k = normalize (...) - local v = self[k] - if v == nil then - v = fn (...) - self[k] = v - end - return v - end - }) end) @@ -168,47 +138,6 @@ export (M, "eval (string)", function (s) end) ---- Collect the results of an iterator. --- @function collect --- @func i iterator --- @param ... iterator arguments --- @return results of running the iterator on *arguments* --- @see filter --- @see map --- @usage --- > =collect (std.list.relems, List {"a", "b", "c"}) --- {"c", "b", "a"} -export (M, "collect (func, any*)", function (i, ...) - local t = {} - for e in i (...) do - t[#t + 1] = e - end - return t -end) - - ---- Map a function over an iterator. --- @function map --- @func f function --- @func i iterator --- @param ... iterator arguments --- @treturn table results --- @see filter --- @usage --- > map (function (e) return e % 2 end, std.list.elems, List {1, 2, 3, 4}) --- {1, 0, 1, 0} -export (M, "map (func, func, any*)", function (f, i, ...) - local t = {} - for e in i (...) do - local r = f (e) - if r ~= nil then - table.insert (t, r) - end - end - return t -end) - - --- Filter an iterator with a predicate. -- @function filter -- @func p predicate @@ -249,6 +178,77 @@ export (M, "fold (func, any, func, any*)", function (f, d, i, ...) end) +--- Identity function. +-- @function id +-- @param ... +-- @return the arguments passed to the function +function M.id (...) + return ... +end + + +--- Map a function over an iterator. +-- @function map +-- @func f function +-- @func i iterator +-- @param ... iterator arguments +-- @treturn table results +-- @see filter +-- @usage +-- > map (function (e) return e % 2 end, std.list.elems, List {1, 2, 3, 4}) +-- {1, 0, 1, 0} +export (M, "map (func, func, any*)", function (f, i, ...) + local t = {} + for e in i (...) do + local r = f (e) + if r ~= nil then + table.insert (t, r) + end + end + return t +end) + + +--- Memoize a function, by wrapping it in a functable. +-- +-- To ensure that memoize always returns the same object for the same +-- arguments, it passes arguments to `normalize` (std.string.tostring +-- by default). You may need a more sophisticated function if memoize +-- should handle complicated argument equivalencies. +-- @function memoize +-- @func fn function that returns a single result +-- @func normalize[opt] function to normalize arguments +-- @treturn functable memoized function +-- @usage +-- local fast = memoize (function (...) --[[ slow code ]] end) +export (M, "memoize (func, func?)", function (fn, normalize) + if normalize == nil then + -- Call require here, to avoid pulling in all of 'std.string' + -- even when memoize is never called. + local stringify = require "std.string".tostring + normalize = function (...) return stringify {...} end + end + + return setmetatable ({}, { + __call = function (self, ...) + local k = normalize (...) + local v = self[k] + if v == nil then + v = fn (...) + self[k] = v + end + return v + end + }) +end) + + +--- Signature of memoize `normalize` functions. +-- @function memoize_normalize +-- @param ... arguments +-- @treturn string normalized arguments + + --- Functional forms of infix operators. -- Defined here so that other modules can write to it. -- @table op From 487bc87e8990ab07ce9ac0bb2778ed2fbc29da0d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 4 Jul 2014 17:25:52 +0100 Subject: [PATCH 268/703] specs: avoid tickling sc_error_message_uppercase sanity check. * specs/spec_helper.lua.in (raise): An alias to the error matcher to subvert matching `error` followed by `"[A-Z]` that prevents make dist from completing. * specs/container_spec.yaml (construction): Use the raise alias. Signed-off-by: Gary V. Vaughan --- specs/container_spec.yaml | 4 ++-- specs/spec_helper.lua.in | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index d806c3b..03e06ca 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -12,9 +12,9 @@ specify std.container: - describe construction: - context with table _init: - it diagnoses missing arguments: - expect (Container ()).to_error (badarg (nil, "Container", 1, "table")) + expect (Container ()).to_raise (badarg (nil, "Container", 1, "table")) - it diagnoses too many arguments: - expect (Container ({}, false)).to_error (toomanyarg (nil, "Container", 1, 2)) + expect (Container ({}, false)).to_raise (toomanyarg (nil, "Container", 1, 2)) - context with function _init: - before: Thing = Container { _type = "Thing", _init = function (obj) return obj end } diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 2ef0852..179f3bd 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -271,4 +271,7 @@ do util.concat (alternatives, util.QUOTED) .. ", " end, } + + -- Alias that doesn't tickle sc_error_message_uppercase. + matchers.raise = matchers.error end From bcef63962b2ddd903c1f975c4f4552241cb153e1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 4 Jul 2014 20:21:36 +0100 Subject: [PATCH 269/703] functional: support relational operators in op table. * lib/std/functional.lua (op): Add `<`, `<=`, `>` and `>=`. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 4 +++- lib/std/functional.lua | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 4a57e61..2189e25 100644 --- a/NEWS +++ b/NEWS @@ -17,10 +17,12 @@ Stdlib NEWS - User visible changes `debug.argscheck`) for production code. Similarly `_DEBUG = false` disables those functions too. - - New `std.array` object, for clean and fast queue-like or stack-like + - New `std.vector` object, for clean and fast queue-like or stack-like container management. When alien is installed, and element types are compatible, use alien.buffers for efficient element management. + - New relational `functional.op` fields, `<`, `<=`, `>`, `>=`. + - New `table.elems` and `table.ielems` functions for iterating table values cleanly, and for orthogonality between std.table, std.base, std.list and std.set. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index a452fe3..69f34fd 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -273,6 +273,10 @@ M.op = { ["not"] = function (a) return not a end, ["=="] = function (a, b) return a == b end, ["~="] = function (a, b) return a ~= b end, + ["<"] = function (a, b) return a < b end, + ["<="] = function (a, b) return a <= b end, + [">"] = function (a, b) return a > b end, + [">="] = function (a, b) return a >= b end, } return M From 2a33dc6eb582bacd4bdf8fe0889bc85417e2c0e4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 4 Jul 2014 20:56:46 +0100 Subject: [PATCH 270/703] doc: fix functional.op LDocs. * lib/std/functional.lua (op): Workaround LDoc's inability to render non-alphanumeric @field names. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 69f34fd..7d716ac 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -250,18 +250,26 @@ end) --- Functional forms of infix operators. +-- -- Defined here so that other modules can write to it. +-- +-- 1. `"[]"`: dereference a table +-- 1. `"+"`: addition +-- 1. `"-"`: subtraction +-- 1. `"*"`: multiplication +-- 1. `"/"`: division +-- 1. `"and"`: logical and +-- 1. `"or"`: logical or +-- 1. `"not"`: logical not +-- 1. `"=="`: equality +-- 1. `"~="`: inequality +-- 1. `"<"`: less than +-- 1. `"<="`: less than or equal +-- 1. `">"`: greater than +-- 1. `">="`: greater than or equal -- @table op --- @field [] dereference table index --- @field + addition --- @field - subtraction --- @field * multiplication --- @field / division --- @field and logical and --- @field or logical or --- @field not logical not --- @field == equality --- @field ~= inequality + +--- M.op = { ["[]"] = function (t, s) return t and t[s] or nil end, ["+"] = function (a, b) return a + b end, From 649244639a23d4254e590c9cac5cf12b58760211 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 7 Jul 2014 15:50:07 +0100 Subject: [PATCH 271/703] functional: support multiple return values with memoize. * specs/functional_spec.yaml (memoize): Specify behaviour when passed a function with multiple return values. * lib/std/functional.lua (memoize): Save return values from wrapped function as a table, and unpack it when called again with the same arguments. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++++ lib/std/functional.lua | 16 ++++++++-------- specs/functional_spec.yaml | 6 +++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 2189e25..209c1fa 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,10 @@ Stdlib NEWS - User visible changes values cleanly, and for orthogonality between std.table, std.base, std.list and std.set. + - `functional.memoize` now propagates multiple return values correctly. + This allows memoizing of functions that use the `return nil, "message"` + pattern for error message reporting. + ** Incompatible changes: - `functional.bind` sets fixed positional arguments when called as diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 7d716ac..bb7d6ef 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -211,12 +211,12 @@ end) --- Memoize a function, by wrapping it in a functable. -- --- To ensure that memoize always returns the same object for the same +-- To ensure that memoize always returns the same results for the same -- arguments, it passes arguments to `normalize` (std.string.tostring --- by default). You may need a more sophisticated function if memoize +-- by default). You can specify a more sophisticated function if memoize -- should handle complicated argument equivalencies. -- @function memoize --- @func fn function that returns a single result +-- @func fn function with no side effects -- @func normalize[opt] function to normalize arguments -- @treturn functable memoized function -- @usage @@ -232,12 +232,12 @@ export (M, "memoize (func, func?)", function (fn, normalize) return setmetatable ({}, { __call = function (self, ...) local k = normalize (...) - local v = self[k] - if v == nil then - v = fn (...) - self[k] = v + local t = self[k] + if t == nil then + t = {fn (...)} + self[k] = t end - return v + return unpack (t) end }) end) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 621b255..3fcf96e 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -266,7 +266,9 @@ specify std.functional: fname = "memoize" msg = M.bind (badarg, {this_module, fname}) f = M[fname] - memfn = f (function (x) return {x} end) + memfn = f (function (x) + if x then return {x} else return nil, "bzzt" end + end) - it diagnoses missing arguments: expect (f ()).to_error (msg (1, "function")) @@ -276,6 +278,8 @@ specify std.functional: - it diagnoses too many arguments: expect (f (f, f, false)).to_error (toomanyarg (this_module, fname, 2, 3)) + - it propagates multiple return values: + expect (select (2, memfn (false))).to_be "bzzt" - it returns the same object for the same arguments: t = memfn (1) expect (memfn (1)).to_be (t) From 992fd94a386cc54a1faf05183baa25c14c28835c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 4 Jul 2014 21:54:31 +0100 Subject: [PATCH 272/703] functional: new lambda function. Support compiling an anonymous Lua function from a "lambda string". * specs/functional_spec.yaml (lambda): Specify behaviour for a new lambda function. * lib/std/functional.lua (lambda): Satisfy specification. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 12 ++++++ lib/std/functional.lua | 88 +++++++++++++++++++++++++++++++++++++- specs/functional_spec.yaml | 38 ++++++++++++++++ 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 209c1fa..34b73bb 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,18 @@ Stdlib NEWS - User visible changes This allows memoizing of functions that use the `return nil, "message"` pattern for error message reporting. + - New `functional.lambda` function for compiling lambda strings: + + table.sort (t, functional.lambda "|a,b| a Date: Sun, 6 Jul 2014 21:53:41 +0100 Subject: [PATCH 273/703] refactor: factor functional.op into new std.operator module. * lib/std/functional.lua (op): Move from here... * lib/std/operator.lua (M): ...to here. (M[".."], M["{}"], M[#"], M["~"], M["%"], M["^"]): New operators. * local.mk (dist_luastd_DATA): Add lib/std/operator.lua. * build-aux/config.ld.in (files): Likewise. * local.mk (dist_module_DATA): Add std.operator.html. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 13 +++++++++- build-aux/config.ld.in | 1 + lib/std/functional.lua | 54 ++++++++------------------------------- lib/std/list.lua | 4 --- lib/std/operator.lua | 58 ++++++++++++++++++++++++++++++++++++++++++ local.mk | 2 ++ 6 files changed, 84 insertions(+), 48 deletions(-) create mode 100644 lib/std/operator.lua diff --git a/NEWS b/NEWS index 34b73bb..85b669d 100644 --- a/NEWS +++ b/NEWS @@ -21,7 +21,10 @@ Stdlib NEWS - User visible changes container management. When alien is installed, and element types are compatible, use alien.buffers for efficient element management. - - New relational `functional.op` fields, `<`, `<=`, `>`, `>=`. + - New `std.operator` module, with new functional operators for + concatenation `..`, tablification `{}`, stringification `""`, length + `#` and matching `~`, plus new mathematical operators `%` and `^`, and + relational operators, `<`, `<=`, `>` and `>=`. - New `table.elems` and `table.ielems` functions for iterating table values cleanly, and for orthogonality between std.table, std.base, @@ -80,6 +83,14 @@ Stdlib NEWS - User visible changes old name now gives a deprecation warning on first use, and will be removed entirely in some future release. + - 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. + ** Bug fixes: diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 0fe0a0c..c9cd7ff 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -11,6 +11,7 @@ file = { "../lib/std/functional.lua", "../lib/std/io.lua", "../lib/std/math.lua", + "../lib/std/operator.lua", "../lib/std/package.lua", "../lib/std/strict.lua", "../lib/std/string.lua", diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 2c38ec1..236567e 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -1,10 +1,12 @@ --[[-- Functional programming. + @module std.functional ]] -local export = require "std.base".export +local export = require "std.base".export +local operator = require "std.operator" local M = { "std.functional" } @@ -279,11 +281,11 @@ end) -- -- A valid lambda string takes one of the following forms: -- --- 1. `op`: where *op* is a key in @{op}, equivalent to the stored function +-- 1. `operator`: where *op* is a key in @{std.operator}, equivalent to that operation -- 1. `"=expression"`: equivalent to `function (...) return (expression) end` -- 1. `"|args|expression"`: equivalent to `function (args) return (expression) end` -- --- The second format (starting with `=`) automatically assigns the first +-- The second form (starting with `=`) automatically assigns the first -- nine arguments to parameters `_1` through `_9` for use within the -- expression body. -- @function lambda @@ -295,9 +297,9 @@ end) -- table.sort (t, lambda "= _1 < _2") -- table.sort (t, lambda "|a,b| a"`: greater than --- 1. `">="`: greater than or equal --- @table op - ---- -M.op = { - ["[]"] = function (t, s) return t and t[s] or nil end, - ["+"] = function (a, b) return a + b end, - ["-"] = function (a, b) return a - b end, - ["*"] = function (a, b) return a * b end, - ["/"] = function (a, b) return a / b end, - ["and"] = function (a, b) return a and b end, - ["or"] = function (a, b) return a or b end, - ["not"] = function (a) return not a end, - ["=="] = function (a, b) return a == b end, - ["~="] = function (a, b) return a ~= b end, - ["<"] = function (a, b) return a < b end, - ["<="] = function (a, b) return a <= b end, - [">"] = function (a, b) return a > b end, - [">="] = function (a, b) return a >= b end, -} +-- For backwards compatibility. +M.op = operator + return M diff --git a/lib/std/list.lua b/lib/std/list.lua index 5d8bc6a..3bd09a6 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -773,8 +773,4 @@ List = Object { } --- Function forms of operators -func.op[".."] = concat - - return List diff --git a/lib/std/operator.lua b/lib/std/operator.lua new file mode 100644 index 0000000..cb69bc9 --- /dev/null +++ b/lib/std/operator.lua @@ -0,0 +1,58 @@ +--[[-- + Functional Operators. + + @module std.operator +]] + + +--- Functional forms of Lua operators. +-- +-- Defined here so that other modules can write to it. +-- +-- 1. `..`: concatenation +-- 1. `[]`: dereference a table +-- 1. `{}`: tablification +-- 1. `""`: stringification +-- 1. `~`: string matching +-- 1. `#`: table or string length +-- 1. `+`: addition +-- 1. `-`: subtraction +-- 1. `*`: multiplication +-- 1. `/`: division +-- 1. `%`: modulo +-- 1. `^`: exponentiation +-- 1. `and`: logical and +-- 1. `or`: logical or +-- 1. `not`: logical not +-- 1. `==`: equality +-- 1. `~=`: inequality +-- 1. `<`: less than +-- 1. `<=`: less than or equal +-- 1. `>`: greater than +-- 1. `>=`: greater than or equal +-- @table std.operator + +--- +return { + [".."] = function (a, b) return tostring (a) .. tostring (b) end, + ["[]"] = function (t, s) return t and t[s] or nil end, + ["{}"] = function (...) return {...} end, + ['""'] = function (x) return tostring (x) end, + ["~"] = function (s, p) return string.find (s, p) end, + ["#"] = function (t) return #t end, + ["+"] = function (a, b) return a + b end, + ["-"] = function (a, b) return a - b end, + ["*"] = function (a, b) return a * b end, + ["/"] = function (a, b) return a / b end, + ["%"] = function (a, b) return a % b end, + ["^"] = function (a, b) return math.pow (a, b) end, + ["and"] = function (a, b) return a and b end, + ["or"] = function (a, b) return a or b end, + ["not"] = function (a) return not a end, + ["=="] = function (a, b) return a == b end, + ["~="] = function (a, b) return a ~= b end, + ["<"] = function (a, b) return a < b end, + ["<="] = function (a, b) return a <= b end, + [">"] = function (a, b) return a > b end, + [">="] = function (a, b) return a >= b end, +} diff --git a/local.mk b/local.mk index 547dadb..b790d2c 100644 --- a/local.mk +++ b/local.mk @@ -71,6 +71,7 @@ dist_luastd_DATA = \ lib/std/list.lua \ lib/std/math.lua \ lib/std/object.lua \ + lib/std/operator.lua \ lib/std/optparse.lua \ lib/std/package.lua \ lib/std/set.lua \ @@ -132,6 +133,7 @@ dist_modules_DATA += \ $(srcdir)/doc/modules/std.functional.html \ $(srcdir)/doc/modules/std.io.html \ $(srcdir)/doc/modules/std.math.html \ + $(srcdir)/doc/modules/std.operator.html \ $(srcdir)/doc/modules/std.package.html \ $(srcdir)/doc/modules/std.strict.html \ $(srcdir)/doc/modules/std.string.html \ From c9a6ff745037a7c2666fff86e8f056a2279100e6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 9 Jul 2014 13:03:17 +0100 Subject: [PATCH 274/703] specs: add specifications for std.operator. * specs/operator_spec.yaml: New file. Specify behaviours for operator functions. * specs/specs.mk (specl_SPECS): Add specs/operator_spec.yaml. Signed-off-by: Gary V. Vaughan --- specs/operator_spec.yaml | 251 +++++++++++++++++++++++++++++++++++++++ specs/specs.mk | 1 + 2 files changed, 252 insertions(+) create mode 100644 specs/operator_spec.yaml diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml new file mode 100644 index 0000000..63f6faf --- /dev/null +++ b/specs/operator_spec.yaml @@ -0,0 +1,251 @@ +before: | + this_module = "std.operator" + global_table = "_G" + + M = require (this_module) + +specify std.operator: +- 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 {} + + - context via the std module: + - it does not touch the global table: + expect (show_apis {added_to=global_table, by="std"}). + to_equal {} + + +- describe ..: + - before: + f = M[".."] + + - it stringifies its arguments: + expect (f (1, "")).to_be "1" + expect (f ("", 2)).to_be "2" + - it concatenates its arguments: + expect (f (1, 2)).to_be "12" + +- describe []: + - before: + f = M["[]"] + + - it dereferences a table: + expect (f ({}, 1)).to_be (nil) + expect (f ({"foo", "bar"}, 1)).to_be "foo" + expect (f ({foo = "bar"}, "foo")).to_be "bar" + +- describe {}: + - before: + f = M["{}"] + + - it packs its arguments into a table: + expect (f ()).to_equal {} + expect (f (42)).to_equal {42} + expect (f ("foo", "bar")).to_equal {"foo", "bar"} + expect (f ("a", "b", "c", 1, 2, 3)).to_equal {"a", "b", "c", 1, 2, 3} + +- describe "": + - before: + f = M['""'] + + - it stringifies its argument: + expect (f ()).to_be "nil" + expect (f (42)).to_be "42" + expect (f ("foo")).to_be "foo" + +- describe ~: + - before: + f = M["~"] + + - it finds a pattern match in a string: + haystack = "foo bar baz" + expect (haystack:sub (f (haystack, "ba."))).to_be "bar" + +- 'describe #': + - before: + f = M["#"] + + - it returns the length of a string: + expect (f "1234567890").to_be (10) + - it returns the length of a table: + expect (f {1, 2, 3, 4, 5}).to_be (5) + +- describe +: + - before: + f = M["+"] + + - it returns the sum of its arguments: + expect (f (99, 2)).to_be (99 + 2) + +- describe -: + - before: + f = M["-"] + + - it returns the difference of its arguments: + expect (f (99, 2)).to_be (99 - 2) + +- describe *: + - before: + f = M["*"] + + - it returns the product of its arguments: + expect (f (99, 2)).to_be (99 * 2) + +- describe /: + - before: + f = M["/"] + + - it returns the quotient of its arguments: + expect (f (99, 2)).to_be (99 / 2) + +- describe %: + - before: + f = M["%"] + + - it returns the modulus of its arguments: + expect (f (99, 2)).to_be (99 % 2) + +- describe ^: + - before: + f = M["^"] + + - it returns the power of its arguments: + expect (f (99, 2)).to_be (math.pow (99, 2)) + +- describe and: + - before: + f = M["and"] + + - it returns the logical and of its arguments: + expect (f (false, false)).to_be (false) + expect (f (false, true)).to_be (false) + expect (f (true, false)).to_be (false) + expect (f (true, true)).to_be (true) + - it supports truthy and falsey arguments: + expect (f ()).to_be (nil) + expect (f (0)).to_be (nil) + expect (f (nil, 0)).to_be (nil) + expect (f (0, "false")).to_be ("false") + +- describe or: + - before: + f = M["or"] + + - it returns the logical or of its arguments: + expect (f (false, false)).to_be (false) + expect (f (false, true)).to_be (true) + expect (f (true, false)).to_be (true) + expect (f (true, true)).to_be (true) + - it supports truthy and falsey arguments: + expect (f ()).to_be (nil) + expect (f (0)).to_be (0) + expect (f (nil, 0)).to_be (0) + expect (f (0, "false")).to_be (0) + +- describe not: + - before: + f = M["not"] + + - it returns the logical not of its argument: + expect (f (false)).to_be (true) + expect (f (true)).to_be (false) + - it supports truthy and falsey arguments: + expect (f ()).to_be (true) + expect (f (0)).to_be (false) + +- describe ==: + - before: + f = M["=="] + + - it returns true if the arguments are equal: + expect (f ()).to_be (true) + expect (f ("foo", "foo")).to_be (true) + - it returns false if the arguments are unequal: + expect (f (1)).to_be (false) + expect (f ("foo", "bar")).to_be (false) + +- describe ~=: + - before: + f = M["=="] + + - it returns false if the arguments are equal: + expect (f (1)).to_be (false) + expect (f ("foo", "bar")).to_be (false) + expect (f ({}, {})).to_be (false) + - it returns true if the arguments are unequal: + expect (f ()).to_be (true) + expect (f ("foo", "foo")).to_be (true) + +- describe <: + - before: + f = M["<"] + + - it returns true if the arguments are in ascending order: + expect (f (1, 2)).to_be (true) + expect (f ("a", "b")).to_be (true) + - it returns false if the arguments are not in ascending order: + expect (f (2, 2)).to_be (false) + expect (f (3, 2)).to_be (false) + expect (f ("b", "b")).to_be (false) + expect (f ("c", "b")).to_be (false) + - it supports __lt metamethods: + List = require "std.list" {} + expect (f (List {1, 2, 3}, List {1, 2, 3, 4})).to_be (true) + expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (false) + expect (f (List {1, 2, 4}, List {1, 2, 3})).to_be (false) + +- describe <=: + - before: + f = M["<="] + + - it returns true if the arguments are not in descending order: + expect (f (1, 2)).to_be (true) + expect (f (2, 2)).to_be (true) + expect (f ("a", "b")).to_be (true) + expect (f ("b", "b")).to_be (true) + - it returns false if the arguments are in descending order: + expect (f (3, 2)).to_be (false) + expect (f ("c", "b")).to_be (false) + - it supports __lte metamethods: + List = require "std.list" {} + expect (f (List {1, 2, 3}, List {1, 2, 3, 4})).to_be (true) + expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (true) + expect (f (List {1, 2, 4}, List {1, 2, 3})).to_be (false) + +- describe >: + - before: + f = M[">"] + + - it returns true if the arguments are in descending order: + expect (f (2, 1)).to_be (true) + expect (f ("b", "a")).to_be (true) + - it returns false if the arguments are not in descending order: + expect (f (2, 2)).to_be (false) + expect (f (2, 3)).to_be (false) + expect (f ("b", "b")).to_be (false) + expect (f ("b", "c")).to_be (false) + - it supports __lt metamethods: + List = require "std.list" {} + expect (f (List {1, 2, 3, 4}, List {1, 2, 3})).to_be (true) + expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (false) + expect (f (List {1, 2, 3}, List {1, 2, 4})).to_be (false) + +- describe >=: + - before: + f = M[">="] + + - it returns true if the arguments are not in ascending order: + expect (f (2, 1)).to_be (true) + expect (f (2, 2)).to_be (true) + expect (f ("b", "a")).to_be (true) + expect (f ("b", "b")).to_be (true) + - it returns false if the arguments are in ascending order: + expect (f (2, 3)).to_be (false) + expect (f ("b", "c")).to_be (false) + - it supports __lte metamethods: + List = require "std.list" {} + expect (f (List {1, 2, 3, 4}, List {1, 2, 3})).to_be (true) + expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (true) + expect (f (List {1, 2, 3}, List {1, 2, 4})).to_be (false) diff --git a/specs/specs.mk b/specs/specs.mk index 221b2e3..dd0056b 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -32,6 +32,7 @@ specl_SPECS = \ $(srcdir)/specs/list_spec.yaml \ $(srcdir)/specs/math_spec.yaml \ $(srcdir)/specs/object_spec.yaml \ + $(srcdir)/specs/operator_spec.yaml \ $(srcdir)/specs/optparse_spec.yaml \ $(srcdir)/specs/package_spec.yaml \ $(srcdir)/specs/set_spec.yaml \ From 5429ca7d3dfaae5bcd8af1ec2f379a5f8a06eead Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 7 Jul 2014 12:35:41 +0100 Subject: [PATCH 275/703] std: accept lamda strings as an alternative to functions. * specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/package_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Specify behaviours of API calls that accept functions when given a lambda string instead. * lib/std/functional.lua (Lambda, lambda): Move from here... * lib/std/base.lua (Lambda, lamba): ...to here. (Lambda): Save arguments in table fields. (argcheck): Accept a valid lambda string in lieu of a Lua function. * lib/std/table.lua (sort): When passed a lambda string, pass the associated function to core table.sort. * lib/std/functional.lua (bind, case, collect, curry, filter) (fold, map, memoize): Accept lamda strings in lieu of Lua functions. * lib/std/io.lua (process_files): Likewise. * lib/std/list.lua (filter, foldl, foldr, map, map_with) (zip_with): Likewise. * lib/std/string.lua (render): Likewise. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 5 ++ lib/std/base.lua | 71 ++++++++++++++++++++++++-- lib/std/functional.lua | 100 ++++++++++++------------------------- lib/std/io.lua | 6 ++- lib/std/list.lua | 12 ++++- lib/std/package.lua | 4 +- lib/std/string.lua | 10 +++- lib/std/table.lua | 6 ++- specs/debug_spec.yaml | 3 ++ specs/functional_spec.yaml | 35 +++++++++++-- specs/io_spec.yaml | 7 +++ specs/list_spec.yaml | 10 ++++ specs/package_spec.yaml | 2 + specs/string_spec.yaml | 21 ++++++-- specs/table_spec.yaml | 2 + 15 files changed, 204 insertions(+), 90 deletions(-) diff --git a/NEWS b/NEWS index 85b669d..637a828 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,11 @@ Stdlib NEWS - User visible changes table.sort (t, functional.lambda "<") + - Any stdlib api that accepts a Lua function as an argument now + accepts either a Lua function or a direct lambda string: + + std.table.sort (t, "|a,b| a>b") + ** Incompatible changes: - `functional.bind` sets fixed positional arguments when called as diff --git a/lib/std/base.lua b/lib/std/base.lua index 467cc69..1f57830 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,13 +24,14 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK +local operator = require "std.operator" local argcheck, argerror, argscheck, prototype -- forward declarations --[[ ================= ]]-- ---[[ Helper functions. ]]-- +--[[ Helper Functions. ]]-- --[[ ================= ]]-- @@ -38,6 +39,22 @@ local toomanyarg_fmt = "too many arguments to '%s' (no more than %d expected, got %d)" +--- Construct a new Lambda functable. +-- The lambda string can be retrieved from functable `y` with `tostring (y)`, +-- or it can be executed with `y (args)`. +-- @string value lambda string +-- @func call compiled Lua function +-- @treturn table Lambda functable. +local function Lambda (value, call) + return setmetatable ({ value = value, call = call }, + { + _type = "Lambda", + __call = function (self, ...) return call (...) end, + __tostring = function (self) return 'Lambda "' .. value .. '"' end, + }) +end + + --- Make a shallow copy of a table. -- @tparam table t source table -- @treturn table shallow copy of *t* @@ -245,9 +262,9 @@ end ---[[ ============== ]]-- ---[[ API functions. ]]-- ---[[ ============== ]]-- +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- -- Doc-commented in object.lua @@ -256,6 +273,48 @@ function prototype (o) end +-- Doc-commented in functional.lua +local function lambda (l) + local s + + -- Support operator table lookup. + if operator[l] then + return Lambda (l, operator[l]) + end + + -- Support "|args|expression" format. + local args, body = string.match (l, "^|([^|]*)|%s*(.+)$") + if args and body then + s = "return function (" .. args .. ") return " .. body .. " end" + end + + -- Support "=expression" format. + if not s then + body = l:match "^=%s*(.+)$" + if body then + s = [[ + return function (...) + local _1,_2,_3,_4,_5,_6,_7,_8,_9 = (unpack or table.unpack) {...} + return ]] .. body .. [[ + end + ]] + end + end + + local ok, fn + if s then + ok, fn = pcall (loadstring (s)) + end + + -- Diagnose invalid input. + if not ok then + return nil, "invalid lambda string '" .. l .. "'" + end + + return Lambda (s, fn) +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*. @@ -305,7 +364,8 @@ if _ARGCHECK then elseif check == "function" or check == "func" then if actualtype == "function" or - (getmetatable (actual) or {}).__call ~= nil + (getmetatable (actual) or {}).__call ~= nil or + (actualtype == "string" and lambda (actual) ~= nil) then ok = true end @@ -585,6 +645,7 @@ local M = { export = export, getmetamethod = getmetamethod, ielems = ielems, + lambda = lambda, leaves = leaves, prototype = prototype, split = split, diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 236567e..d3a084f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -5,36 +5,12 @@ ]] -local export = require "std.base".export -local operator = require "std.operator" +local base = require "std.base" -local M = { "std.functional" } - - - ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - ---- Construct a new Lambda functable. --- The lambda string can be retrieved from functable `y` with `tostring (y)`, --- or it can be executed with `y (args)`. --- @string value lambda string --- @func call compiled Lua function --- @treturn table Lambda functable. -local function Lambda (value, call) - return setmetatable ({}, { - _type = "Lambda", - __call = function (self, ...) return call (...) end, - __tostring = function (self) return 'Lambda "' .. value .. '"' end, - }) -end +local export, lambda = base.export, base.lambda +local M = { "std.functional" } ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- --- Partially apply a function. @@ -51,6 +27,8 @@ local bind = export (M, "bind (func, any?*)", function (f, ...) if type (fix[1]) == "table" and fix[2] == nil then fix = fix[1] end + f = type (f) == "string" and lambda (f) or f + return function (...) local arg = {} for i, v in pairs (fix) do @@ -81,8 +59,9 @@ end) -- function (s) error ("unhandled type: "..s) end, -- }) export (M, "case (any?, #table)", function (with, branches) - local fn = branches[with] or branches[1] - if fn then return fn (with) end + local f = branches[with] or branches[1] + f = type (f) == "string" and lambda (f) or f + if f then return f (with) end end) @@ -97,6 +76,8 @@ end) -- > =collect (std.list.relems, List {"a", "b", "c"}) -- {"c", "b", "a"} export (M, "collect (func, any*)", function (i, ...) + i = type (i) == "string" and lambda (i) or i + local t = {} for e in i (...) do t[#t + 1] = e @@ -124,6 +105,11 @@ end) export (M, "compose (func*)", function (...) local arg = {...} local fns, n = arg, #arg + for i = 1, n do + local f = fns[i] + fns[i] = type (f) == "string" and lambda (f) or f + end + return function (...) local arg = {...} for i = 1, n do @@ -146,6 +132,8 @@ end) -- 100 98 local curry curry = export (M, "curry (func, int)", function (f, n) + f = type (f) == "string" and lambda (f) or f + if n <= 1 then return f else @@ -174,9 +162,12 @@ end) -- @treturn table elements e for which `p (e)` is not falsey. -- @see collect -- @usage --- > filter (function (e) return e % 2 == 0 end, std.list.elems, List {1, 2, 3, 4}) +-- > filter ("|e| e%2==0", std.list.elems, List {1, 2, 3, 4}) -- {2, 4} export (M, "filter (func, func, any*)", function (p, i, ...) + p = type (p) == "string" and lambda (p) or p + i = type (i) == "string" and lambda (i) or i + local t = {} for e in i (...) do if p (e) then @@ -198,6 +189,9 @@ end) -- @see std.list.foldr -- @usage fold (math.pow, 1, std.list.elems, List {2, 3, 4}) export (M, "fold (func, any, func, any*)", function (f, d, i, ...) + f = type (f) == "string" and lambda (f) or f + i = type (i) == "string" and lambda (i) or i + local r = d for e in i (...) do r = f (r, e) @@ -226,6 +220,9 @@ end -- > map (function (e) return e % 2 end, std.list.elems, List {1, 2, 3, 4}) -- {1, 0, 1, 0} export (M, "map (func, func, any*)", function (f, i, ...) + f = type (f) == "string" and lambda (f) or f + i = type (i) == "string" and lambda (i) or i + local t = {} for e in i (...) do local r = f (e) @@ -250,6 +247,9 @@ end) -- @usage -- local fast = memoize (function (...) --[[ slow code ]] end) local memoize = export (M, "memoize (func, func?)", function (fn, normalize) + fn = type (fn) == "string" and lambda (fn) or fn + normalize = type (normalize) == "string" and lambda (normalize) or normalize + if normalize == nil then -- Call require here, to avoid pulling in all of 'std.string' -- even when memoize is never called. @@ -296,47 +296,11 @@ end) -- table.sort (t, lambda "<") -- table.sort (t, lambda "= _1 < _2") -- table.sort (t, lambda "|a,b| a0", + "|v| function (s,k) return k Date: Wed, 9 Jul 2014 13:36:43 +0100 Subject: [PATCH 276/703] maint: clean up NEWS. * NEWS: Update bitrotted recent entries to match reality. Signed-off-by: Gary V. Vaughan --- NEWS | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 637a828..3dfcbf0 100644 --- a/NEWS +++ b/NEWS @@ -42,7 +42,7 @@ Stdlib NEWS - User visible changes table.sort (t, functional.lambda "= _1 < _2" - or, equivalently using `functional.op` references: + or, equivalently using `std.operator` references: table.sort (t, functional.lambda "<") @@ -105,16 +105,16 @@ Stdlib NEWS - User visible changes - `optparse.on` now works with `std.strict` enabled. - - `string.require_version` now extracts the first substring made - entirely of digits and periods from the required module's version - string before splitting on period. That means, for version strings - like stdlib's "General Lua Libraries / 41" we now correctly compare - just the numeric part against specified version range rather than - an ASCII comparison of the whole thing as before! + - `string.require` (nee `require_version`) now extracts the first + substring made entirely of digits and periods from the required + module's version string before splitting on period. That means, for + version strings like stdlib's "General Lua Libraries / 41" 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 `string.require_version` - looks first in `module.version` and then `module._VERSION` to match - the long-standing implementation. + - The documentation now correcly notes that `string.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 From a75308489884f1173644d05a53f854785354ac11 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 10 Jul 2014 10:18:38 +0100 Subject: [PATCH 277/703] functional: make nop an official functional method. * specs/functional_spec.yaml (nop): Specify behaviour of new nop module method. * specs/spec_helper.lua.in (nop): Remove one-off nop declaration. * specs/string_spec.yaml (finds): Specify in-situ nop. * specs/base_spec.yaml (std.base): Likewise. * lib/std/base.lua (nop): Declare an official nop function. * lib/std/functional.lua (nop): Re-export std.base.nop. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/base.lua | 7 +++++-- lib/std/functional.lua | 10 +++++++++- specs/base_spec.yaml | 2 ++ specs/functional_spec.yaml | 10 ++++++++++ specs/spec_helper.lua.in | 4 ---- specs/string_spec.yaml | 2 +- 7 files changed, 30 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index 3dfcbf0..608e581 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,9 @@ Stdlib NEWS - User visible changes values cleanly, and for orthogonality between std.table, std.base, std.list and std.set. + - New `functional.nop` function, for use where a function is required + but no work should be done. + - `functional.memoize` now propagates multiple return values correctly. This allows memoizing of functions that use the `return nil, "message"` pattern for error message reporting. diff --git a/lib/std/base.lua b/lib/std/base.lua index 1f57830..605d94a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -273,6 +273,10 @@ function prototype (o) end +-- Doc-commented in functional.lua +local function nop () end + + -- Doc-commented in functional.lua local function lambda (l) local s @@ -423,8 +427,6 @@ if _ARGCHECK then else - local function nop () end - -- Turn off argument checking if _DEBUG is false, or a table containing -- a false valued `argcheck` field. @@ -647,6 +649,7 @@ local M = { ielems = ielems, lambda = lambda, leaves = leaves, + nop = nop, prototype = prototype, split = split, toomanyarg_fmt = toomanyarg_fmt, diff --git a/lib/std/functional.lua b/lib/std/functional.lua index d3a084f..81434e4 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -7,7 +7,7 @@ local base = require "std.base" -local export, lambda = base.export, base.lambda +local export, lambda, nop = base.export, base.lambda, base.nop local M = { "std.functional" } @@ -299,6 +299,14 @@ end) export (M, "lambda (string)", memoize (lambda, function (s) return s end)) +--- No operation. +-- This function ignores all arguments, and returns no values. +-- @function nop +-- @usage +-- if unsupported then vtable["memrmem"] = nop end +M.nop = nop + + -- For backwards compatibility. M.op = require "std.operator" diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 7201b9a..fc41c16 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -3,6 +3,8 @@ before: M = require (this_module) + nop = M.nop + specify std.base: - describe deprecate: - before: | diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 75b1518..7efa69b 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -366,3 +366,13 @@ specify std.functional: memfn = f ("{}", '..') expect (memfn (1, 2)).to_be (memfn (1, 1 + 1)) expect (memfn (1, 2)).not_to_be (memfn (2, 1)) + +- describe nop: + - before: + f = M.nop + - it accepts any number of arguments: + expect (f ()).to_be (nil) + expect (f (false)).to_be (nil) + expect (f (1, 2, 3, nil, "str", {}, f)).to_be (nil) + - it returns no values: + expect (f (1, "two", false)).to_be (nil) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 179f3bd..3c61c9b 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -12,10 +12,6 @@ package.path = std.package.normalize ("lib/?.lua", package.path) local LUA = "@LUA@" --- Null operation function. -nop = function () 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. diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index cad760a..eddea86 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -235,7 +235,7 @@ specify std.string: expect (f ("string", "pattern", nil, "plain")). to_error (msg (4, "boolean, :plain or nil", "string")) - it diagnoses too many arguments: - expect (f ("string", "pattern", nil, false, nop)). + expect (f ("string", "pattern", nil, false, function () end)). to_error (toomanyarg (this_module, fname, 4, 5)) - context given a complex nested list: From f5e5a05e218ecda9d7c4997b5443fe39e68817cb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 10 Jul 2014 10:45:53 +0100 Subject: [PATCH 278/703] doc: improve functional module LDocs. * lib/std/functional.lua: Improve module header LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 81434e4..0608621 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -1,6 +1,21 @@ --[[-- Functional programming. + A selection of higher-order functions to enable a functional style of + programming in Lua. + + Of special note is the `lambda` function: when calling other stdlib apis + that accept a function argument, the call to `lambda` itself is not + required. The following are equivalent: + + std.table.sort (t, lambda '|a,b| a Date: Thu, 10 Jul 2014 11:03:08 +0100 Subject: [PATCH 279/703] doc: improve debug module LDocs. * lib/std/debug.lua (_DEBUG): Document default field values. (argerror): Document interaction between function and lambda strings. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index f8e276f..161e088 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -67,9 +67,9 @@ local tabify = functional.compose ( -- (equivalent to {level = 1}), or as documented below. -- @class table -- @name _DEBUG --- @field argcheck honor argcheck and argscheck calls --- @field call do call trace debugging --- @field level debugging level +-- @field[opt=true] argcheck honor argcheck and argscheck calls +-- @field[opt=false] call do call trace debugging +-- @field[opt=1] level debugging level -- @usage _DEBUG = { argcheck = false, level = 9 } @@ -182,13 +182,19 @@ export (M, "argerror (string, int, string?, int?)", base.argerror) -- #table accept any non-empty table -- any accept any non-nil argument type -- file accept an open file object --- function accept a function, or object with a __call metamethod +-- function accept a function, lambda string, or object with a __call metamethod -- int accept an integer valued number -- list accept a table where all keys are a contiguous 1-based integer range -- #list accept any non-empty list -- object accept any std.Object derived type -- :foo accept only the exact string ":foo", works for any :-prefixed string -- +-- The `function` type does not compile lambda strings, so you should +-- always do that yourself whenever you accept a `function` argument: +-- +-- argcheck ("table.sort", 2, "function|nil") +-- fn = type (fn) == "string" and lambda (fn) or fn +-- -- The `:foo` format allows for type-checking of self-documenting -- boolean-like constant string parameters predicated on `nil` versus -- `:option` instead of `false` versus `true`. Or you could support From 3acb2935cab096bbcf9034d68bc792b3bfe0361b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 10 Jul 2014 11:16:40 +0100 Subject: [PATCH 280/703] doc: add LDocs for functional.lambda return functables. * lib/std/functional.lua (Lambda): Add LDocs. (lambda): Document return type correctly. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 0608621..29534d0 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -292,6 +292,16 @@ end) -- @treturn string normalized arguments +--- A compiled lambda string returned by @{lambda}. +-- +-- @{lambda} returns a functable with this signature, which has a +-- metatable that returns `value` when passed to @{tostring} and +-- can be called like any other function. +-- @table Lambda +-- @func call compiled Lua function +-- @string value original lambda string + + --- Compile a lambda string into a Lua function. -- -- A valid lambda string takes one of the following forms: @@ -305,7 +315,7 @@ end) -- expression body. -- @function lambda -- @string s a lambda string --- @treturn function compiled lambda string +-- @treturn table compiled lambda string, can be called like a function -- @usage -- -- The following are all equivalent: -- table.sort (t, lambda "<") From 04099fd650dbab68ba7bcc57ad84a73e87f10ff5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 10 Jul 2014 11:22:38 +0100 Subject: [PATCH 281/703] refactor: simplify std.base.lambda. * lib/std/base.lua (lambda): Use `unpack` unconditionally. Save lambda string in Lambda object. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 605d94a..47d721f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -298,7 +298,7 @@ local function lambda (l) if body then s = [[ return function (...) - local _1,_2,_3,_4,_5,_6,_7,_8,_9 = (unpack or table.unpack) {...} + local _1,_2,_3,_4,_5,_6,_7,_8,_9 = unpack {...} return ]] .. body .. [[ end ]] @@ -315,7 +315,7 @@ local function lambda (l) return nil, "invalid lambda string '" .. l .. "'" end - return Lambda (s, fn) + return Lambda (l, fn) end From a92dd7e3f07f528bd60165b6f07e60cfd010c7b4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 10 Jul 2014 11:32:19 +0100 Subject: [PATCH 282/703] doc: clarify use of compiled lambda strings. * lib/std/functional.lua (Lambda): Add LDocs cross-references, and a usage example with call field. (lambda): Fix usage example not to show calling core Lua table.sort with a functable! Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 29534d0..03a0a70 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -300,6 +300,11 @@ end) -- @table Lambda -- @func call compiled Lua function -- @string value original lambda string +-- @see string.tostring +-- @see object.prototype +-- @usage +-- -- Core Lua apis accept a function, but not a functable +-- table.sort (t, (lambda "<").call) --- Compile a lambda string into a Lua function. @@ -318,9 +323,9 @@ end) -- @treturn table compiled lambda string, can be called like a function -- @usage -- -- The following are all equivalent: --- table.sort (t, lambda "<") --- table.sort (t, lambda "= _1 < _2") --- table.sort (t, lambda "|a,b| a Date: Thu, 10 Jul 2014 14:35:17 +0100 Subject: [PATCH 283/703] doc: object and container _functions fields are optional. * lib/std/container.lua (Container): _functions field is optional. * lib/std/object.lua (Object): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 2 +- lib/std/object.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 9001a71..d61e020 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -315,7 +315,7 @@ end -- @{std.object.prototype} -- @tfield table|function _init a table of field names, or -- initialisation function, used by @{__call} --- @tfield nil|table _functions a table of module functions not copied +-- @tfield[opt=nil] table _functions a table of module functions not copied -- by @{std.object.__call} return setmetatable ({ diff --git a/lib/std/object.lua b/lib/std/object.lua index d8adfe8..cb15d77 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -94,7 +94,7 @@ local getmetamethod, prototype = base.getmetamethod, base.prototype -- @string[opt="Object"] _type type of Object, returned by @{prototype} -- @tfield table|function _init a table of field names, or -- initialisation function, used by @{clone} --- @tfield nil|table _functions a table of module functions not copied +-- @tfield[opt=nil] table _functions a table of module functions not copied -- by @{std.object.__call} return Container { _type = "Object", From 131dd6d1aedbd31c03db1507f5b4aeb6cd855085 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 11 Jul 2014 08:02:58 +0100 Subject: [PATCH 284/703] base: provide better errors from exported object methods. * specs/base_spec.yaml (export): Specify behaviour of exported object methods. * lib/std/base.lua (export): Use separator ':' between module name and method name when dealing with methods, as opposed to '.' when dealing with functions. Count method arguments starting at '0' for self in error messages. * build-aux/sanity-cfg.mk (sc_error_message_uppercase): Add specs/base_spec.yaml. Signed-off-by: Gary V. Vaughan --- build-aux/sanity-cfg.mk | 2 +- lib/std/base.lua | 9 ++++++++- specs/base_spec.yaml | 29 +++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/build-aux/sanity-cfg.mk b/build-aux/sanity-cfg.mk index 893fead..bfc41f7 100644 --- a/build-aux/sanity-cfg.mk +++ b/build-aux/sanity-cfg.mk @@ -1,3 +1,3 @@ -exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua|specs/debug_spec.yaml$$ +exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua|specs/(base|debug)_spec.yaml$$ EXTRA_DIST += build-aux/sanity-cfg.mk diff --git a/lib/std/base.lua b/lib/std/base.lua index 47d721f..39a9e7b 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -522,7 +522,7 @@ local function export (M, decl, fn, ...) argerror (fname, 2, "at least 1 argument type expected, got 0") end - local name = M[1] .. "." .. name + local name = M[1] .. (M[2] and ":" or ".") .. name -- If the final element of types ends with "*", then set max to a -- sentinel value to denote type-checking of *all* remaining @@ -542,6 +542,13 @@ local function export (M, decl, fn, ...) local args = {...} local argc, bestmismatch, at = arglen (args), 0, 0 + -- For object methods, report type mismatch on self as argument 0. + if M[2] then + argcheck (name, 0, M[2], args[1], 2) + table.remove (args, 1) + argc = argc - 1 + end + for i, types in ipairs (type_specs) do local mismatch = match (types, args, max == math.huge) if mismatch == nil then diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index fc41c16..7d3bdca 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -192,3 +192,32 @@ specify std.base: - it accepts correct argument types: expect (chk ("two")).to_equal (MAGIC) -- should be .to_be? expect (chk (1, "two")).to_equal (MAGIC) -- should be .to_be? + - context when checking self on an object method: + - before: + fake_module = "base_spec.yaml" + M = { fake_module, "Object" } + fname = "chk_method" + msg = function (...) + return badarg (nil, fake_module .. ":" .. fname, ...) + end + f (M, "chk_method (string)", function (self, x) return x end) + Object = require "std.object" {} + Bad = Object { _type = "Bad" } + obj = Object { chk = M.chk_method } + - it diagnoses missing arguments: + expect (obj.chk ()).to_error (msg (0, "Object")) + expect (obj:chk ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (obj.chk ({})).to_error (msg (0, "Object", "empty table")) + expect (obj.chk (Bad)).to_error (msg (0, "Object", "Bad")) + expect (obj:chk ({})).to_error (msg (1, "string", "empty table")) + expect (obj:chk (obj)).to_error (msg (1, "string", "Object")) + - it diagnoses too many arguments: + expect (obj.chk (obj, "str", false)). + to_error (toomanyarg (nil, fake_module .. ":" .. fname, 1, 2)) + expect (obj:chk ("str", false)). + to_error (toomanyarg (nil, fake_module .. ":" .. fname, 1, 2)) + - it accepts correct argument types: + expect (obj.chk (obj, "str")).to_be "str" + expect (obj.chk (Object, "str")).to_be "str" + expect (obj:chk ("str")).to_be "str" From 3de3a9268730b042314f7fb1035f6a81eeaa48c4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 11 Jul 2014 08:19:39 +0100 Subject: [PATCH 285/703] specs: don't load std.object for std.list spec examples. * specs/spec_helper.lua.in (prototype): Copied from lib/std/base.lua. * specs/list_spec.yaml: Don't load 'std.object', use prototype from spec_helper. Signed-off-by: Gary V. Vaughan --- specs/list_spec.yaml | 59 ++++++++++++++++++++-------------------- specs/spec_helper.lua.in | 7 +++++ 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 522fbb1..d4bb7ee 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1,5 +1,4 @@ before: - Object = require "std.object" list = require "std.list" List = list {} l = List {"foo", "bar", "baz"} @@ -17,25 +16,25 @@ specify std.list: - it constructs a new list: l = List:clone {} expect (l).not_to_be (List) - expect (Object.type (l)).to_be "List" + expect (prototype (l)).to_be "List" - it reuses the List metatable: l, m = List:clone {"l"}, List:clone {"m"} expect (getmetatable (l)).to_be (getmetatable (m)) - - it initialises list with constructor parameters: + - it initialises List with constructor parameters: m = List:clone {"foo", "bar", "baz"} expect (m).to_equal (l) - it serves as a prototype for new instances: - obj = l:clone {} - expect (Object.type (obj)).to_be "List" - expect (obj).to_equal (l) - expect (getmetatable (obj)).to_be (getmetatable (l)) + m = l:clone {} + expect (prototype (m)).to_be "List" + expect (m).to_equal (l) + expect (getmetatable (m)).to_be (getmetatable (l)) # List {args} is just syntactic sugar for List:clone {args} - context from List object prototype: - it constructs a new list: l = List {} expect (l).not_to_be (List) - expect (Object.type (l)).to_be "List" + expect (prototype (l)).to_be "List" - it reuses the List metatable: l, m = List {"l"}, List {"m"} expect (getmetatable (l)).to_be (getmetatable (m)) @@ -43,16 +42,16 @@ specify std.list: m = List {"foo", "bar", "baz"} expect (m).to_equal (l) - it serves as a prototype for new instances: - obj = l {} - expect (Object.type (obj)).to_be "List" - expect (obj).to_equal (l) - expect (getmetatable (obj)).to_be (getmetatable (l)) + m = l {} + expect (prototype (m)).to_be "List" + expect (m).to_equal (l) + expect (getmetatable (m)).to_be (getmetatable (l)) - describe metatable propagation: - it reuses the metatable for List constructed objects: - obj = List {"foo", "bar"} - expect (getmetatable (obj)).to_be (getmetatable (l)) + m = List {"foo", "bar"} + expect (getmetatable (m)).to_be (getmetatable (l)) - describe append: @@ -69,7 +68,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: l = l:append ("quux") - expect (Object.type (l)).to_be "List" + expect (prototype (l)).to_be "List" - it works for an empty list: l = List {} expect (l:append ("quux")).to_equal (List {"quux"}) @@ -79,7 +78,7 @@ specify std.list: - context when called as a list metamethod: - it returns a list object: l = l + "quux" - expect (Object.type (l)).to_be "List" + expect (prototype (l)).to_be "List" - it works for an empty list: l = List {} expect (l + "quux").to_equal (List {"quux"}) @@ -180,7 +179,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: l = l:concat (List {"baz"}) - expect (Object.type (l)).to_be "List" + expect (prototype (l)).to_be "List" - it works for an empty list: l = List {} expect (l:concat (List {"baz"})).to_equal (List {"baz"}) @@ -192,7 +191,7 @@ specify std.list: - context whne called as a list metamethod: - it returns a list object: l = l .. List {"baz"} - expect (Object.type (l)).to_be "List" + expect (prototype (l)).to_be "List" - it works for an empty list: l = List {} expect (l .. List {"baz"}).to_equal (List {"baz"}) @@ -217,7 +216,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: l = l:cons "quux" - expect (Object.type (l)).to_be "List" + expect (prototype (l)).to_be "List" - it works for empty lists: l = List {} expect (l:cons "quux").to_equal (List {"quux"}) @@ -247,7 +246,7 @@ specify std.list: to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got string at index 2)" - context when called as a list object method: - it returns a primitive table: - expect (Object.type (l:depair ())).to_be "table" + expect (prototype (l:depair ())).to_be "table" - it works with an empty list: l = List {} expect (l:depair ()).to_equal {} @@ -296,7 +295,7 @@ specify std.list: expect (f (false)). to_error "bad argument #1 to 'std.list.enpair' (table expected, got boolean)" - it returns a list object: - expect (Object.type (f (t))).to_be "List" + expect (prototype (f (t))).to_be "List" - it works for an empty table: expect (f {}).to_equal (List {}) - it turns a table into a list of pairs: @@ -324,7 +323,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: m = l:filter (p) - expect (Object.type (m)).to_be "List" + expect (prototype (m)).to_be "List" - it works for an empty list: l = List {} expect (l:filter (p)).to_equal (List {}) @@ -349,7 +348,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: m = List.flatten (l) - expect (Object.type (m)).to_be "List" + expect (prototype (m)).to_be "List" - it works for an empty list: l = List {} expect (l:flatten ()).to_equal (List {}) @@ -516,7 +515,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: m = l:map (sq) - expect (Object.type (m)).to_be "List" + expect (prototype (m)).to_be "List" - it works for an empty list: l = List {} expect (l:map (sq)).to_equal (List {}) @@ -556,7 +555,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: m = l:map_with (n) - expect (Object.type (m)).to_be "List" + expect (prototype (m)).to_be "List" - it works for an empty list: l = List {} expect (l:map_with (n)).to_equal (List {}) @@ -595,7 +594,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: p = l:project ("third") - expect (Object.type (p)).to_be "List" + expect (prototype (p)).to_be "List" - it works with an empty list: l = List {} expect (l:project ("third")).to_equal (List {}) @@ -654,7 +653,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: - expect (Object.type (l:rep (3))).to_be "List" + expect (prototype (l:rep (3))).to_be "List" - it works for an empty list: l = List {} expect (l:rep (99)).to_equal (List {}) @@ -677,7 +676,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: - expect (Object.type (l:reverse ())).to_be "List" + expect (prototype (l:reverse ())).to_be "List" - it works for an empty list: l = List {} expect (l:reverse ()).to_equal (List {}) @@ -725,7 +724,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: | - expect (Object.type (l:sub (1, 1))).to_be "List" + expect (prototype (l:sub (1, 1))).to_be "List" - it makes a list from a subrange of another list: | expect (l:sub (2, 5)).to_equal (List {2, 3, 4, 5}) - it truncates the result if 'to' argument is too large: | @@ -756,7 +755,7 @@ specify std.list: - context when called as a list object method: - it returns a list object: | - expect (Object.type (l:tail ())).to_be "List" + expect (prototype (l:tail ())).to_be "List" - it makes a new list with the first element removed: | expect (l:tail ()).to_equal (List {2, 3, 4, 5, 6, 7}) - it works for an empty list: | diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 3c61c9b..5cf495f 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -12,6 +12,13 @@ package.path = std.package.normalize ("lib/?.lua", package.path) local LUA = "@LUA@" +-- A copy of base.lua:prototype, so that an unloadable base.lua doesn't +-- prevent everything else from working. +function prototype (o) + return (getmetatable (o) or {})._type or io.type (o) or type (o) +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. From 8124b547b76f9c4a334c0077b78a2a22cf23bce9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 11 Jul 2014 11:50:09 +0100 Subject: [PATCH 286/703] maint: revert automatic lambda string compilation. * lib/std/base.lua (lambda): Core Lua APIs require actual functions, so to be useful for passing lambda strings to core APIs and stdlib APIs alike, return a raw function rather than a functable. (argcheck): Don't accept a compilable lambda string where a function argument is expected. * lib/std/debug.lua (lambda, argcheck): Adjust LDocs. * lib/std/functional.lua: Likewise. (bind, case, collect, compose, curry, filter, fold, map) (memoize): Remove lambda argument compilation. * lib/std/io.lua (process_files): Likewise. * lib/std/list.lua (filter, foldl, foldr, map, map_with) (zip_with): Likewise. * lib/std/package.lua (mappath): Likewise. * lib/std/string.lua (render): Likewise. * lib/std/table.lua (sort): Likewise. * specs/debug_spec.yaml, specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/package_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Adjust lambda specs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 5 ----- lib/std/base.lua | 23 +++---------------- lib/std/debug.lua | 8 +------ lib/std/functional.lua | 46 -------------------------------------- lib/std/io.lua | 6 ++--- lib/std/list.lua | 16 ++----------- lib/std/package.lua | 4 +--- lib/std/string.lua | 10 ++------- lib/std/table.lua | 6 ++--- specs/debug_spec.yaml | 3 --- specs/functional_spec.yaml | 35 ++++------------------------- specs/io_spec.yaml | 4 ---- specs/list_spec.yaml | 10 --------- specs/package_spec.yaml | 2 -- specs/string_spec.yaml | 14 ------------ specs/table_spec.yaml | 2 -- 16 files changed, 17 insertions(+), 177 deletions(-) diff --git a/NEWS b/NEWS index 608e581..237852c 100644 --- a/NEWS +++ b/NEWS @@ -49,11 +49,6 @@ Stdlib NEWS - User visible changes table.sort (t, functional.lambda "<") - - Any stdlib api that accepts a Lua function as an argument now - accepts either a Lua function or a direct lambda string: - - std.table.sort (t, "|a,b| a>b") - ** Incompatible changes: - `functional.bind` sets fixed positional arguments when called as diff --git a/lib/std/base.lua b/lib/std/base.lua index 39a9e7b..195cedd 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -39,22 +39,6 @@ local toomanyarg_fmt = "too many arguments to '%s' (no more than %d expected, got %d)" ---- Construct a new Lambda functable. --- The lambda string can be retrieved from functable `y` with `tostring (y)`, --- or it can be executed with `y (args)`. --- @string value lambda string --- @func call compiled Lua function --- @treturn table Lambda functable. -local function Lambda (value, call) - return setmetatable ({ value = value, call = call }, - { - _type = "Lambda", - __call = function (self, ...) return call (...) end, - __tostring = function (self) return 'Lambda "' .. value .. '"' end, - }) -end - - --- Make a shallow copy of a table. -- @tparam table t source table -- @treturn table shallow copy of *t* @@ -283,7 +267,7 @@ local function lambda (l) -- Support operator table lookup. if operator[l] then - return Lambda (l, operator[l]) + return operator[l] end -- Support "|args|expression" format. @@ -315,7 +299,7 @@ local function lambda (l) return nil, "invalid lambda string '" .. l .. "'" end - return Lambda (l, fn) + return fn end @@ -368,8 +352,7 @@ if _ARGCHECK then elseif check == "function" or check == "func" then if actualtype == "function" or - (getmetatable (actual) or {}).__call ~= nil or - (actualtype == "string" and lambda (actual) ~= nil) + (getmetatable (actual) or {}).__call ~= nil then ok = true end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 161e088..fca4734 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -182,19 +182,13 @@ export (M, "argerror (string, int, string?, int?)", base.argerror) -- #table accept any non-empty table -- any accept any non-nil argument type -- file accept an open file object --- function accept a function, lambda string, or object with a __call metamethod +-- function accept a function, or object with a __call metamethod -- int accept an integer valued number -- list accept a table where all keys are a contiguous 1-based integer range -- #list accept any non-empty list -- object accept any std.Object derived type -- :foo accept only the exact string ":foo", works for any :-prefixed string -- --- The `function` type does not compile lambda strings, so you should --- always do that yourself whenever you accept a `function` argument: --- --- argcheck ("table.sort", 2, "function|nil") --- fn = type (fn) == "string" and lambda (fn) or fn --- -- The `:foo` format allows for type-checking of self-documenting -- boolean-like constant string parameters predicated on `nil` versus -- `:option` instead of `false` versus `true`. Or you could support diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 03a0a70..8a73cb3 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -4,18 +4,6 @@ A selection of higher-order functions to enable a functional style of programming in Lua. - Of special note is the `lambda` function: when calling other stdlib apis - that accept a function argument, the call to `lambda` itself is not - required. The following are equivalent: - - std.table.sort (t, lambda '|a,b| a =collect (std.list.relems, List {"a", "b", "c"}) -- {"c", "b", "a"} export (M, "collect (func, any*)", function (i, ...) - i = type (i) == "string" and lambda (i) or i - local t = {} for e in i (...) do t[#t + 1] = e @@ -122,7 +106,6 @@ export (M, "compose (func*)", function (...) local fns, n = arg, #arg for i = 1, n do local f = fns[i] - fns[i] = type (f) == "string" and lambda (f) or f end return function (...) @@ -147,8 +130,6 @@ end) -- 100 98 local curry curry = export (M, "curry (func, int)", function (f, n) - f = type (f) == "string" and lambda (f) or f - if n <= 1 then return f else @@ -180,9 +161,6 @@ end) -- > filter ("|e| e%2==0", std.list.elems, List {1, 2, 3, 4}) -- {2, 4} export (M, "filter (func, func, any*)", function (p, i, ...) - p = type (p) == "string" and lambda (p) or p - i = type (i) == "string" and lambda (i) or i - local t = {} for e in i (...) do if p (e) then @@ -204,9 +182,6 @@ end) -- @see std.list.foldr -- @usage fold (math.pow, 1, std.list.elems, List {2, 3, 4}) export (M, "fold (func, any, func, any*)", function (f, d, i, ...) - f = type (f) == "string" and lambda (f) or f - i = type (i) == "string" and lambda (i) or i - local r = d for e in i (...) do r = f (r, e) @@ -235,9 +210,6 @@ end -- > map (function (e) return e % 2 end, std.list.elems, List {1, 2, 3, 4}) -- {1, 0, 1, 0} export (M, "map (func, func, any*)", function (f, i, ...) - f = type (f) == "string" and lambda (f) or f - i = type (i) == "string" and lambda (i) or i - local t = {} for e in i (...) do local r = f (e) @@ -262,9 +234,6 @@ end) -- @usage -- local fast = memoize (function (...) --[[ slow code ]] end) local memoize = export (M, "memoize (func, func?)", function (fn, normalize) - fn = type (fn) == "string" and lambda (fn) or fn - normalize = type (normalize) == "string" and lambda (normalize) or normalize - if normalize == nil then -- Call require here, to avoid pulling in all of 'std.string' -- even when memoize is never called. @@ -292,21 +261,6 @@ end) -- @treturn string normalized arguments ---- A compiled lambda string returned by @{lambda}. --- --- @{lambda} returns a functable with this signature, which has a --- metatable that returns `value` when passed to @{tostring} and --- can be called like any other function. --- @table Lambda --- @func call compiled Lua function --- @string value original lambda string --- @see string.tostring --- @see object.prototype --- @usage --- -- Core Lua apis accept a function, but not a functable --- table.sort (t, (lambda "<").call) - - --- Compile a lambda string into a Lua function. -- -- A valid lambda string takes one of the following forms: diff --git a/lib/std/io.lua b/lib/std/io.lua index c025fe4..0edbe3c 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -17,8 +17,8 @@ local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } -local argerror, export, lambda, leaves, split = - base.argerror, base.export, base.lambda, base.leaves, base.split +local argerror, export, leaves, split = + base.argerror, base.export, base.leaves, base.split local M = { "std.io" } @@ -194,8 +194,6 @@ end) -- local io = require "std.io" -- io.process_files (function () io.write (io.slurp ()) end) export (M, "process_files (function)", function (fn) - fn = type (fn) == "string" and lambda (fn) or fn - -- N.B. "arg" below refers to the global array of command-line args if #arg == 0 then arg[#arg + 1] = "-" diff --git a/lib/std/list.lua b/lib/std/list.lua index 5303ba0..23e352f 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -35,8 +35,8 @@ local func = require "std.functional" local object = require "std.object" -local argcheck, argerror, argscheck, ielems, lambda, prototype = - base.argcheck, base.argerror, base.argscheck, base.ielems, base.lambda, base.prototype +local argcheck, argerror, argscheck, ielems, prototype = + base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype local Object = object {} @@ -206,8 +206,6 @@ end -- @see std.list:filter local function filter (p, l) argscheck ("std.list.filter", {"function", "List"}, {p, l}) - p = type (p) == "string" and lambda (p) or p - return List (func.filter (p, ielems, l)) end @@ -234,8 +232,6 @@ end -- @see std.list:foldl local function foldl (fn, e, l) argscheck ("std.list.foldl", {"function", "any?", "List"}, {fn, e, l}) - fn = type (fn) == "string" and lambda (fn) or fn - return func.fold (fn, e, ielems, l) end @@ -268,8 +264,6 @@ end -- @see std.list:foldr local function foldr (fn, e, l) argscheck ("std.list.foldr", {"function", "any?", "List"}, {fn, e, l}) - fn = type (fn) == "string" and lambda (fn) or fn - return List (func.fold (function (x, y) return fn (y, x) end, e, relems, l)) end @@ -322,8 +316,6 @@ end, nil, -- @see std.list:map local function map (fn, l) argscheck ("std.list.map", {"function", "List|table"}, {fn, l}) - fn = type (fn) == "string" and lambda (fn) or fn - return List (func.map (fn, ielems, l)) end @@ -333,8 +325,6 @@ end -- @tparam List ls a list of lists -- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` local function map_with (fn, ls) - fn = type (fn) == "string" and lambda (fn) or fn - if _ARGCHECK then local fname = "std.list.map_with" argscheck (fname, {"function", "List"}, {fn, ls}) @@ -539,8 +529,6 @@ end -- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` -- where `N = max {map (function (l) return #l end, ls)}` local function zip_with (ls, fn) - fn = type (fn) == "string" and lambda (fn) or fn - if _ARGCHECK then local fname = "std.list.zip_with" argscheck (fname, {"List", "function"}, {ls, fn}) diff --git a/lib/std/package.lua b/lib/std/package.lua index b4af8d5..dfad1d5 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -17,7 +17,7 @@ local catfile = require "std.io".catfile local invert = require "std.table".invert local escape_pattern = require "std.string".escape_pattern -local export, lambda, split = base.export, base.lambda, base.split +local export, split = base.export, base.split local M = { "std.package" } @@ -146,8 +146,6 @@ end) -- @usage mappath (package.path, searcherfn, transformfn) export (M, "mappath (string, function, any?*)", function (pathstrings, callback, ...) - callback = type (callback) == "string" and lambda (callback) or callback - for _, path in ipairs (split (pathstrings, M.pathsep)) do local r = callback (path, ...) if r ~= nil then return r end diff --git a/lib/std/string.lua b/lib/std/string.lua index 17a9549..06dbf17 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -18,8 +18,8 @@ local table = require "std.table" local List = list {} local StrBuf = strbuf {} -local export, getmetamethod, lambda, split = - base.export, base.getmetamethod, base.lambda, base.split +local export, getmetamethod, split = + base.export, base.getmetamethod, base.split local _format = string.format local _tostring = _G.tostring @@ -466,12 +466,6 @@ end) -- end render = export (M, "render (any?, func, func, func, func, func, table?)", function (x, open, close, elem, pair, sep, roots) - open = type (open) == "string" and lambda (open) or open - close = type (close) == "string" and lambda (close) or close - elem = type (elem) == "string" and lambda (elem) or elem - pair = type (pair) == "string" and lambda (pair) or pair - sep = type (sep) == "string" and lambda (sep) or sep - local function stop_roots (x) return roots[x] or render (x, open, close, elem, pair, sep, table.clone (roots)) end diff --git a/lib/std/table.lua b/lib/std/table.lua index f5dd32e..c140daa 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -13,8 +13,8 @@ local base = require "std.base" -local export, getmetamethod, lambda, ielems = - base.export, base.getmetamethod, base.lambda, base.ielems +local export, getmetamethod, ielems = + base.export, base.getmetamethod, base.ielems local M = { "std.table" } @@ -300,8 +300,6 @@ local _sort = table.sort -- @return *t* with keys sorted accordind to *c* -- @usage table.concat (sort (object)) export (M, "sort (table, function?)", function (t, c) - c = type (c) == "string" and lambda (c).call or c - _sort (t, c) return t end) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index a7ea36d..dac18fa 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -187,9 +187,6 @@ specify std.debug: expect (fn ("function", {0})).to_error "function expected, got table" - it matches callable types: expect (fn ("function", function () end)).not_to_error () - expect (fn ("function", "|x|1+x")).not_to_error () - expect (fn ("function", "=_1<_2")).not_to_error () - expect (fn ("function", "<")).not_to_error () expect (fn ("function", setmetatable ({}, {__call = function () end}))). not_to_error () - it diagnoses missing non-empty table types: diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 7efa69b..eb00d3e 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -40,8 +40,6 @@ specify std.functional: expect (f (div, {100}) (25)).to_be (4) - it supports out of order extra arguments: expect (f (math.pow, {[2] = 3}) (2)).to_be (8) - - it supports lambda strings: - expect (f ("^", {[2] = 3}) (2)).to_be (8) - it supports the legacy api: expect (f (math.min) (2, 3, 4)).to_be (2) expect (f (math.min, 1, 0) (2, 3, 4)).to_be (0) @@ -83,8 +81,6 @@ specify std.functional: princess = function () return "one" end, function () return "gibberish" end, })).to_be "many" - - it supports lambda strings: - expect (f ("1", { ["1"] = '="one"', '="unknown"' })).to_be "one" - describe collect: @@ -100,9 +96,6 @@ specify std.functional: - it collects iterator results: expect (f (ipairs, {"a", "b", "c"})).to_equal {1, 2, 3} - - it supports lambda string iterators: - expect (f ("|v|function (s, k) return k0", - "|v| function (s,k) return k Date: Fri, 11 Jul 2014 14:28:10 +0100 Subject: [PATCH 287/703] refactor: move language features to a new `std.lua` module. * specs/functional_spec.yaml (case, eval, lambda, memoize): Move from here... * specs/lua_spec.yaml: New file. ...to here. * specs/std_spec.yaml: Adjust accordingly. * specs/specs.mk (specl_SPECS): Add specs/lua_spec.yaml. * build-aux/config.ld.in (file): Add lib/std/lua.lua. * lib/std/base.lua (lambda): Move from here... * lib/std/lua.lua (lambda): New file. ...to here. * local.mk (dist_luastd_DATA): Add lib/std/lua.lua. * lib/std/functional.lua (case, eval, lambda, memoize): Move from here... * lib/std/lua.lua (case, eval, lambda, memoize): ...to here. * lib/std/string.lua (pickle): Adjust LDocs eval cross reference. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 15 ++-- build-aux/config.ld.in | 1 + lib/std.lua.in | 8 +- lib/std/base.lua | 43 ----------- lib/std/functional.lua | 105 +++---------------------- lib/std/lua.lua | 148 +++++++++++++++++++++++++++++++++++ lib/std/string.lua | 2 +- local.mk | 1 + specs/functional_spec.yaml | 136 -------------------------------- specs/lua_spec.yaml | 154 +++++++++++++++++++++++++++++++++++++ specs/specs.mk | 1 + specs/std_spec.yaml | 6 +- 12 files changed, 335 insertions(+), 285 deletions(-) create mode 100644 lib/std/lua.lua create mode 100644 specs/lua_spec.yaml diff --git a/NEWS b/NEWS index 237852c..03979bf 100644 --- a/NEWS +++ b/NEWS @@ -33,21 +33,26 @@ Stdlib NEWS - User visible changes - New `functional.nop` function, for use where a function is required but no work should be done. - - `functional.memoize` now propagates multiple return values correctly. + - New `std.lua` module collects additional Lua language features added + by stdlib that do not really belong in type modules: `std.lua.case`, + `std.lua.eval`, and `std.lua.memoize` (original access points exported + by earlier releases will be preserved for the forseeable future). + + - `lua.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.lambda` function for compiling lambda strings: + - New `lua.lambda` function for compiling lambda strings: - table.sort (t, functional.lambda "|a,b| a Date: Wed, 16 Jul 2014 13:14:48 +0100 Subject: [PATCH 289/703] list: deprecate list.elems module function. * lib/std/list.lua (elems): Deprecate. * specs/list_spec.yaml (elems): Specify deprecation warning behaviour. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 24 +++++-------------- specs/list_spec.yaml | 57 +++++++++++++++++++++++++------------------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 4aed363..d96ca50 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -94,6 +94,7 @@ local function compare (l, m) end +-- DEPRECATED: Remove in first release following 2015-07-11. --- An iterator over the elements of a list. -- @static -- @function elems @@ -102,21 +103,8 @@ end -- of `l` -- @treturn List `l` -- @return `true` -local elems - -if _ARGCHECK then - - elems = function (l) - argcheck ("std.list.elems", 1, "List", l) - return ielems (l) - end - -else - - -- Save a stack frame and a comparison on each call. - elems = ielems - -end +local elems = base.deprecate (ielems, nil, + "list.elems is deprecated, use lua.ielems instead.") --- Concatenate arguments into a list. @@ -555,7 +543,6 @@ local _functions = { concat = concat, cons = cons, depair = depair, - elems = elems, enpair = enpair, filter = filter, flatten = flatten, @@ -575,6 +562,7 @@ local _functions = { } -- Deprecated and undocumented. +_functions.elems = elems _functions.index_key = index_key _functions.index_value = index_value @@ -654,11 +642,11 @@ List = Object { ------ -- An iterator over the elements of a list. -- @function elems - -- @treturn function iterator function which returns successive + -- @treturn function iterator function which returns successive -- elements of `self` -- @treturn List `self` -- @return `true` - elems = elems, + elems = ielems, ------ -- Filter a list according to a predicate. diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 64701b2..c30ad60 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -255,32 +255,39 @@ specify std.list: - describe elems: - - before: - f = list.elems - - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.elems' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.elems' (List expected, got boolean)" + - context as a module function: + - before: + f = list.elems + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {{}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "elems is deprecated" + end + expect (select (2, capture (f, {{}}))).to_be (nil) - - it is an iterator over list members: - t = {} - for e in List.elems (l) do table.insert (t, e) end - expect (t).to_equal {"foo", "bar", "baz"} - - it works for an empty list: - t = {} - for e in List.elems (List {}) do table.insert (t, e) end - expect (t).to_equal {} - - it can be called from the list module: - t = {} - for e in List.elems (l) do table.insert (t, e) end - expect (t).to_equal {"foo", "bar", "baz"} - - it can be called as a list object method: - t = {} - for e in l:elems () do table.insert (t, e) end - expect (t).to_equal {"foo", "bar", "baz"} + - it is an iterator over list members: + t = {} + for e in f (l) do table.insert (t, e) end + expect (t).to_equal {"foo", "bar", "baz"} + - it works for an empty list: + t = {} + for e in f (List {}) do table.insert (t, e) end + expect (t).to_equal {} + + - context as an object method: + - it is an iterator over list members: + t = {} + for e in l:elems () do table.insert (t, e) end + expect (t).to_equal {"foo", "bar", "baz"} + - it works for an empty list: + t, l = {}, List {} + for e in l:elems () do table.insert (t, e) end + expect (t).to_equal {} - describe enpair: From b8188395cce254bbadedc09a8c8c426ee0f31951 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 16 Jul 2014 13:21:56 +0100 Subject: [PATCH 290/703] container: don't rewrap existing modulefunction functables. * lib/std/container.lua (modulefunction): When re-exporting module functions from another module, don't wrap inside another functable. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index d61e020..7ada779 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -120,7 +120,12 @@ local ModuleFunction = { -- @func fn a function -- @treturn functable a callable functable for `fn` local function modulefunction (fn) - return setmetatable ({_type = "modulefunction", call = fn}, ModuleFunction) + if getmetatable (fn) == ModuleFunction then + -- Don't double wrap! + return fn + else + return setmetatable ({_type = "modulefunction", call = fn}, ModuleFunction) + end end From 0621df721525bb05edf040140aeab417d7f806e1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 16 Jul 2014 14:34:05 +0100 Subject: [PATCH 291/703] refactor: simplify list.flatten implementation. * lib/std/list.lua (flatten): Simplify. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index d96ca50..de15f26 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -206,11 +206,7 @@ end local function flatten (l) argcheck ("std.list.flatten", 1, "List", l) - local r = List {} - for v in base.leaves (ipairs, l) do - r[#r + 1] = v - end - return r + return List (func.collect (base.leaves, ipairs, l)) end From 88e3395546ef2f9ec0c25e9ae97ffbee4b52238b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 17 Jul 2014 14:40:56 +0100 Subject: [PATCH 292/703] debug: argcheck accepts a List object for a list parameter. * specs/debug_spec.yaml (argcheck): Remove specifications for mismatch errors between list parameters and List arguments. * lib/std/base.lua (argcheck): Accept an empty List object for a Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 2 +- specs/debug_spec.yaml | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index a13c76a..460a1e8 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -321,7 +321,7 @@ if _ARGCHECK then end elseif check == "list" or check == "#list" then - if actualtype == "table" then + if actualtype == "table" or actualtype == "List" then local len, count = #actual, 0 local i = next (actual) repeat diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index dac18fa..f876bd9 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -209,8 +209,6 @@ specify std.debug: to_error "list expected, got table" expect (fn ("list", Object)). to_error "list expected, got Object" - expect (fn ("list", List {})). - to_error "list expected, got List" - it matches list types: expect (fn ("list", {})).not_to_error () expect (fn ("list", {1})).not_to_error () @@ -228,8 +226,6 @@ specify std.debug: to_error "non-empty list expected, got empty Object" expect (fn ("#list", List {})). to_error "non-empty list expected, got empty List" - expect (fn ("#list", List {1})). - to_error "non-empty list expected, got List" - it matches non-empty list types: expect (fn ("#list", {1})).not_to_error () - it diagnoses missing object types: From 9ca0ef76d32ee1eac5e0459e640676a4f952d6d8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 17 Jul 2014 13:28:21 +0100 Subject: [PATCH 293/703] lua: support __ipairs and __pairs metamethods on Lua 5.1. * specs/lua_spec.yaml (ipairs, pairs): Specify portable behaviour for new functions. * lib/std/lua.lua (ipairs, pairs): New functions that support __ipairs and __pairs metamethods, even on Lua 5.1. (ielems, elems): Improve LDocs and argchecks. * specs/lua_spec.yaml (elems, ielems): Adjust error message specs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 ++ lib/std/lua.lua | 45 ++++++++++++++---- specs/lua_spec.yaml | 113 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 147 insertions(+), 14 deletions(-) diff --git a/NEWS b/NEWS index 4829697..9bca8b3 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,9 @@ Stdlib NEWS - User visible changes `std.lua.eval`, and `std.lua.memoize` (original access points exported by earlier releases will be preserved for the forseeable future). + - New `lua.ipairs` and `lua.pairs` functions, that respect `__ipairs` + and `__pairs` metamethods, even on Lua 5.1. + - New `lua.ielems` and `lua.elems` functions for iterating sequences cleanly, while respecting `__ipairs` and `__pairs` metamethods respectively. diff --git a/lib/std/lua.lua b/lib/std/lua.lua index 29daf08..e4e8e43 100644 --- a/lib/std/lua.lua +++ b/lib/std/lua.lua @@ -67,28 +67,43 @@ end) --- An iterator over all elements of a sequence. +-- If there is a `__pairs` metamethod, use that to iterate. -- @function elems --- @tparam sequence x a sequence +-- @tparam table t a table -- @treturn function iterator function --- @treturn sequence *x*, the sequence being iterated over --- @treturn int *key*, the previous iteration key +-- @treturn table *t*, the table being iterated over +-- @return *key*, the previous iteration key -- @usage -- for v in elems {a = 1, b = 2, c = 5} do process (v) end -export (M, "elems (string|table)", function (x) - return wrapiterator (getmetamethod (x, "__pairs") or pairs, x) +export (M, "elems (table)", function (t) + return wrapiterator (getmetamethod (t, "__pairs") or pairs, t) end) --- An iterator over the integer keyed elements of a sequence. +-- If there is an `__ipairs` metamethod, use that to iterate. -- @function ielems --- @tparam sequence x a sequence +-- @tparam list l a list -- @treturn function iterator function --- @treturn sequence *x*, the sequence being iterated over +-- @treturn list *l*, the list being iterated over -- @treturn int *index*, the previous iteration index -- @usage -- for v in ielems {"a", "b", "c"} do process (v) end -export (M, "ielems (List|list|string)", function (x) - return wrapiterator (getmetamethod (x, "__ipairs") or ipairs, x) +export (M, "ielems (list)", function (l) + return wrapiterator (getmetamethod (l, "__ipairs") or ipairs, l) +end) + + +--- An implementation of core ipairs that respects __ipairs even in Lua 5.1. +-- @function ipairs +-- @tparam list l a list +-- @treturn function iterator function +-- @treturn list *l*, the list being iterated over +-- @treturn int *index*, the previous iteration index +-- @usage +-- for i, v in ipairs {"a", "b", "c"} do process (v) end +export (M, "ipairs (list)", function (l) + return ((getmetatable (l) or {}).__ipairs or ipairs) (l) end) @@ -194,5 +209,17 @@ end export (M, "lambda (string)", memoize (lambda, function (s) return s end)) +--- An implementation of core pairs that respects __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 +-- @usage +-- for i, v in ipairs {"a", "b", "c"} do process (v) end +export (M, "pairs (table)", function (t) + return (getmetamethod (t, "__pairs") or pairs) (t) +end) + return M diff --git a/specs/lua_spec.yaml b/specs/lua_spec.yaml index f01c5a2..b8e8a10 100644 --- a/specs/lua_spec.yaml +++ b/specs/lua_spec.yaml @@ -61,9 +61,9 @@ specify std.lua: f = M[fname] - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string or table")) + expect (f ()).to_error (msg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string or table", "boolean")) + expect (f (false)).to_error (msg (1, "table", "boolean")) - it diagnoses too many arguments: expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) @@ -73,6 +73,19 @@ specify std.lua: t[#t + 1] = e end expect (t).to_contain.a_permutation_of {"foo", "baz", 42} + - it respects __pairs metamethod: | + x = setmetatable ({ "a string" }, { + __pairs = function (x) + return function (x, n) + if n < #x[1] then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end + }) + t = {} + for v in f (x) 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 @@ -106,10 +119,10 @@ specify std.lua: f = M[fname] - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List, list or string")) + expect (f ()).to_error (msg (1, "list")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List, list or string", "boolean")) - expect (f {foo = "bar"}).to_error (msg (1, "List, list or string", "table")) + expect (f (false)).to_error (msg (1, "list", "boolean")) + expect (f {foo = "bar"}).to_error (msg (1, "list", "table")) - it diagnoses too many arguments: expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) @@ -119,12 +132,64 @@ specify std.lua: t[#t + 1] = e end expect (t).to_equal {"foo", 42} + - it respects __ipairs metamethod: | + x = setmetatable ({ "a string" }, { + __ipairs = function (x) + return function (x, n) + if n < #x[1] then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end + }) + t = {} + for v in f (x) 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: + fname = "ipairs" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "list")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "list", "boolean")) + expect (f {foo = "bar"}).to_error (msg (1, "list", "table")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - 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 respects __ipairs metamethod: | + x = setmetatable ({ "a string" }, { + __ipairs = function (x) + return function (x, n) + if n < #x[1] then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end + }) + t = {} + for k, v in f (x) 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 lambda: - before: fname = "lambda" @@ -203,3 +268,41 @@ specify std.lua: expect (memfn "same").to_be (memfn "not same") expect (memfn (1, 2)).to_be (memfn (false, "x")) expect (memfn "one").not_to_be (memfn ("one", "two")) + + +- describe pairs: + - before: + fname = "pairs" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - 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: | + x = setmetatable ({ "a string" }, { + __pairs = function (x) + return function (x, n) + if n < #x[1] then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end + }) + t = {} + for k, v in f (x) do t[k] = v end + expect (t).to_equal {"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 {} From df359bce719642b825b6aa1605da4b325505f48a Mon Sep 17 00:00:00 2001 From: "Making GitHub Delicious." Date: Thu, 17 Jul 2014 15:05:47 -0600 Subject: [PATCH 294/703] add waffle.io badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8ba232c..3740727 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Stories in Ready](https://badge.waffle.io/lua-stdlib/lua-stdlib.png?label=ready&title=Ready)](https://waffle.io/lua-stdlib/lua-stdlib) Standard Lua libraries ====================== From 84a0a3ef31df42611f4d10a5c002d2875a1f724c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 18 Jul 2014 12:24:38 +0100 Subject: [PATCH 295/703] lua: add a monkey_patch function. * specs/lua_spec.yaml (monkey_patch): Specify behaviour of lua monkey_patch function. * lib/std/lua.lua (monkey_patch): Install lua functions into the given namespace. * lib/std/std.lua.in (monkey_patch): Add lua.monkey_patch invocation. (barrel): Remove double injection of `std.lua` functions. * specs/std_spec.yaml (barrel): Add new 'std.lua' functions. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 3 +-- lib/std/lua.lua | 24 ++++++++++++++++++++++-- specs/lua_spec.yaml | 24 ++++++++++++++++++++++++ specs/std_spec.yaml | 4 ++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 0a94452..8520332 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -40,6 +40,7 @@ local function monkey_patch (namespace) namespace = namespace or _G require "std.io".monkey_patch (namespace) + require "std.lua".monkey_patch (namespace) require "std.math".monkey_patch (namespace) require "std.string".monkey_patch (namespace) require "std.table".monkey_patch (namespace) @@ -69,8 +70,6 @@ local function barrel (namespace) "io.die", "io.warn", - "lua.case", "lua.eval", "lua.lambda", "lua.memoize", - "string.assert", "string.pickle", "string.prettytostring", "string.render", "string.require_version", "string.tostring", diff --git a/lib/std/lua.lua b/lib/std/lua.lua index e4e8e43..28e3eda 100644 --- a/lib/std/lua.lua +++ b/lib/std/lua.lua @@ -89,7 +89,7 @@ end) -- @treturn int *index*, the previous iteration index -- @usage -- for v in ielems {"a", "b", "c"} do process (v) end -export (M, "ielems (list)", function (l) +local ielems = export (M, "ielems (list)", function (l) return wrapiterator (getmetamethod (l, "__ipairs") or ipairs, l) end) @@ -209,6 +209,26 @@ end export (M, "lambda (string)", memoize (lambda, function (s) return s end)) +--- Inject `std.lua` functions into global table, overwriting core functions. +-- +-- This function does not inject itself into the global table, however! +-- @function monkey_patch +-- @tparam[opt=_G] table namespace to install `std.lua` functions +-- @treturn table the module table +-- @usage local lua = require "std.lua".monkey_patch () +export (M, "monkey_patch (table?)", function (namespace) + namespace = namespace or _G + + for fname in ielems { + "case", "eval", "elems", "ielems", "ipairs", "lambda", "memoize", "pairs", + } do + namespace[fname] = M[fname] + end + + return M +end) + + --- An implementation of core pairs that respects __pairs even in Lua 5.1. -- @function pairs -- @tparam table t a table @@ -216,7 +236,7 @@ export (M, "lambda (string)", memoize (lambda, function (s) return s end)) -- @treturn table *t*, the table being iterated over -- @return *key*, the previous iteration key -- @usage --- for i, v in ipairs {"a", "b", "c"} do process (v) end +-- for k, v in pairs {"a", b = "c", foo = 42} do process (k, v) end export (M, "pairs (table)", function (t) return (getmetamethod (t, "__pairs") or pairs) (t) end) diff --git a/specs/lua_spec.yaml b/specs/lua_spec.yaml index b8e8a10..9065eb3 100644 --- a/specs/lua_spec.yaml +++ b/specs/lua_spec.yaml @@ -270,6 +270,30 @@ specify std.lua: expect (memfn "one").not_to_be (memfn ("one", "two")) +- describe monkey_patch: + - before: + fname = "monkey_patch" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + monkeys = { "case", "eval", "elems", "ielems", "ipairs", "lambda", + "memoize", "pairs" } + + t = {} + f (t) + + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (t, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - it installs monkeys to the given namespace: + expect (t).to_contain.a_permutation_of (monkeys) + for _, monkey in ipairs (monkeys) do + expect (t[monkey]).to_be (M[monkey]) + end + + - describe pairs: - before: fname = "pairs" diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 145fa48..ee8151c 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -58,13 +58,16 @@ specify std: compose = std.functional.compose, curry = std.functional.curry, die = std.io.die, + elems = std.lua.elems, eval = std.lua.eval, filter = std.functional.filter, fold = std.functional.fold, id = std.functional.id, + ielems = std.lua.ielems, ileaves = std.tree.ileaves, inodes = std.tree.inodes, io = t.io, + ipairs = std.lua.ipairs, lambda = std.lua.lambda, leaves = std.tree.leaves, map = std.functional.map, @@ -74,6 +77,7 @@ specify std: nodes = std.tree.nodes, op = std.functional.op, pack = std.table.pack, + pairs = std.lua.pairs, pickle = std.string.pickle, prettytostring = std.string.prettytostring, render = std.string.render, From 57c2f5e45e075722072f3176e11efba6656d2b8d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 18 Jul 2014 13:32:46 +0100 Subject: [PATCH 296/703] refactor: decouple std.lua from other modules. * lib/std/lua.lua (wrapiterator): Move from here... * lib/std/base.lua (wrapiterator): ...to here. (ielems): Non-argchecked implementation. * lib/std/lua.lua (ielems): Use it. * lib/std/debug.lua, lib/std/list.lua, lib/std/set.lua, lib/std/tree.lua: Use base.ielems internally. * lib/std/functional.lua (case, eval, lambda, memoize): Load std.lua on demand when these functions are called rather than depending on it at require time. * lib/std/table.lua: Remove unused std.lua requirement. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 31 +++++++++++++++++++++++++++++++ lib/std/debug.lua | 4 +--- lib/std/functional.lua | 10 +++------- lib/std/list.lua | 6 ++---- lib/std/lua.lua | 30 +++--------------------------- lib/std/set.lua | 4 +--- lib/std/table.lua | 1 - lib/std/tree.lua | 4 +--- 8 files changed, 42 insertions(+), 48 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 460a1e8..17be3d3 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -245,6 +245,28 @@ local function formaterror (expectedtypes, actual) end +--- Iterator adaptor for discarding first value from core iterator function. +-- @func factory iterator to be wrapped +-- @param ... *factory* arguments +-- @treturn function iterator that discards first returned value of +-- factory iterator +-- @return invariant state from *factory* +-- @return `true` +-- @usage +-- for v in wrapiterator (ipairs {"a", "b", "c"}) do process (v) end +local function wrapiterator (factory, ...) + -- Capture wrapped ctrl variable into an upvalue... + local fn, istate, ctrl = factory (...) + -- Wrap the returned iterator fn to maintain wrapped ctrl. + return function (state, _) + local v + ctrl, v = fn (state, ctrl) + if ctrl then return v end + end, istate, true -- wrapped initial state, and wrapper ctrl +end + + + --[[ ================= ]]-- --[[ Module Functions. ]]-- @@ -569,6 +591,13 @@ local function getmetamethod (x, n) end +-- Doc-commented in lua.lua +local function ielems (l) + return wrapiterator (getmetamethod (l, "__ipairs") or ipairs, l) +end + + + local M = { argcheck = argcheck, argerror = argerror, @@ -577,11 +606,13 @@ local M = { deprecate = deprecate, export = export, getmetamethod = getmetamethod, + ielems = ielems, leaves = leaves, nop = nop, prototype = prototype, split = split, toomanyarg_fmt = toomanyarg_fmt, + wrapiterator = wrapiterator, } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index a15065b..7344aaf 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -33,11 +33,9 @@ local _DEBUG = require "std.debug_init"._DEBUG local base = require "std.base" local functional = require "std.functional" -local lua = require "std.lua" local string = require "std.string" -local export = base.export -local ielems = lua.ielems +local export, ielems = base.export, base.ielems local M = { "std.debug" } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index c06703a..ed643fd 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -9,11 +9,8 @@ local base = require "std.base" -local lua = require "std.lua" local export, nop = base.export, base.nop -local case, eval, lambda, memoize = - lua.case, lua.eval, lua.lambda, lua.memoize local M = { "std.functional" } @@ -203,10 +200,9 @@ M.nop = nop -- For backwards compatibility. -M.case = case -M.eval = eval -M.lambda = lambda -M.memoize = memoize +M.case = function (...) return require "std.lua".case (...) end +M.eval = function (...) return require "std.lua".eval (...) end +M.memoize = function (...) return require "std.lua".memoize (...) end M.op = require "std.operator" diff --git a/lib/std/list.lua b/lib/std/list.lua index de15f26..0e55ae7 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -32,13 +32,11 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" local func = require "std.functional" -local lua = require "std.lua" local object = require "std.object" -local argcheck, argerror, argscheck, prototype = - base.argcheck, base.argerror, base.argscheck, base.prototype -local ielems = lua.ielems +local argcheck, argerror, argscheck, ielems, prototype = + base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype local Object = object {} diff --git a/lib/std/lua.lua b/lib/std/lua.lua index 28e3eda..f85ee15 100644 --- a/lib/std/lua.lua +++ b/lib/std/lua.lua @@ -7,35 +7,13 @@ local base = require "std.base" local operator = require "std.operator" -local export, getmetamethod = base.export, base.getmetamethod +local export, getmetamethod, ielems, wrapiterator = + base.export, base.getmetamethod, base.ielems, base.wrapiterator local M = { "std.lua" } ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - -local function wrapiterator (factory, ...) - -- Capture wrapped ctrl variable into an upvalue... - local fn, istate, ctrl = factory (...) - -- Wrap the returned iterator fn to maintain wrapped ctrl. - return function (state, _) - local v - ctrl, v = fn (state, ctrl) - if ctrl then return v end - end, istate, true -- wrapped initial state, and wrapper ctrl -end - - - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - --- A rudimentary case statement. -- Match `with` against keys in `branches` table, and return the result -- of running the function in the table value for the matching key, or @@ -89,9 +67,7 @@ end) -- @treturn int *index*, the previous iteration index -- @usage -- for v in ielems {"a", "b", "c"} do process (v) end -local ielems = export (M, "ielems (list)", function (l) - return wrapiterator (getmetamethod (l, "__ipairs") or ipairs, l) -end) +export (M, "ielems (list)", ielems) --- An implementation of core ipairs that respects __ipairs even in Lua 5.1. diff --git a/lib/std/set.lua b/lib/std/set.lua index bd49ef3..cbd6061 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -12,12 +12,10 @@ ]] local base = require "std.base" -local lua = require "std.lua" local container = require "std.container" local Container = container {} -local ielems = lua.ielems -local prototype = base.prototype +local ielems, prototype = base.ielems, base.prototype local Set -- forward declaration diff --git a/lib/std/table.lua b/lib/std/table.lua index efd9c51..5e7c6f4 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -12,7 +12,6 @@ local base = require "std.base" -local lua = require "std.lua" local export, getmetamethod = base.export, base.getmetamethod diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 7e80109..659d6be 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -14,12 +14,10 @@ local base = require "std.base" local container = require "std.container" local func = require "std.functional" -local lua = require "std.lua" local Container = container {} -local base_leaves, prototype = base.leaves, base.prototype +local ielems, base_leaves, prototype = base.ielems, base.leaves, base.prototype local fold, op = func.fold, func.op -local ielems = lua.ielems local Tree -- forward declaration From d6ed186222f4593b3f787492bad1331d16d4d836 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 18 Jul 2014 14:26:10 +0100 Subject: [PATCH 297/703] refactor: move assert and require from std.string to std.lua. * specs/string_spec.yaml (assert, require): Move from here... * specs/lua_spec.yaml (assert, require): ...to here. * lib/std/string.lua (assert, module_version, require, version_to_list): Move from here... * lib/std/lua.lua (assert, module_version, require, version_to_list): ...to here. * lib/std/string.lua (assert): Propagate invocations to std.lua, with a deprecation warning. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 7 ++- lib/std/lua.lua | 77 +++++++++++++++++++++++++++++-- lib/std/string.lua | 74 ++++++------------------------ specs/lua_spec.yaml | 101 ++++++++++++++++++++++++++++++++++++++++- specs/std_spec.yaml | 19 +++++--- specs/string_spec.yaml | 94 ++++---------------------------------- 6 files changed, 212 insertions(+), 160 deletions(-) diff --git a/NEWS b/NEWS index 9bca8b3..2242aca 100644 --- a/NEWS +++ b/NEWS @@ -89,8 +89,11 @@ Stdlib NEWS - User visible changes passing a non-string will now raise an error as specified in the api documentation. - - `string.require_version` has been renamed to `string.require`, the - old name now gives a deprecation warning on first use, and will be + - `string.assert` has been moved to `lua.assert`, the old name now + gives a deprecation warning on first use. + + - `string.require_version` has been moved to `lua.require`, the old + name now gives a deprecation warning on first use, and will be removed entirely in some future release. - The `functional.op` table has been factored out into its own new diff --git a/lib/std/lua.lua b/lib/std/lua.lua index f85ee15..0566dd4 100644 --- a/lib/std/lua.lua +++ b/lib/std/lua.lua @@ -5,15 +5,62 @@ ]] local base = require "std.base" +local list = require "std.list" local operator = require "std.operator" -local export, getmetamethod, ielems, wrapiterator = - base.export, base.getmetamethod, base.ielems, base.wrapiterator +local List = list {} +local export, getmetamethod, ielems, split, wrapiterator = + base.export, base.getmetamethod, base.ielems, base.split, base.wrapiterator local M = { "std.lua" } +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + +--- Return a List object by splitting version string on periods. +-- @string version a period delimited version string +-- @treturn List a list of version components +local function version_to_list (version) + return List (split (version, "%.")) +end + + +--- Extract a list of period delimited integer version components. +-- @tparam table module returned from a `require` call +-- @string pattern to capture version number from a string +-- (default: `"%D*([%.%d]+)"`) +-- @treturn List a list of version components +local function module_version (module, pattern) + local version = module.version or module._VERSION + return version_to_list (version:match (pattern or "%D*([%.%d]+)")) +end + + + +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- + + +--- Extend to 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 +-- assert (expected ~= nil, "100% unexpected!") +-- assert (expected ~= nil, "%s unexpected!", expected) +export (M, "assert (any?, string?, any?*)", function (expect, f, arg1, ...) + local msg = (arg1 ~= nil) and string.format (f, arg1, ...) or f or "" + return expect or error (msg, 2) +end) + + --- A rudimentary case statement. -- Match `with` against keys in `branches` table, and return the result -- of running the function in the table value for the matching key, or @@ -189,14 +236,15 @@ export (M, "lambda (string)", memoize (lambda, function (s) return s end)) -- -- This function does not inject itself into the global table, however! -- @function monkey_patch --- @tparam[opt=_G] table namespace to install `std.lua` functions +-- @tparam[opt=_G] table namespace to install `std.lua` functions into -- @treturn table the module table -- @usage local lua = require "std.lua".monkey_patch () export (M, "monkey_patch (table?)", function (namespace) namespace = namespace or _G for fname in ielems { - "case", "eval", "elems", "ielems", "ipairs", "lambda", "memoize", "pairs", + "assert", "case", "eval", "elems", "ielems", "ipairs", "lambda", + "memoize", "pairs", "require", } do namespace[fname] = M[fname] end @@ -218,4 +266,25 @@ export (M, "pairs (table)", function (t) end) +--- Require a module with a particular version. +-- @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 std = require ("std", "41") +export (M, "require (string, string?, string?, string?)", +function (module, min, too_big, pattern) + local m = require (module) + if min then + assert (module_version (m, pattern) >= version_to_list (min)) + end + if too_big then + assert (module_version (m, pattern) < version_to_list (too_big)) + end + return m +end) + + return M diff --git a/lib/std/string.lua b/lib/std/string.lua index 484bd19..0f8bfdc 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -11,11 +11,9 @@ ]] local base = require "std.base" -local list = require "std.list" local strbuf = require "std.strbuf" local table = require "std.table" -local List = list {} local StrBuf = strbuf {} local export, getmetamethod, split = @@ -48,25 +46,6 @@ local function tpack (from, to, ...) end ---- Return a List object by splitting version string on periods. --- @string version a period delimited version string --- @treturn List a list of version components -local function version_to_list (version) - return List (split (version, "%.")) -end - - ---- Extract a list of period delimited integer version components. --- @tparam table module returned from a `require` call --- @string pattern to capture version number from a string --- (default: `"%D*([%.%d]+)"`) --- @treturn List a list of version components -local function module_version (module, pattern) - local version = module.version or module._VERSION - return version_to_list (version:match (pattern or "%D*([%.%d]+)")) -end - - --[[ ============ ]]-- --[[ Metamethods. ]]-- @@ -109,6 +88,13 @@ end --[[ ================= ]]-- +-- DEPRECATED: Remove in first release following 2015-07-30. +M.assert = base.deprecate (function (...) + return require "std.lua".assert (...) +end, nil, + "string.assert is deprecated, use lua.assert instead") + + --- Extend to work better with one argument. -- If only one argument is passed, no formatting is attempted. -- @function format @@ -121,18 +107,6 @@ local format = export (M, "format (string, any?*)", function (f, arg1, ...) end) ---- Extend to 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 assert (expected == actual, "100% unexpected!") -export (M, "assert (any?, string?, any?*)", function (expect, f, ...) - return expect or error (format (f or "", ...), 2) -end) - - --- Do `string.find`, returning a table of captures. -- @function tfind -- @string s target string @@ -187,30 +161,11 @@ end) export (M, "split (string, string?)", split) ---- Require a module with a particular version. --- @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 std = require ("std", "41") -export (M, "require (string, string?, string?, string?)", -function (module, min, too_big, pattern) - local m = require (module) - if min then - assert (module_version (m, pattern) >= version_to_list (min)) - end - if too_big then - assert (module_version (m, pattern) < version_to_list (too_big)) - end - return m -end) - - -- DEPRECATED: Remove in first release following 2015-06-30. -M.require_version = base.deprecate (M.require, nil, - "string.require_version is deprecated, use string.require instead") +M.require_version = base.deprecate (function (...) + return require "std.lua".require (...) +end, nil, + "string.require_version is deprecated, use lua.require instead") --- Overwrite core methods and metamethods with `std` enhanced versions. @@ -218,16 +173,13 @@ M.require_version = base.deprecate (M.require, nil, -- Adds auto-stringification to `..` operator on core strings, and -- integer indexing of strings with `[]` dereferencing. -- --- Also replaces core `assert`, `require` and `tostring` functions with --- `std.string` versions. +-- Also replaces core `tostring` functions with `std.string` version. -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local string = require "std.string".monkey_patch () export (M, "monkey_patch (table?)", function (namespace) - namespace = namespace or _G - namespace.assert, namespace.require, namespace.tostring = - M.assert, M.require, M.tostring + (namespace or _G).tostring = M.tostring local string_metatable = getmetatable "" string_metatable.__concat = M.__concat diff --git a/specs/lua_spec.yaml b/specs/lua_spec.yaml index 9065eb3..5d9ed60 100644 --- a/specs/lua_spec.yaml +++ b/specs/lua_spec.yaml @@ -17,6 +17,38 @@ specify std.lua: to_equal {} +- describe assert: + - before: + fname = "assert" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses wrong argument types: + expect (f (false, false)).to_error (msg (2, "string or nil", "boolean")) + + - context when it does not trigger: + - it has a truthy initial argument: + expect (f (1)).not_to_error () + expect (f (true)).not_to_error () + expect (f "yes").not_to_error () + expect (f (false == false)).not_to_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_error () + expect (f (false)).to_error () + expect (f (1 == 0)).to_error () + - it throws an optional error string: + expect (f (false, "ah boo")).to_error "ah boo" + - it plugs specifiers with string.format: | + expect (f (nil, "%s %d: %q", "here", 42, "a string")). + to_error (string.format ("%s %d: %q", "here", 42, "a string")) + + - describe case: - before: yes = function () return true end @@ -276,8 +308,8 @@ specify std.lua: msg = bind (badarg, {this_module, fname}) f = M[fname] - monkeys = { "case", "eval", "elems", "ielems", "ipairs", "lambda", - "memoize", "pairs" } + monkeys = { "assert", "case", "eval", "elems", "ielems", "ipairs", + "lambda", "memoize", "pairs", "require" } t = {} f (t) @@ -330,3 +362,68 @@ specify std.lua: t = {} for k, v in f {} do t[k] = v end expect (t).to_equal {} + + +- describe require: + - before: + fname = "require" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("module", false)).to_error (msg (2, "string or nil", "boolean")) + expect (f ("module", "min", false)). + to_error (msg (3, "string or nil", "boolean")) + expect (f ("module", "min", "too_big", false)). + to_error (msg (4, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("module", "min", "too_big", "pattern", false)). + to_error (toomanyarg (this_module, fname, 4, 5)) + + - it diagnoses non-existent module: + expect (f ("module-not-exists", "", "")).to_error "module-not-exists" + - it diagnoses module too old: + expect (f ("std", "9999", "9999")).to_error () + - it diagnoses module too new: + expect (f ("std", "0", "0")).to_error () + - 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", "41")).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: + std = require "std" + std._VERSION, std.version = std.version, std._VERSION + expect (f ("std", "41", "9999")).to_be (require "std") + std._VERSION, std.version = std.version, std._VERSION + - 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_error () + expect (f ("std", "1.3")).to_error () + expect (f ("std", "2.1.2")).to_error () + expect (f ("std", "2")).to_error () + expect (f ("std", "1.2.10")).to_error () + - it diagnoses module too new: + expect (f ("std", nil, "1.2.2")).to_error () + expect (f ("std", nil, "1.1")).to_error () + expect (f ("std", nil, "1.1.2")).to_error () + expect (f ("std", nil, "1")).to_error () + - 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) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index ee8151c..dfbc7f7 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -36,6 +36,17 @@ specify std: - it installs std.io monkey patches: expect (io_mt.readlines).to_be (std.io.readlines) expect (io_mt.writelines).to_be (std.io.writelines) + - it installs std.lua monkey patches: + expect (t.assert).to_be (std.lua.assert) + expect (t.case).to_be (std.lua.case) + expect (t.elems).to_be (std.lua.elems) + expect (t.eval).to_be (std.lua.eval) + expect (t.ielems).to_be (std.lua.ielems) + expect (t.ipairs).to_be (std.lua.ipairs) + expect (t.lambda).to_be (std.lua.lambda) + expect (t.memoize).to_be (std.lua.memoize) + expect (t.pairs).to_be (std.lua.pairs) + expect (t.require).to_be (std.lua.require) - it installs std.math monkey patches: expect (t.math.floor).to_be (std.math.floor) - it installs std.string monkey patches: @@ -44,14 +55,12 @@ specify std: expect (mt.__append).to_be (std.string.__append) expect (mt.__concat).to_be (std.string.__concat) expect (mt.__index).to_be (std.string.__index) - expect (t.assert).to_be (std.string.assert) - expect (t.require).to_be (std.string.require) expect (t.tostring).to_be (std.string.tostring) - it installs std.table monkey patches: expect (t.table.sort).to_be (std.table.sort) - it scribbles into the supplied namespace: expect (t).should_equal { - assert = std.string.assert, + assert = std.lua.assert, bind = std.functional.bind, case = std.lua.case, collect = std.functional.collect, @@ -81,7 +90,7 @@ specify std: pickle = std.string.pickle, prettytostring = std.string.prettytostring, render = std.string.render, - require = std.string.require, + require = std.lua.require, require_version = std.string.require_version, ripairs = std.table.ripairs, table = t.table, @@ -118,8 +127,6 @@ specify std: expect (mt.__append).to_be (std.string.__append) expect (mt.__concat).to_be (std.string.__concat) expect (mt.__index).to_be (std.string.__index) - expect (t.assert).to_be (std.string.assert) - expect (t.require).to_be (std.string.require) expect (t.tostring).to_be (std.string.tostring) - it installs std.table monkey patches: expect (t.table.sort).to_be (std.table.sort) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 65172c8..11bf049 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -8,7 +8,7 @@ before: "escape_shell", "finds", "format", "ltrim", "monkey_patch", "numbertosi", "ordinal_suffix", "pad", "pickle", "prettytostring", "render", - "require", "require_version", "rtrim", "split", + "require_version", "rtrim", "split", "tfind", "tostring", "trim", "wrap" } M = require (this_module) @@ -63,8 +63,14 @@ specify std.string: msg = bind (badarg, {this_module, fname}) f = M[fname] - - it diagnoses wrong argument types: - expect (f (false, false)).to_error (msg (2, "string or nil", "boolean")) + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {"std.string"}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "assert is deprecated" + end + _, err = capture (f, {"std.string"}) + expect (err).to_be (nil) - context when it does not trigger: - it has a truthy initial argument: @@ -338,10 +344,6 @@ specify std.string: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" expect (mt.__index).to_be (M.__index) - - it installs the assert function: - expect (t.assert).to_be (M.assert) - - it installs the require function: - expect (t.require).to_be (M.require) - it installs the tostring function: expect (t.tostring).to_be (M.tostring) @@ -583,71 +585,6 @@ specify std.string: to_be ('Vector ("any", {a, b, ' .. tostring (a[3]) .. ', e})') -- describe require: - - before: - fname = "require" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("module", false)).to_error (msg (2, "string or nil", "boolean")) - expect (f ("module", "min", false)). - to_error (msg (3, "string or nil", "boolean")) - expect (f ("module", "min", "too_big", false)). - to_error (msg (4, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("module", "min", "too_big", "pattern", false)). - to_error (toomanyarg (this_module, fname, 4, 5)) - - - it diagnoses non-existent module: - expect (f ("module-not-exists", "", "")).to_error "module-not-exists" - - it diagnoses module too old: - expect (f ("std", "9999", "9999")).to_error () - - it diagnoses module too new: - expect (f ("std", "0", "0")).to_error () - - 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", "41")).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: - std = require "std" - std._VERSION, std.version = std.version, std._VERSION - expect (f ("std", "41", "9999")).to_be (require "std") - std._VERSION, std.version = std.version, std._VERSION - - 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_error () - expect (f ("std", "1.3")).to_error () - expect (f ("std", "2.1.2")).to_error () - expect (f ("std", "2")).to_error () - expect (f ("std", "1.2.10")).to_error () - - it diagnoses module too new: - expect (f ("std", nil, "1.2.2")).to_error () - expect (f ("std", nil, "1.1")).to_error () - expect (f ("std", nil, "1.1.2")).to_error () - expect (f ("std", nil, "1")).to_error () - - 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) - - # DEPRECATED: Remove in first release following 2015-06-30. - describe require_version: - before: @@ -664,19 +601,6 @@ specify std.string: _, err = capture (f, {"std.string"}) expect (err).to_be (nil) - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("module", false)).to_error (msg (2, "string or nil", "boolean")) - expect (f ("module", "min", false)). - to_error (msg (3, "string or nil", "boolean")) - expect (f ("module", "min", "too_big", false)). - to_error (msg (4, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("module", "min", "too_big", "pattern", false)). - to_error (toomanyarg (this_module, "require", 4, 5)) - - it diagnoses non-existent module: expect (f ("module-not-exists", "", "")).to_error "module-not-exists" - it diagnoses module too old: From a0d96ce0ecf5a1ae7e43a437440bc374fc457229 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 18 Jul 2014 14:33:43 +0100 Subject: [PATCH 298/703] doc: add missing @function to prettytostring LDocs. * lib/std/string.lua (prettytostring): Add missing @function. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/string.lua b/lib/std/string.lua index 0f8bfdc..42f4282 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -518,6 +518,7 @@ end --- 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 From 8cab0777523f82c16bb2ca7f4c8ce775207d1bcc Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 18 Jul 2014 14:41:42 +0100 Subject: [PATCH 299/703] doc: improve render LDocs @usage examples. * lib/std/string.lua (render, render_separator): Improve LDocs @usage examples. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index 42f4282..2721224 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -412,9 +412,9 @@ end) -- @return string representation of *x* -- @usage -- function tostring (x) --- return render (x, mkterminal "{", mkterminal "}", string.tostring, --- function (_, _, _, i, v) return i .. "=" .. v end, --- mkterminal ",") +-- return render (x, lambda '="{"', lambda '="}"', string.tostring, +-- lambda '=_4.."=".._5', lambda '= _4 and "," or ""', +-- lambda '=","') -- end render = export (M, "render (any?, func, func, func, func, func, table?)", function (x, open, close, elem, pair, sep, roots) @@ -484,13 +484,14 @@ end) --- Signature of render separator callback. -- @function render_separator --- @tparam table t table currently being renedered +-- @tparam table t table currently being rendered -- @param pk *t* key preceding separator, or `nil` for first key -- @param pv *t* value preceding separator, or `nil` for first value -- @param fk *t* key following separator, or `nil` for last key -- @param fv *t* value following separator, or `nil` for last value -- @treturn string separator rendering --- @usage function separator (t) return fk and "," or "" end +-- @usage +-- function separator (_, _, _, fk) return fk and "," or "" end --- Extend `tostring` to render table contents as a string. From c0da3b8f2cc5ee1b6d20f0e7af5b9e29135f17ce Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Jul 2014 19:11:36 +0100 Subject: [PATCH 300/703] operator: make '#' operator Lua 5.1 compatible. * specs/operator_spec.yaml (#): Specify behaviour of # operator. * lib/std/operator.lua (#): If there's a `__len` metamethod, call it manually before falling back to actual `#` operator. Signed-off-by: Gary V. Vaughan --- lib/std/operator.lua | 9 ++++++++- specs/operator_spec.yaml | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/std/operator.lua b/lib/std/operator.lua index cb69bc9..90dac83 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -5,6 +5,9 @@ ]] +local getmetamethod = require "std.base".getmetamethod + + --- Functional forms of Lua operators. -- -- Defined here so that other modules can write to it. @@ -39,7 +42,11 @@ return { ["{}"] = function (...) return {...} end, ['""'] = function (x) return tostring (x) end, ["~"] = function (s, p) return string.find (s, p) end, - ["#"] = function (t) return #t end, + ["#"] = function (t) + -- Lua < 5.2 doesn't call `__len` automatically! + local m = getmetamethod (t, "__len") + return m and m (t) or #t + end, ["+"] = function (a, b) return a + b end, ["-"] = function (a, b) return a - b end, ["*"] = function (a, b) return a * b end, diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index 63f6faf..30794cb 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -71,6 +71,8 @@ specify std.operator: expect (f "1234567890").to_be (10) - it returns the length of a table: expect (f {1, 2, 3, 4, 5}).to_be (5) + - it uses the __len metamethod: + expect (f (setmetatable ({}, { __len = function () return 42 end }))).to_be (42) - describe +: - before: From 8835d58b5b7b03c904b633201f933cf8f7c0f0f3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 21 Jul 2014 19:22:31 +0100 Subject: [PATCH 301/703] operator: break a require loop. * lib/std/base.lua: Remove unused `require "std.operator"` to break a require loop. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 17be3d3..c1dfb6c 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,7 +24,6 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK -local operator = require "std.operator" local argcheck, argerror, argscheck, prototype -- forward declarations From a9a70d55858466057cfa19aa934dd6114bf352ca Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 23 Jul 2014 13:36:47 +0100 Subject: [PATCH 302/703] refactor: replace `list.reverse` with `lua.ireverse`. * specs/lua_spec.yaml (ireverse): Specify behaviour of new ireverse function. * specs/list_spec.yaml (relems, reverse): Specify new deprecated behaviours of these functions. * lib/std/list.lua (relems, reverse): Deprecated. * lib/std/base.lua (ireverse): New `__ipairs` aware generator of new reversed array-part of any table. * lib/std/lua.lua (ireverse): Export `base.ireverse`. (monkey_patch): Inject ireverse into given namespace. * specs/std_spec.yaml (monkey_patch): Adjust accordingly. * NEWS: Updated. Signed-off-by: Gary V. Vaughan --- NEWS | 21 +++++++--- lib/std/base.lua | 19 +++++++++ lib/std/list.lua | 99 +++++++++++++++----------------------------- lib/std/lua.lua | 38 +++++++++++++---- specs/list_spec.yaml | 28 +++++++------ specs/lua_spec.yaml | 73 ++++++++++++++++++++++++++------ specs/std_spec.yaml | 1 + 7 files changed, 175 insertions(+), 104 deletions(-) diff --git a/NEWS b/NEWS index 2242aca..7012b07 100644 --- a/NEWS +++ b/NEWS @@ -34,12 +34,6 @@ Stdlib NEWS - User visible changes `std.lua.eval`, and `std.lua.memoize` (original access points exported by earlier releases will be preserved for the forseeable future). - - New `lua.ipairs` and `lua.pairs` functions, that respect `__ipairs` - and `__pairs` metamethods, even on Lua 5.1. - - - New `lua.ielems` and `lua.elems` functions for iterating sequences cleanly, - while respecting `__ipairs` and `__pairs` metamethods respectively. - - `lua.memoize` now propagates multiple return values correctly. This allows memoizing of functions that use the `return nil, "message"` pattern for error message reporting. @@ -56,6 +50,15 @@ Stdlib NEWS - User visible changes table.sort (t, lua.lambda "<") + - New `lua.ipairs` and `lua.pairs` functions, that respect `__ipairs` + and `__pairs` metamethods, even on Lua 5.1. + + - New `lua.ielems` and `lua.elems` functions for iterating sequences cleanly, + while respecting `__ipairs` and `__pairs` metamethods respectively. + + - New `lua.ireverse` function for reversing the array part of any + table, while respecting `__ipairs`, even on Lua 5.1. + ** Incompatible changes: - `functional.bind` sets fixed positional arguments when called as @@ -83,6 +86,12 @@ Stdlib NEWS - User visible changes use. After that, in some future release, they will be removed entirely. + - `list.reverse` has been deprecated in favour of the more general + and more accurately named `lua.ireverse`. + + - `list.relems` has been deprecated, in favour of the more composable + `functional.compose (lua.ireverse, lua.ielems)`. + - `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, diff --git a/lib/std/base.lua b/lib/std/base.lua index c1dfb6c..d21c652 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -596,6 +596,24 @@ local function ielems (l) end +-- Doc-commented in lua.lua +local function ireverse (l) + local iter, r = getmetamethod (l, "__ipairs"), {} + if not iter then + -- Calculate reverse indices for direct element access. + local len = #l + 1 + for i, v in ipairs (l) do r[len - i] = v end + else + -- Two passes in case __ipairs fetches from a proxy or similar, + -- where #l might not be accurate. + local t = {} + for i, v in iter (l) do t[i] = v end + for i = #t, 1, -1 do r[#r + 1] = t[i] end + end + return r +end + + local M = { argcheck = argcheck, @@ -606,6 +624,7 @@ local M = { export = export, getmetamethod = getmetamethod, ielems = ielems, + ireverse = ireverse, leaves = leaves, nop = nop, prototype = prototype, diff --git a/lib/std/list.lua b/lib/std/list.lua index 0e55ae7..5a869e6 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -8,21 +8,20 @@ local list = require "std.list" -- module table local List = list {} -- prototype object - local l = List {1, 2, 3} - for e in l:relems () do print (e) end - => 3 - => 2 - => 1 + local l = List {"foo", "bar"} + for e in ielems (l:append ("baz")) do print (e) end + => foo + => bar + => baz ... some can also be called as module functions with an explicit list argument in the first or last parameter, check the documentation for details: - local l = List {1, 2, 3} - for e in List.relems (l) do print (e) end - => 3 - => 2 - => 1 + for e in ielems (list.append (l, "quux")) do print (e) end + => foo + => bar + => quux @classmod std.list ]] @@ -35,8 +34,8 @@ local func = require "std.functional" local object = require "std.object" -local argcheck, argerror, argscheck, ielems, prototype = - base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype +local argcheck, argerror, argscheck, ielems, prototype, ireverse = + base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype, base.ireverse local Object = object {} @@ -93,7 +92,7 @@ end -- DEPRECATED: Remove in first release following 2015-07-11. ---- An iterator over the elements of a list. +-- An iterator over the elements of a list. -- @static -- @function elems -- @tparam List l a list @@ -220,24 +219,17 @@ local function foldl (fn, e, l) end ---- An iterator over the elements of a list, in reverse. +-- DEPRECATED: Remove in first release following 2015-07-11 +-- An iterator over the elements of a list, in reverse. -- @tparam List l a list --- @treturn function iterator function which returns precessive elements +-- @treturn function iterator function which returns precessive elements -- of the `l` -- @treturn List `l` -- @return `true` -local function relems (l) - argcheck ("std.list.relems", 1, "List|table", l) - - local n = #l + 1 - return function (l) - n = n - 1 - if n > 0 then - return l[n] - end - end, - l, true -end +local relems = base.deprecate (function (l) + return ielems (ireverse (l)) + end, nil, + "list.relems is deprecated, use lua.ipairs with lua.ireverse instead.") --- Fold a binary function through a list right associatively. @@ -249,7 +241,7 @@ end local function foldr (fn, e, l) argscheck ("std.list.foldr", {"function", "any?", "List"}, {fn, e, l}) return List (func.fold (function (x, y) return fn (y, x) end, - e, relems, l)) + e, ielems, ireverse (l))) end @@ -365,18 +357,14 @@ local function rep (l, n) end ---- Reverse a list. +-- DEPRECATED: Remove in first release following 2015-07-11 +-- Reverse a list. -- @tparam List l a list -- @treturn List new list containing `{l[#l], ..., l[1]}` -local function reverse (l) - argcheck ("std.list.reverse", 1, "List", l) - - local r = List {} - for i = #l, 1, -1 do - r[#r + 1] = l[i] - end - return r -end +local reverse = base.deprecate (function (l) + return List (ireverse (l)) + end, nil, + "list.reverse is deprecated, use lua.ireverse instead.") --- Shape a list according to a list of dimensions. @@ -545,9 +533,7 @@ local _functions = { map = map, map_with = map_with, project = project, - relems = relems, rep = rep, - reverse = reverse, shape = shape, sub = sub, tail = tail, @@ -556,9 +542,11 @@ local _functions = { } -- Deprecated and undocumented. -_functions.elems = elems -_functions.index_key = index_key +_functions.elems = elems +_functions.index_key = index_key _functions.index_value = index_value +_functions.relems = relems +_functions.reverse = reverse List = Object { @@ -633,15 +621,6 @@ List = Object { -- @treturn List new list containing `{x, unpack (self)}` cons = cons, - ------ - -- An iterator over the elements of a list. - -- @function elems - -- @treturn function iterator function which returns successive - -- elements of `self` - -- @treturn List `self` - -- @return `true` - elems = ielems, - ------ -- Filter a list according to a predicate. -- @function filter @@ -692,15 +671,6 @@ List = Object { -- @see std.list.project project = function (self, f) return project (f, self) end, - ------ - -- An iterator over the elements of a list, in reverse. - -- @function relems - -- @treturn function iterator function which returns precessive elements - -- of the `self` - -- @treturn List `self` - -- @return `true` - relems = relems, - ------ -- Repeat a list. -- @function rep @@ -708,12 +678,6 @@ List = Object { -- @treturn List `n` copies of `self` appended together rep = rep, - ------ - -- Reverse a list. - -- @function reverse - -- @treturn List new list containing `{self[#self], ..., self[1]}` - reverse = reverse, - ----- -- Shape a list according to a list of dimensions. -- @function shape @@ -741,9 +705,12 @@ List = Object { -- For backwards compatibility with pre-Object era lists, but -- undocumented so that new code doesn't get tangled up in it. depair = depair, + elems = ielems, index_key = function (self, f) return index_key (f, self) end, index_value = function (self, f) return index_value (f, self) end, map_with = function (self, f) return map_with (f, self) end, + relems = relems, + reverse = reverse, transpose = transpose, zip_with = function (self, f) return zip_with (f, self) end, }, diff --git a/lib/std/lua.lua b/lib/std/lua.lua index 0566dd4..4d83b1a 100644 --- a/lib/std/lua.lua +++ b/lib/std/lua.lua @@ -9,8 +9,8 @@ local list = require "std.list" local operator = require "std.operator" local List = list {} -local export, getmetamethod, ielems, split, wrapiterator = - base.export, base.getmetamethod, base.ielems, base.split, base.wrapiterator +local export, getmetamethod, ielems, ireverse, split, wrapiterator = + base.export, base.getmetamethod, base.ielems, base.ireverse, base.split, base.wrapiterator local M = { "std.lua" } @@ -98,6 +98,8 @@ end) -- @treturn function iterator function -- @treturn table *t*, the table being iterated over -- @return *key*, the previous iteration key +-- @see ielems +-- @see pairs -- @usage -- for v in elems {a = 1, b = 2, c = 5} do process (v) end export (M, "elems (table)", function (t) @@ -108,28 +110,46 @@ end) --- An iterator over the integer keyed elements of a sequence. -- If there is an `__ipairs` metamethod, use that to iterate. -- @function ielems --- @tparam list l a list +-- @tparam table t a table -- @treturn function iterator function -- @treturn list *l*, the list being iterated over -- @treturn int *index*, the previous iteration index +-- @see elems +-- @see ipairs -- @usage -- for v in ielems {"a", "b", "c"} do process (v) end -export (M, "ielems (list)", ielems) +export (M, "ielems (table)", ielems) --- An implementation of core ipairs that respects __ipairs even in Lua 5.1. -- @function ipairs --- @tparam list l a list +-- @tparam table t a table -- @treturn function iterator function -- @treturn list *l*, the list being iterated over -- @treturn int *index*, the previous iteration index +-- @see ielems +-- @see pairs -- @usage -- for i, v in ipairs {"a", "b", "c"} do process (v) end -export (M, "ipairs (list)", function (l) +local ipairs = export (M, "ipairs (table)", function (l) return ((getmetatable (l) or {}).__ipairs or ipairs) (l) end) +--- A new reversed list. +-- @function ireverse +-- @tparam table t a table +-- @treturn list a new list +-- @see ielems +-- @see ipairs +-- @usage +-- rielems = std.functional.compose (ireverse, ielems) +-- for e in rielems (l) do process (e) end +export (M, "ireverse (table)", function (l) + return ireverse (l) +end) + + --- Memoize a function, by wrapping it in a functable. -- -- To ensure that memoize always returns the same results for the same @@ -243,8 +263,8 @@ export (M, "monkey_patch (table?)", function (namespace) namespace = namespace or _G for fname in ielems { - "assert", "case", "eval", "elems", "ielems", "ipairs", "lambda", - "memoize", "pairs", "require", + "assert", "case", "eval", "elems", "ielems", "ipairs", "ireverse", + "lambda", "memoize", "pairs", "require" } do namespace[fname] = M[fname] end @@ -259,6 +279,8 @@ end) -- @treturn function iterator function -- @treturn table *t*, the table being iterated over -- @return *key*, the previous iteration key +-- @see elems +-- @see ipairs -- @usage -- for k, v in pairs {"a", b = "c", foo = 42} do process (k, v) end export (M, "pairs (table)", function (t) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index c30ad60..2107035 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -607,12 +607,14 @@ specify std.list: - before: f = list.relems - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.relems' (List or table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.relems' (List or table expected, got boolean)" + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f.call, {{}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "relems is deprecated" + end + _, err = capture (f.call, {{}}) + expect (err).to_be (nil) - it is a reverse iterator over list members: t = {} @@ -664,12 +666,14 @@ specify std.list: l = List {"foo", "bar", "baz", "quux"} f = list.reverse - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.reverse' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.reverse' (List expected, got boolean)" + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f.call, {{}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "reverse is deprecated" + end + _, err = capture (f.call, {{}}) + expect (err).to_be (nil) - context when called as a list object method: - it returns a list object: diff --git a/specs/lua_spec.yaml b/specs/lua_spec.yaml index 5d9ed60..385dae9 100644 --- a/specs/lua_spec.yaml +++ b/specs/lua_spec.yaml @@ -151,10 +151,9 @@ specify std.lua: f = M[fname] - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "list")) + expect (f ()).to_error (msg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "list", "boolean")) - expect (f {foo = "bar"}).to_error (msg (1, "list", "table")) + expect (f (false)).to_error (msg (1, "table", "boolean")) - it diagnoses too many arguments: expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) @@ -164,15 +163,22 @@ specify std.lua: 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 __ipairs metamethod: | + len = require "std.operator"["#"] x = setmetatable ({ "a string" }, { __ipairs = function (x) return function (x, n) - if n < #x[1] then + if n < len (x[1]) then return n+1, string.sub (x[1], n+1, n+1) end end, x, 0 - end + end, }) t = {} for v in f (x) do t[#t + 1] = v end @@ -190,10 +196,9 @@ specify std.lua: f = M[fname] - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "list")) + expect (f ()).to_error (msg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "list", "boolean")) - expect (f {foo = "bar"}).to_error (msg (1, "list", "table")) + expect (f (false)).to_error (msg (1, "table", "boolean")) - it diagnoses too many arguments: expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) @@ -203,15 +208,22 @@ specify std.lua: 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 __ipairs metamethod: | + len = require "std.operator"["#"] x = setmetatable ({ "a string" }, { __ipairs = function (x) return function (x, n) - if n < #x[1] then + if n < len (x[1]) then return n+1, string.sub (x[1], n+1, n+1) end end, x, 0 - end + end, }) t = {} for k, v in f (x) do t[k] = v end @@ -222,6 +234,43 @@ specify std.lua: expect (t).to_equal {} +- describe ireverse: + - before: + fname = "ireverse" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - it returns a new list: + t = {1, 2, 5} + expect (f (t)).not_to_be (t) + - it reverses the elements relative to the original list: + expect (f {1, 2, "five"}).to_equal {"five", 2, 1} + - it ignores the dictionary part of a table: + expect (f {1, 2, "five"; a = "b", c = "d"}).to_equal {"five", 2, 1} + - it respects __ipairs metamethod: | + len = require "std.operator"["#"] + x = setmetatable ({ "a string" }, { + __ipairs = function (x) + return function (x, n) + if n < len (x[1]) then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end, + }) + t = f (x) + expect (f (x)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} + - it works for an empty list: + expect (f {}).to_equal {} + + - describe lambda: - before: fname = "lambda" @@ -308,8 +357,8 @@ specify std.lua: msg = bind (badarg, {this_module, fname}) f = M[fname] - monkeys = { "assert", "case", "eval", "elems", "ielems", "ipairs", - "lambda", "memoize", "pairs", "require" } + monkeys = { "assert", "case", "elems", "eval", "ielems", "ipairs", + "ireverse", "lambda", "memoize", "pairs", "require" } t = {} f (t) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index dfbc7f7..141a126 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -77,6 +77,7 @@ specify std: inodes = std.tree.inodes, io = t.io, ipairs = std.lua.ipairs, + ireverse = std.lua.ireverse, lambda = std.lua.lambda, leaves = std.tree.leaves, map = std.functional.map, From 45851736347f8fb501b5b3cf68415f1da7b0f7db Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 24 Jul 2014 12:16:36 +0100 Subject: [PATCH 303/703] refactor: move `table.ripairs` to `lua.ripairs`. * specs/table_spec.yaml (ripairs): Specify deprecation warning on first use. * specs/lua_spec.yaml (ripairs): Specify all behaviours for ripairs. * lib/std/base.lua (ripairs): Shared core functionality for ripairs, respecting `__ipairs` metamethod even on Lua 5.1. * lib/std/table.lua (ripairs): Use it, with a deprecation warning on first use. * lib/std/lua.lua (ripairs): Re-export it from here with full argchecks. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 7 +++++++ lib/std/base.lua | 21 +++++++++++++++++++ lib/std/lua.lua | 16 +++++++++++++-- lib/std/table.lua | 17 ++++++--------- specs/lua_spec.yaml | 48 +++++++++++++++++++++++++++++++++++++++++++ specs/table_spec.yaml | 16 +++++++++------ 6 files changed, 106 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index 7012b07..eec513f 100644 --- a/NEWS +++ b/NEWS @@ -59,6 +59,9 @@ Stdlib NEWS - User visible changes - New `lua.ireverse` function for reversing the array part of any table, while respecting `__ipairs`, even on Lua 5.1. + - New `lua.ripairs` function for returning index & value pairs in + reverse order, while respecting `__ipairs`, even on Lua 5.1. + ** Incompatible changes: - `functional.bind` sets fixed positional arguments when called as @@ -105,6 +108,10 @@ Stdlib NEWS - User visible changes name now gives a deprecation warning on first use, and will be removed entirely in some future release. + - `table.ripairs` has been moved to `lua.ripairs`, the old name now + gives a deprecation warning on first use, and will be removed + entirely in some future release. + - 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. diff --git a/lib/std/base.lua b/lib/std/base.lua index d21c652..435cf60 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -614,6 +614,26 @@ local function ireverse (l) end +-- Doc-commented in lua.lua +local function ripairs (t) + local iter = getmetamethod (t, "__ipairs") + if iter then + -- Two passes in case __ipairs fetches from a proxy or similar, + -- where #t might not be accurate. + local l = {} + for i, v in iter (t) do l[i] = v end + t = l + end + + return function (t, n) + n = n - 1 + if n > 0 then + return n, t[n] + end + end, t, #t + 1 +end + + local M = { argcheck = argcheck, @@ -628,6 +648,7 @@ local M = { leaves = leaves, nop = nop, prototype = prototype, + ripairs = ripairs, split = split, toomanyarg_fmt = toomanyarg_fmt, wrapiterator = wrapiterator, diff --git a/lib/std/lua.lua b/lib/std/lua.lua index 4d83b1a..2318a55 100644 --- a/lib/std/lua.lua +++ b/lib/std/lua.lua @@ -9,8 +9,10 @@ local list = require "std.list" local operator = require "std.operator" local List = list {} -local export, getmetamethod, ielems, ireverse, split, wrapiterator = - base.export, base.getmetamethod, base.ielems, base.ireverse, base.split, base.wrapiterator +local export, getmetamethod, wrapiterator = + base.export, base.getmetamethod, base.wrapiterator +local ielems, ireverse, ripairs, split = + base.ielems, base.ireverse, base.ripairs, base.split local M = { "std.lua" } @@ -309,4 +311,14 @@ function (module, min, too_big, pattern) end) +--- An iterator like ipairs, but in reverse. +-- @function ripairs +-- @tparam table t any table +-- @treturn function iterator function +-- @treturn table *t* +-- @treturn number `#t + 1` +-- @usage for i, v = ripairs (t) do ... end +export (M, "ripairs (table)", ripairs) + + return M diff --git a/lib/std/table.lua b/lib/std/table.lua index 5e7c6f4..98c3051 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -13,7 +13,8 @@ local base = require "std.base" -local export, getmetamethod = base.export, base.getmetamethod +local export, getmetamethod, ripairs = + base.export, base.getmetamethod, base.ripairs local M = { "std.table" } @@ -227,22 +228,16 @@ function M.pack (...) end ---- An iterator like ipairs, but in reverse. +-- DEPRECATED: Remove in first release following 2015-07-11. +-- An iterator like ipairs, but in reverse. -- @function ripairs -- @tparam table t any table -- @treturn function iterator function -- @treturn table *t* -- @treturn number `#t + 1` -- @usage for i, v = ripairs (t) do ... end -export (M, "ripairs (table)", function (t) - return function (t, n) - n = n - 1 - if n > 0 then - return n, t[n] - end - end, - t, #t + 1 -end) +M.ripairs = base.deprecate (ripairs, nil, + "table.ripairs is deprecated, use lua.ripairs instead.") --- Find the number of elements in a table. diff --git a/specs/lua_spec.yaml b/specs/lua_spec.yaml index 385dae9..614d9c6 100644 --- a/specs/lua_spec.yaml +++ b/specs/lua_spec.yaml @@ -476,3 +476,51 @@ specify std.lua: 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) + + +- describe ripairs: + - before: + fname = "ripairs" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + + - 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 __ipairs metamethod: | + len = require "std.operator"["#"] + x = setmetatable ({ "a string" }, { + __ipairs = function (x) + return function (x, n) + if n < len (x[1]) then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end, + }) + t = {} + for i, v in f (x) do t[i] = v end + expect (t).to_equal {"a", " ", "s", "t", "r", "i", "n", "g"} + t = {} + for _, v in f (x) 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 {} diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 43dae9c..e08f1a7 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -440,12 +440,16 @@ specify std.table: - describe ripairs: - before: f = M.ripairs - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.ripairs' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.ripairs' (table expected, got boolean)" + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {{}, subject}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "ripairs is deprecated" + end + _, err = capture (f, {{}, subject}) + expect (err).to_be (nil) + - 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"} From 6b9c135678228213f0c42946836861c413f2a369 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Jul 2014 11:28:55 +0100 Subject: [PATCH 304/703] refactor: relocate std.lua contents to std.base and std. * specs/lua_spec.yaml: Remove. All specs moved from here... * specs/std_spec.yaml: ...to here. * specs/specs.mk (specl_SPECS): Remove specs/lua_spec.yaml. * lib/std/operator.lua (getmetamethod): Remove to break a require loop. * lib/std/lua.lua (assert, case, elems, eval, ielems, ipairs) (ireverse, lambda, memoize, pairs, require, ripairs): Move from here... * lib/std/base.lua (assert, case, elems, eval, ielems, ipairs) (ireverse, lambda, memoize, pairs, require, ripairs): ...to here, removing argchecks... * lib/std.lua.in (assert, case, elems, eval, ielems, ipairs) (ireverse, lambda, memoize, pairs, require, ripairs): ...and re-export from here with argcheck wrappers. (barrel, monkey_patch): Adjust accordingly. * lib/std/functional.lua (filter, fold, map): Adjust LDocs. (case, eval, memoize): Re-export with argcheck wrappers. * lib/std/string.lua (assert, require_version): Deprecate. * build-aux/config.ld.in (file): Remove lib/std/lua.lua. * local.mk (dist_luastd_DATA): Remove lib/std/lua.lua. * specs/functional_spec.yaml (fold): Adjust require imports. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 49 +-- build-aux/config.ld.in | 1 - lib/std.lua.in | 218 +++++++++++-- lib/std/base.lua | 521 ++++++++++++++++++++---------- lib/std/functional.lua | 14 +- lib/std/lua.lua | 324 ------------------- lib/std/operator.lua | 5 +- lib/std/string.lua | 10 +- local.mk | 1 - specs/functional_spec.yaml | 8 +- specs/lua_spec.yaml | 526 ------------------------------ specs/specs.mk | 1 - specs/std_spec.yaml | 641 +++++++++++++++++++++++++++++++++---- 13 files changed, 1149 insertions(+), 1170 deletions(-) delete mode 100644 lib/std/lua.lua delete mode 100644 specs/lua_spec.yaml diff --git a/NEWS b/NEWS index eec513f..9d8c8aa 100644 --- a/NEWS +++ b/NEWS @@ -19,47 +19,48 @@ Stdlib NEWS - User visible changes - New `std.vector` object, for clean and fast queue-like or stack-like container management. When alien is installed, and element types - are compatible, use alien.buffers for efficient element management. + are compatible, uses alien.buffers for efficient element management. - New `std.operator` module, with new functional operators for concatenation `..`, tablification `{}`, stringification `""`, length `#` and matching `~`, plus new mathematical operators `%` and `^`, and - relational operators, `<`, `<=`, `>` and `>=`. + relational operators, `<`, `<=`, `>` and `>=`. The `#` operator + respects the `__len` metamethod, if any, even on Lua 5.1. - New `functional.nop` function, for use where a function is required but no work should be done. - - New `std.lua` module collects additional Lua language features added - by stdlib that do not really belong in type modules: `std.lua.case`, - `std.lua.eval`, and `std.lua.memoize` (original access points exported - by earlier releases will be preserved for the forseeable future). + - `std` module now collects stdlib functions that do not really belong + in specific type modules: including `std.case`, `std.eval`, and + `std.memoize` (original access points exported by earlier releases will + be preserved for the forseeable future). - - `lua.memoize` now propagates multiple return values correctly. + - `std.memoize` now propagates multiple return values correctly. This allows memoizing of functions that use the `return nil, "message"` pattern for error message reporting. - - New `lua.lambda` function for compiling lambda strings: + - New `std.lambda` function for compiling lambda strings: - table.sort (t, lua.lambda "|a,b| a 0 then + return n, t[n] + end + end, t, #t + 1 +end + + +local function assert (expect, f, arg1, ...) + local msg = (arg1 ~= nil) and string.format (f, arg1, ...) or f or "" + return expect or error (msg, 2) +end + + +local function case (with, branches) + local f = branches[with] or branches[1] + if f then return f (with) end +end + + +local function eval (s) + return loadstring ("return " .. s)() +end + + +local function lambda (l) + local s + + -- Support operator table lookup. + if operator[l] then + return operator[l] + end + + -- Support "|args|expression" format. + local args, body = string.match (l, "^|([^|]*)|%s*(.+)$") + if args and body then + s = "return function (" .. args .. ") return " .. body .. " end" + end + + -- Support "=expression" format. + if not s then + body = l:match "^=%s*(.+)$" + if body then + s = [[ + return function (...) + local _1,_2,_3,_4,_5,_6,_7,_8,_9 = unpack {...} + return ]] .. body .. [[ + end + ]] + end + end + + local ok, fn + if s then + ok, fn = pcall (loadstring (s)) + end + + -- Diagnose invalid input. + if not ok then + return nil, "invalid lambda string '" .. l .. "'" + end + + return fn +end + + +local function memoize (fn, normalize) + if normalize == nil then + -- Call require here, to avoid pulling in all of 'std.string' + -- even when memoize is never called. + local stringify = require "std.string".tostring + normalize = function (...) return stringify {...} end + end + + return setmetatable ({}, { + __call = function (self, ...) + local k = normalize (...) + local t = self[k] + if t == nil then + t = {fn (...)} + self[k] = t + end + return unpack (t) + end + }) +end + + +local function require_version (module, min, too_big, pattern) + local m = require (module) + if min then + assert (module_version (m, pattern) >= version_to_list (min)) + end + if too_big then + assert (module_version (m, pattern) < version_to_list (too_big)) + end + return m +end + + + +--[[ ============================= ]]-- +--[[ Documented in functional.lua. ]]-- +--[[ ============================= ]]-- + + +local function nop () end + + + +--[[ ========================= ]]-- +--[[ Documented in object.lua. ]]-- +--[[ ========================= ]]-- + + +local function prototype (o) + return (getmetatable (o) or {})._type or io.type (o) or type (o) +end + + + +--[[ ======================== ]]-- +--[[ Documented in table.lua. ]]-- +--[[ ======================== ]]-- + + +local function getmetamethod (x, n) + local _, m = pcall (function (x) + return getmetatable (x)[n] + end, + x) + if type (m) ~= "function" then + m = nil + end + return m +end + + + +--[[ ======================= ]]-- +--[[ Documented in tree.lua. ]]-- +--[[ ======================= ]]-- + + +local function leaves (it, tr) + local function visit (n) + if type (n) == "table" then + for _, v in it (n) do + visit (v) + end + else + coroutine.yield (n) + end + end + return coroutine.wrap (visit), tr +end + + -local argcheck, argerror, argscheck, prototype -- forward declarations + +--[[ ================== ]]-- +--[[ Argument Checking. ]]-- +--[[ ================== ]]-- + + +local _ARGCHECK = require "std.debug_init"._ARGCHECK ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- +local argcheck, argerror, argscheck -- forward declarations local toomanyarg_fmt = @@ -111,8 +392,8 @@ end -- @treturn function iterator function -- @treturn table t -- @usage --- for i,v in opairs {"one", nil, "three"} do print (i, v) end -local function opairs (t) +-- for i,v in argpairs {"one", nil, "three"} do print (i, v) end +local function argpairs (t) local i, max = 0, 0 for k in pairs (t) do if type (k) == "number" and k > max then max = k end @@ -130,7 +411,7 @@ end -- @treturn table list of merged and normalized type-specs local function merge (...) local i, t = 1, {} - for _, v in opairs {...} do + for _, v in argpairs {...} do v:gsub ("([^|]+)", function (m) t[i] = m; i = i + 1 end) end return normalize (t) @@ -244,64 +525,6 @@ local function formaterror (expectedtypes, actual) end ---- Iterator adaptor for discarding first value from core iterator function. --- @func factory iterator to be wrapped --- @param ... *factory* arguments --- @treturn function iterator that discards first returned value of --- factory iterator --- @return invariant state from *factory* --- @return `true` --- @usage --- for v in wrapiterator (ipairs {"a", "b", "c"}) do process (v) end -local function wrapiterator (factory, ...) - -- Capture wrapped ctrl variable into an upvalue... - local fn, istate, ctrl = factory (...) - -- Wrap the returned iterator fn to maintain wrapped ctrl. - return function (state, _) - local v - ctrl, v = fn (state, ctrl) - if ctrl then return v end - end, istate, true -- wrapped initial state, and wrapper ctrl -end - - - - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - --- Doc-commented in object.lua -function prototype (o) - return (getmetatable (o) or {})._type or io.type (o) or type (o) -end - - --- Doc-commented in functional.lua -local function nop () 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 -local function split (s, sep) - sep = sep or "%s+" - local b, len, t, patt = 0, #s, {}, "(.-)" .. sep - if sep == "" then patt = "(.)"; t[#t + 1] = "" end - while b <= len do - local e, n, m = string.find (s, patt, b + 1) - t[#t + 1] = m or s:sub (b + 1, len) - b = n or len + 1 - end - return t -end - - if _ARGCHECK then local typeof = type -- free up `type` for use as a variable @@ -410,32 +633,10 @@ function argerror (name, i, extramsg, level) end ---- Write a deprecation warning to stderr on first call. --- @func fn deprecated function --- @string[opt] name function name for automatic warning message. --- @string[opt] warnmsg full specified warning message (overrides *name*) --- @return a function to show the warning on first call, and hand off to *fn* --- @usage funcname = deprecate (function (...) ... end, "funcname") -local function deprecate (fn, name, warnmsg) - argscheck ("std.base.deprecate", {"function", "string?", "string?"}, - {fn, name, warnmsg}) - - if not (name or warnmsg) then - error ("missing argument to 'std.base.deprecate' (2 or 3 arguments expected)", 2) - end - warnmsg = warnmsg or (name .. " is deprecated, and will go away in a future release.") - local warnp = true - return function (...) - if warnp then - local _, where = pcall (function () error ("", 4) end) - io.stderr:write ((string.gsub (where, "(^w%*%.%w*%:%d+)", "%1"))) - io.stderr:write (warnmsg .. "\n") - warnp = false - end - return fn (...) - end -end +--[[ ============ ]]-- +--[[ Maintenance. ]]-- +--[[ ============ ]]-- --- Export a function definition, optionally with argument type checking. @@ -553,106 +754,74 @@ local function export (M, decl, fn, ...) end ---- Iterator returning leaf nodes from nested tables. --- @tparam function it table iterator function --- @tparam tree|table tr tree or tree-like table --- @treturn function iterator function --- @treturn tree|table the tree `tr` -local function leaves (it, tr) - local function visit (n) - if type (n) == "table" then - for _, v in it (n) do - visit (v) - end - else - coroutine.yield (n) - end - end - return coroutine.wrap (visit), tr -end +--- Write a deprecation warning to stderr on first call. +-- @func fn deprecated function +-- @string[opt] name function name for automatic warning message. +-- @string[opt] warnmsg full specified warning message (overrides *name*) +-- @return a function to show the warning on first call, and hand off to *fn* +-- @usage funcname = deprecate (function (...) ... end, "funcname") +local function deprecate (fn, name, warnmsg) + argscheck ("std.base.deprecate", {"function", "string?", "string?"}, + {fn, name, warnmsg}) + if not (name or warnmsg) then + error ("missing argument to 'std.base.deprecate' (2 or 3 arguments expected)", 2) + end ---- Return given metamethod, if any, or nil. --- @tparam std.object x object to get metamethod of --- @string n name of metamethod to get --- @treturn function|nil metamethod function or `nil` if no metamethod or --- not a function --- @usage lookup = getmetamethod (require "std.object", "__index") -local function getmetamethod (x, n) - local _, m = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if type (m) ~= "function" then - m = nil + warnmsg = warnmsg or (name .. " is deprecated, and will go away in a future release.") + local warnp = true + return function (...) + if warnp then + local _, where = pcall (function () error ("", 4) end) + io.stderr:write ((string.gsub (where, "(^w%*%.%w*%:%d+)", "%1"))) + io.stderr:write (warnmsg .. "\n") + warnp = false + end + return fn (...) end - return m end --- Doc-commented in lua.lua -local function ielems (l) - return wrapiterator (getmetamethod (l, "__ipairs") or ipairs, l) -end +return { --- Doc-commented in lua.lua -local function ireverse (l) - local iter, r = getmetamethod (l, "__ipairs"), {} - if not iter then - -- Calculate reverse indices for direct element access. - local len = #l + 1 - for i, v in ipairs (l) do r[len - i] = v end - else - -- Two passes in case __ipairs fetches from a proxy or similar, - -- where #l might not be accurate. - local t = {} - for i, v in iter (l) do t[i] = v end - for i = #t, 1, -1 do r[#r + 1] = t[i] end - end - return r -end + -- std.lua -- + assert = assert, + case = case, + eval = eval, + elems = elems, + ielems = ielems, + ipairs = __ipairs, + ireverse = ireverse, + lambda = lambda, + memoize = memoize, + pairs = __pairs, + ripairs = ripairs, + require = require_version, + -- functional.lua -- + nop = nop, --- Doc-commented in lua.lua -local function ripairs (t) - local iter = getmetamethod (t, "__ipairs") - if iter then - -- Two passes in case __ipairs fetches from a proxy or similar, - -- where #t might not be accurate. - local l = {} - for i, v in iter (t) do l[i] = v end - t = l - end + -- object.lua -- + prototype = prototype, - return function (t, n) - n = n - 1 - if n > 0 then - return n, t[n] - end - end, t, #t + 1 -end + -- string.lua -- + split = split, + + -- table.lua -- + getmetamethod = getmetamethod, + -- tree.lua -- + leaves = leaves, + -- Argument Checking. -- + argcheck = argcheck, + argerror = argerror, + arglen = arglen, + argscheck = argscheck, -local M = { - argcheck = argcheck, - argerror = argerror, - arglen = arglen, - argscheck = argscheck, + -- Maintenance -- deprecate = deprecate, export = export, - getmetamethod = getmetamethod, - ielems = ielems, - ireverse = ireverse, - leaves = leaves, - nop = nop, - prototype = prototype, - ripairs = ripairs, - split = split, toomanyarg_fmt = toomanyarg_fmt, - wrapiterator = wrapiterator, } - - -return M diff --git a/lib/std/functional.lua b/lib/std/functional.lua index ed643fd..e8990cf 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -128,7 +128,7 @@ end) -- @treturn table elements e for which `p (e)` is not falsey. -- @see collect -- @usage --- > filter (lua.lambda "|e| e%2==0", lua.elems, {1, 2, 3, 4}) +-- > filter (std.lambda "|e| e%2==0", std.elems, {1, 2, 3, 4}) -- {2, 4} export (M, "filter (func, func, any*)", function (p, i, ...) local t = {} @@ -150,7 +150,7 @@ end) -- @return result -- @see std.list.foldl -- @see std.list.foldr --- @usage fold (math.pow, 1, lua.elems, {2, 3, 4}) +-- @usage fold (math.pow, 1, std.elems, {2, 3, 4}) export (M, "fold (func, any, func, any*)", function (f, d, i, ...) local r = d for e in i (...) do @@ -177,7 +177,7 @@ end -- @treturn table results -- @see filter -- @usage --- > map (function (e) return e % 2 end, lua.elems, {1, 2, 3, 4}) +-- > map (function (e) return e % 2 end, std.elems, {1, 2, 3, 4}) -- {1, 0, 1, 0} export (M, "map (func, func, any*)", function (f, i, ...) local t = {} @@ -200,10 +200,10 @@ M.nop = nop -- For backwards compatibility. -M.case = function (...) return require "std.lua".case (...) end -M.eval = function (...) return require "std.lua".eval (...) end -M.memoize = function (...) return require "std.lua".memoize (...) end -M.op = require "std.operator" +export (M, "case (any?, #table)", base.case) +export (M, "eval (string)", base.eval) +export (M, "memoize (func, func?)", base.memoize) +M.op = require "std.operator" return M diff --git a/lib/std/lua.lua b/lib/std/lua.lua deleted file mode 100644 index 2318a55..0000000 --- a/lib/std/lua.lua +++ /dev/null @@ -1,324 +0,0 @@ ---[[-- - Additional Lua language features. - - @module std.lua -]] - -local base = require "std.base" -local list = require "std.list" -local operator = require "std.operator" - -local List = list {} -local export, getmetamethod, wrapiterator = - base.export, base.getmetamethod, base.wrapiterator -local ielems, ireverse, ripairs, split = - base.ielems, base.ireverse, base.ripairs, base.split - -local M = { "std.lua" } - - - ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - ---- Return a List object by splitting version string on periods. --- @string version a period delimited version string --- @treturn List a list of version components -local function version_to_list (version) - return List (split (version, "%.")) -end - - ---- Extract a list of period delimited integer version components. --- @tparam table module returned from a `require` call --- @string pattern to capture version number from a string --- (default: `"%D*([%.%d]+)"`) --- @treturn List a list of version components -local function module_version (module, pattern) - local version = module.version or module._VERSION - return version_to_list (version:match (pattern or "%D*([%.%d]+)")) -end - - - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - ---- Extend to 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 --- assert (expected ~= nil, "100% unexpected!") --- assert (expected ~= nil, "%s unexpected!", expected) -export (M, "assert (any?, string?, any?*)", function (expect, f, arg1, ...) - local msg = (arg1 ~= nil) and string.format (f, arg1, ...) or f or "" - return expect or error (msg, 2) -end) - - ---- A rudimentary case statement. --- Match `with` against keys in `branches` table, and return the result --- of running the function in the table value for the matching key, or --- the first non-key value function if no key matches. --- @function case --- @param with expression to match --- @tparam table branches map possible matches to functions --- @return the return value from function with a matching key, or nil. --- @usage --- return case (type (object), { --- table = function () return something end, --- string = function () return something else end, --- function (s) error ("unhandled type: "..s) end, --- }) -export (M, "case (any?, #table)", function (with, branches) - local f = branches[with] or branches[1] - if f then return f (with) end -end) - - ---- Evaluate a string. --- @function eval --- @string s string of Lua code --- @return result of evaluating `s` --- @usage eval "math.pow (2, 10)" -export (M, "eval (string)", function (s) - return loadstring ("return " .. s)() -end) - - ---- An iterator over all elements of a sequence. --- If there is 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 --- for v in elems {a = 1, b = 2, c = 5} do process (v) end -export (M, "elems (table)", function (t) - return wrapiterator (getmetamethod (t, "__pairs") or pairs, t) -end) - - ---- An iterator over the integer keyed elements of a sequence. --- If there is an `__ipairs` metamethod, use that to iterate. --- @function ielems --- @tparam table t a table --- @treturn function iterator function --- @treturn list *l*, the list being iterated over --- @treturn int *index*, the previous iteration index --- @see elems --- @see ipairs --- @usage --- for v in ielems {"a", "b", "c"} do process (v) end -export (M, "ielems (table)", ielems) - - ---- An implementation of core ipairs that respects __ipairs even in Lua 5.1. --- @function ipairs --- @tparam table t a table --- @treturn function iterator function --- @treturn list *l*, the list being iterated over --- @treturn int *index*, the previous iteration index --- @see ielems --- @see pairs --- @usage --- for i, v in ipairs {"a", "b", "c"} do process (v) end -local ipairs = export (M, "ipairs (table)", function (l) - return ((getmetatable (l) or {}).__ipairs or ipairs) (l) -end) - - ---- A new reversed list. --- @function ireverse --- @tparam table t a table --- @treturn list a new list --- @see ielems --- @see ipairs --- @usage --- rielems = std.functional.compose (ireverse, ielems) --- for e in rielems (l) do process (e) end -export (M, "ireverse (table)", function (l) - return ireverse (l) -end) - - ---- Memoize a function, by wrapping it in a functable. --- --- To ensure that memoize always returns the same results for the same --- arguments, it passes arguments to `normalize` (std.string.tostring --- by default). You can specify a more sophisticated function if memoize --- should handle complicated argument equivalencies. --- @function memoize --- @func fn function with no side effects --- @func normalize[opt] function to normalize arguments --- @treturn functable memoized function --- @usage --- local fast = memoize (function (...) --[[ slow code ]] end) -local memoize = export (M, "memoize (func, func?)", function (fn, normalize) - if normalize == nil then - -- Call require here, to avoid pulling in all of 'std.string' - -- even when memoize is never called. - local stringify = require "std.string".tostring - normalize = function (...) return stringify {...} end - end - - return setmetatable ({}, { - __call = function (self, ...) - local k = normalize (...) - local t = self[k] - if t == nil then - t = {fn (...)} - self[k] = t - end - return unpack (t) - end - }) -end) - - ---- Signature of memoize `normalize` functions. --- @function memoize_normalize --- @param ... arguments --- @treturn string normalized arguments - - ---- Compile a lambda string into a Lua function. --- --- A valid lambda string takes one of the following forms: --- --- 1. `operator`: where *op* is a key in @{std.operator}, equivalent to that operation --- 1. `"=expression"`: equivalent to `function (...) return (expression) end` --- 1. `"|args|expression"`: equivalent to `function (args) return (expression) end` --- --- The second form (starting with `=`) automatically assigns the first --- nine arguments to parameters `_1` through `_9` for use within the --- expression body. --- @function lambda --- @string s a lambda string --- @treturn table compiled lambda string, can be called like a function --- @usage --- -- The following are all equivalent: --- lambda "<" --- lambda "= _1 < _2" --- lambda "|a,b| a= version_to_list (min)) - end - if too_big then - assert (module_version (m, pattern) < version_to_list (too_big)) - end - return m -end) - - ---- An iterator like ipairs, but in reverse. --- @function ripairs --- @tparam table t any table --- @treturn function iterator function --- @treturn table *t* --- @treturn number `#t + 1` --- @usage for i, v = ripairs (t) do ... end -export (M, "ripairs (table)", ripairs) - - -return M diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 90dac83..40bf7ee 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -5,9 +5,6 @@ ]] -local getmetamethod = require "std.base".getmetamethod - - --- Functional forms of Lua operators. -- -- Defined here so that other modules can write to it. @@ -44,7 +41,7 @@ return { ["~"] = function (s, p) return string.find (s, p) end, ["#"] = function (t) -- Lua < 5.2 doesn't call `__len` automatically! - local m = getmetamethod (t, "__len") + local m = (getmetatable (t) or {}).__len return m and m (t) or #t end, ["+"] = function (a, b) return a + b end, diff --git a/lib/std/string.lua b/lib/std/string.lua index 2721224..e896f13 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -89,9 +89,7 @@ end -- DEPRECATED: Remove in first release following 2015-07-30. -M.assert = base.deprecate (function (...) - return require "std.lua".assert (...) -end, nil, +M.assert = base.deprecate (base.assert, nil, "string.assert is deprecated, use lua.assert instead") @@ -162,9 +160,7 @@ export (M, "split (string, string?)", split) -- DEPRECATED: Remove in first release following 2015-06-30. -M.require_version = base.deprecate (function (...) - return require "std.lua".require (...) -end, nil, +M.require_version = base.deprecate (base.require, nil, "string.require_version is deprecated, use lua.require instead") @@ -585,7 +581,7 @@ end) -- @todo Make it work for recursive tables. -- @param x object to pickle -- @treturn string reversible string rendering of *x* --- @see lua.eval +-- @see std.eval -- @usage -- function slow_identity (x) return functional.eval (pickle (x)) end function M.pickle (x) diff --git a/local.mk b/local.mk index 8db4bc2..b790d2c 100644 --- a/local.mk +++ b/local.mk @@ -69,7 +69,6 @@ dist_luastd_DATA = \ lib/std/functional.lua \ lib/std/io.lua \ lib/std/list.lua \ - lib/std/lua.lua \ lib/std/math.lua \ lib/std/object.lua \ lib/std/operator.lua \ diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 553156f..538f3da 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -141,7 +141,7 @@ specify std.functional: - describe fold: - before: - lua = require "std.lua" + base = require "std.base" fname = "fold" msg = M.bind (badarg, {this_module, fname}) f = M[fname] @@ -155,12 +155,12 @@ specify std.functional: expect (f (f, 1, false)).to_error (msg (3, "function", "boolean")) - it calls a binary function over element keys: - expect (f (M.op["+"], 2, lua.ielems, {3})). + expect (f (M.op["+"], 2, base.ielems, {3})). to_be (2 + 3) - expect (f (M.op["*"], 2, lua.ielems, {3, 4})). + expect (f (M.op["*"], 2, base.ielems, {3, 4})). to_be (2 * 3 * 4) - it folds elements from left to right: - expect (f (math.pow, 2, lua.ielems, {3, 4})). + expect (f (math.pow, 2, base.ielems, {3, 4})). to_be (math.pow (math.pow (2, 3), 4)) diff --git a/specs/lua_spec.yaml b/specs/lua_spec.yaml deleted file mode 100644 index 614d9c6..0000000 --- a/specs/lua_spec.yaml +++ /dev/null @@ -1,526 +0,0 @@ -before: | - this_module = "std.lua" - global_table = "_G" - - M = require (this_module) - -specify std.lua: -- 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 {} - - - context via the std module: - - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). - to_equal {} - - -- describe assert: - - before: - fname = "assert" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses wrong argument types: - expect (f (false, false)).to_error (msg (2, "string or nil", "boolean")) - - - context when it does not trigger: - - it has a truthy initial argument: - expect (f (1)).not_to_error () - expect (f (true)).not_to_error () - expect (f "yes").not_to_error () - expect (f (false == false)).not_to_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_error () - expect (f (false)).to_error () - expect (f (1 == 0)).to_error () - - it throws an optional error string: - expect (f (false, "ah boo")).to_error "ah boo" - - it plugs specifiers with string.format: | - expect (f (nil, "%s %d: %q", "here", 42, "a string")). - to_error (string.format ("%s %d: %q", "here", 42, "a string")) - - -- describe case: - - before: - yes = function () return true end - no = function () return false end - default = function (s) return s end - branches = { yes = yes, no = no, default } - fname = "case" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (2, "non-empty table")) - - it diagnoses wrong argument types: - expect (f ("no", false)). - to_error (msg (2, "non-empty table", "boolean")) - - it diagnoses too many arguments: - expect (f (1, {2}, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) - - - it matches against branch keys: - expect (f ("yes", branches)).to_be (true) - expect (f ("no", branches)).to_be (false) - - it has a default for unmatched keys: - expect (f ("none", branches)).to_be "none" - - it returns nil for unmatched keys with no default: - expect (f ("none", { yes = yes, no = no })).to_be (nil) - - it evaluates `with` exactly once: - s = "prince" - function acc () s = s .. "s"; return s end - expect (f (acc (), { - prince = function () return "one" end, - princes = function () return "many" end, - princess = function () return "one" end, - function () return "gibberish" end, - })).to_be "many" - - -- describe elems: - - before: - fname = "elems" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - - 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: | - x = setmetatable ({ "a string" }, { - __pairs = function (x) - return function (x, n) - if n < #x[1] then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end - }) - t = {} - for v in f (x) 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 eval: - - before: - fname = "eval" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("1", false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - - it diagnoses invalid lua: - # Some internal error when eval tries to call uncompilable "=" code. - expect (f "=").to_error () - - it evaluates a string of lua code: - expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) - - -- describe ielems: - - before: - fname = "ielems" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - - 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 __ipairs metamethod: | - len = require "std.operator"["#"] - x = setmetatable ({ "a string" }, { - __ipairs = function (x) - return function (x, n) - if n < len (x[1]) then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end, - }) - t = {} - for v in f (x) 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: - fname = "ipairs" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - - 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 __ipairs metamethod: | - len = require "std.operator"["#"] - x = setmetatable ({ "a string" }, { - __ipairs = function (x) - return function (x, n) - if n < len (x[1]) then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end, - }) - t = {} - for k, v in f (x) 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 ireverse: - - before: - fname = "ireverse" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - - it returns a new list: - t = {1, 2, 5} - expect (f (t)).not_to_be (t) - - it reverses the elements relative to the original list: - expect (f {1, 2, "five"}).to_equal {"five", 2, 1} - - it ignores the dictionary part of a table: - expect (f {1, 2, "five"; a = "b", c = "d"}).to_equal {"five", 2, 1} - - it respects __ipairs metamethod: | - len = require "std.operator"["#"] - x = setmetatable ({ "a string" }, { - __ipairs = function (x) - return function (x, n) - if n < len (x[1]) then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end, - }) - t = f (x) - expect (f (x)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} - - it works for an empty list: - expect (f {}).to_equal {} - - -- describe lambda: - - before: - fname = "lambda" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - - it diagnoses wrong arguments types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("foo", false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - it diagnoses bad lambda string: - expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" - - it diagnoses an uncompilable expression: - expect (select (2, f "||+")).to_be "invalid lambda string '||+'" - expect (select (2, f "=")).to_be "invalid lambda string '='" - - - context with argument format: - - it returns a function: - expect (prototype (f "|x| 1+x")).to_be "function" - - it compiles to a working Lua function: - fn = f "||42" - expect (fn ()).to_be (42) - - it propagates argument values: - fn = f "|...| {...}" - expect (fn (1,2,3)).to_equal {1,2,3} - - context with expression format: - - it returns a function: - expect (prototype (f "=1")).to_be "function" - - it compiles to a working Lua function: - fn = f "=42" - expect (fn ()).to_be (42) - - it sets auto-argument values: - fn = f "=_1*_1" - expect (fn (42)).to_be (1764) - - -- describe memoize: - - before: - fname = "memoize" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - memfn = f (function (x) - if x then return {x} else return nil, "bzzt" end - end) - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, false)).to_error (msg (2, "function or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (f, f, false)).to_error (toomanyarg (this_module, fname, 2, 3)) - - - it propagates multiple return values: - expect (select (2, memfn (false))).to_be "bzzt" - - it returns the same object for the same arguments: - t = memfn (1) - expect (memfn (1)).to_be (t) - - it returns a different object for different arguments: - expect (memfn (1)).not_to_be (memfn (2)) - - it returns the same object for table valued arguments: - t = memfn {1, 2, 3} - expect (memfn {1, 2, 3}).to_be (t) - t = memfn {foo = "bar", baz = "quux"} - expect (memfn {foo = "bar", baz = "quux"}).to_be (t) - expect (memfn {baz = "quux", foo = "bar"}).to_be (t) - - it returns a different object for different table arguments: - expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2}) - expect (memfn {1, 2, 3}).not_to_be (memfn {3, 1, 2}) - expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2, 3, 4}) - - it accepts alternative normalization function: - normalize = function (...) return select ("#", ...) end - memfn = f (function (x) return {x} end, normalize) - expect (memfn "same").to_be (memfn "not same") - expect (memfn (1, 2)).to_be (memfn (false, "x")) - expect (memfn "one").not_to_be (memfn ("one", "two")) - - -- describe monkey_patch: - - before: - fname = "monkey_patch" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - monkeys = { "assert", "case", "elems", "eval", "ielems", "ipairs", - "ireverse", "lambda", "memoize", "pairs", "require" } - - t = {} - f (t) - - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (t, false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - - it installs monkeys to the given namespace: - expect (t).to_contain.a_permutation_of (monkeys) - for _, monkey in ipairs (monkeys) do - expect (t[monkey]).to_be (M[monkey]) - end - - -- describe pairs: - - before: - fname = "pairs" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - - 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: | - x = setmetatable ({ "a string" }, { - __pairs = function (x) - return function (x, n) - if n < #x[1] then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end - }) - t = {} - for k, v in f (x) do t[k] = v end - expect (t).to_equal {"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: - fname = "require" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("module", false)).to_error (msg (2, "string or nil", "boolean")) - expect (f ("module", "min", false)). - to_error (msg (3, "string or nil", "boolean")) - expect (f ("module", "min", "too_big", false)). - to_error (msg (4, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("module", "min", "too_big", "pattern", false)). - to_error (toomanyarg (this_module, fname, 4, 5)) - - - it diagnoses non-existent module: - expect (f ("module-not-exists", "", "")).to_error "module-not-exists" - - it diagnoses module too old: - expect (f ("std", "9999", "9999")).to_error () - - it diagnoses module too new: - expect (f ("std", "0", "0")).to_error () - - 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", "41")).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: - std = require "std" - std._VERSION, std.version = std.version, std._VERSION - expect (f ("std", "41", "9999")).to_be (require "std") - std._VERSION, std.version = std.version, std._VERSION - - 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_error () - expect (f ("std", "1.3")).to_error () - expect (f ("std", "2.1.2")).to_error () - expect (f ("std", "2")).to_error () - expect (f ("std", "1.2.10")).to_error () - - it diagnoses module too new: - expect (f ("std", nil, "1.2.2")).to_error () - expect (f ("std", nil, "1.1")).to_error () - expect (f ("std", nil, "1.1.2")).to_error () - expect (f ("std", nil, "1")).to_error () - - 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) - - -- describe ripairs: - - before: - fname = "ripairs" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) - - - 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 __ipairs metamethod: | - len = require "std.operator"["#"] - x = setmetatable ({ "a string" }, { - __ipairs = function (x) - return function (x, n) - if n < len (x[1]) then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end, - }) - t = {} - for i, v in f (x) do t[i] = v end - expect (t).to_equal {"a", " ", "s", "t", "r", "i", "n", "g"} - t = {} - for _, v in f (x) 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 {} diff --git a/specs/specs.mk b/specs/specs.mk index 0a00f75..dd0056b 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -25,7 +25,6 @@ SPECL_OPTS = --unicode specl_SPECS = \ $(srcdir)/specs/base_spec.yaml \ - $(srcdir)/specs/lua_spec.yaml \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ $(srcdir)/specs/functional_spec.yaml \ diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 141a126..a6aec9c 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -1,24 +1,70 @@ before: - std = require "std" + this_module = "std" + global_table = "_G" + + exported_apis = { "assert", "barrel", "case", "elems", "eval", "ielems", + "ipairs", "ireverse", "lambda", "memoize", "monkey_patch", + "pairs", "require", "ripairs", "version", 1 } + + M = require (this_module) specify std: +- describe require: + - 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) + - describe lazy loading: - it has no submodules on initial load: - expect (std).to_equal { - barrel = std.barrel, - monkey_patch = std.monkey_patch, - version = std.version, - } + for _, v in pairs (M) do + expect (type (v)).not_to_be "table" + end - it loads submodules on demand: - lazy = std.set + lazy = M.set expect (lazy).to_be (require "std.set") - it loads submodule functions on demand: - expect (std.object.prototype (std.set {"Lazy"})). + expect (M.object.prototype (M.set {"Lazy"})). to_be "Set" +- describe assert: + - before: + fname = "assert" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses wrong argument types: + expect (f (false, false)).to_error (msg (2, "string or nil", "boolean")) + + - context when it does not trigger: + - it has a truthy initial argument: + expect (f (1)).not_to_error () + expect (f (true)).not_to_error () + expect (f "yes").not_to_error () + expect (f (false == false)).not_to_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_error () + expect (f (false)).to_error () + expect (f (1 == 0)).to_error () + - it throws an optional error string: + expect (f (false, "ah boo")).to_error "ah boo" + - it plugs specifiers with string.format: | + expect (f (nil, "%s %d: %q", "here", 42, "a string")). + to_error (string.format ("%s %d: %q", "here", 42, "a string")) + + - describe barrel: - before: - f = std.barrel + f = M.barrel io_mt = {} t = { io = { @@ -34,75 +80,377 @@ specify std: expect (f (false)). to_error "bad argument #1 to 'std.barrel' (table or nil expected, got boolean)" - it installs std.io monkey patches: - expect (io_mt.readlines).to_be (std.io.readlines) - expect (io_mt.writelines).to_be (std.io.writelines) + expect (io_mt.readlines).to_be (M.io.readlines) + expect (io_mt.writelines).to_be (M.io.writelines) - it installs std.lua monkey patches: - expect (t.assert).to_be (std.lua.assert) - expect (t.case).to_be (std.lua.case) - expect (t.elems).to_be (std.lua.elems) - expect (t.eval).to_be (std.lua.eval) - expect (t.ielems).to_be (std.lua.ielems) - expect (t.ipairs).to_be (std.lua.ipairs) - expect (t.lambda).to_be (std.lua.lambda) - expect (t.memoize).to_be (std.lua.memoize) - expect (t.pairs).to_be (std.lua.pairs) - expect (t.require).to_be (std.lua.require) + expect (t.assert).to_be (M.assert) + expect (t.case).to_be (M.case) + expect (t.elems).to_be (M.elems) + expect (t.eval).to_be (M.eval) + expect (t.ielems).to_be (M.ielems) + expect (t.ipairs).to_be (M.ipairs) + expect (t.lambda).to_be (M.lambda) + expect (t.memoize).to_be (M.memoize) + expect (t.pairs).to_be (M.pairs) + expect (t.require).to_be (M.require) - it installs std.math monkey patches: - expect (t.math.floor).to_be (std.math.floor) + expect (t.math.floor).to_be (M.math.floor) - it installs std.string monkey patches: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" - expect (mt.__append).to_be (std.string.__append) - expect (mt.__concat).to_be (std.string.__concat) - expect (mt.__index).to_be (std.string.__index) - expect (t.tostring).to_be (std.string.tostring) + expect (mt.__append).to_be (M.string.__append) + expect (mt.__concat).to_be (M.string.__concat) + expect (mt.__index).to_be (M.string.__index) + expect (t.tostring).to_be (M.string.tostring) - it installs std.table monkey patches: - expect (t.table.sort).to_be (std.table.sort) + expect (t.table.sort).to_be (M.table.sort) - it scribbles into the supplied namespace: expect (t).should_equal { - assert = std.lua.assert, - bind = std.functional.bind, - case = std.lua.case, - collect = std.functional.collect, - compose = std.functional.compose, - curry = std.functional.curry, - die = std.io.die, - elems = std.lua.elems, - eval = std.lua.eval, - filter = std.functional.filter, - fold = std.functional.fold, - id = std.functional.id, - ielems = std.lua.ielems, - ileaves = std.tree.ileaves, - inodes = std.tree.inodes, + assert = M.assert, + bind = M.functional.bind, + case = M.case, + collect = M.functional.collect, + compose = M.functional.compose, + curry = M.functional.curry, + die = M.io.die, + elems = M.elems, + eval = M.eval, + filter = M.functional.filter, + fold = M.functional.fold, + id = M.functional.id, + ielems = M.ielems, + ileaves = M.tree.ileaves, + inodes = M.tree.inodes, io = t.io, - ipairs = std.lua.ipairs, - ireverse = std.lua.ireverse, - lambda = std.lua.lambda, - leaves = std.tree.leaves, - map = std.functional.map, + ipairs = M.ipairs, + ireverse = M.ireverse, + lambda = M.lambda, + leaves = M.tree.leaves, + map = M.functional.map, math = t.math, - memoize = std.lua.memoize, - metamethod = std.table.metamethod, - nodes = std.tree.nodes, - op = std.functional.op, - pack = std.table.pack, - pairs = std.lua.pairs, - pickle = std.string.pickle, - prettytostring = std.string.prettytostring, - render = std.string.render, - require = std.lua.require, - require_version = std.string.require_version, - ripairs = std.table.ripairs, + memoize = M.memoize, + metamethod = M.table.metamethod, + nodes = M.tree.nodes, + op = M.functional.op, + pack = M.table.pack, + pairs = M.pairs, + pickle = M.string.pickle, + prettytostring = M.string.prettytostring, + render = M.string.render, + require = M.require, + require_version = M.string.require_version, + ripairs = M.ripairs, table = t.table, - tostring = std.string.tostring, - totable = std.table.totable, - warn = std.io.warn, + tostring = M.string.tostring, + totable = M.table.totable, + warn = M.io.warn, } +- describe case: + - before: + yes = function () return true end + no = function () return false end + default = function (s) return s end + branches = { yes = yes, no = no, default } + fname = "case" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (2, "non-empty table")) + - it diagnoses wrong argument types: + expect (f ("no", false)). + to_error (msg (2, "non-empty table", "boolean")) + - it diagnoses too many arguments: + expect (f (1, {2}, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + + - it matches against branch keys: + expect (f ("yes", branches)).to_be (true) + expect (f ("no", branches)).to_be (false) + - it has a default for unmatched keys: + expect (f ("none", branches)).to_be "none" + - it returns nil for unmatched keys with no default: + expect (f ("none", { yes = yes, no = no })).to_be (nil) + - it evaluates `with` exactly once: + s = "prince" + function acc () s = s .. "s"; return s end + expect (f (acc (), { + prince = function () return "one" end, + princes = function () return "many" end, + princess = function () return "one" end, + function () return "gibberish" end, + })).to_be "many" + + +- describe elems: + - before: + fname = "elems" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - 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: | + x = setmetatable ({ "a string" }, { + __pairs = function (x) + return function (x, n) + if n < #x[1] then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end + }) + t = {} + for v in f (x) 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 eval: + - before: + fname = "eval" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("1", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - it diagnoses invalid lua: + # Some internal error when eval tries to call uncompilable "=" code. + expect (f "=").to_error () + - it evaluates a string of lua code: + expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) + + +- describe ielems: + - before: + fname = "ielems" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - 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 __ipairs metamethod: | + len = require "std.operator"["#"] + x = setmetatable ({ "a string" }, { + __ipairs = function (x) + return function (x, n) + if n < len (x[1]) then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end, + }) + t = {} + for v in f (x) 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: + fname = "ipairs" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - 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 __ipairs metamethod: | + len = require "std.operator"["#"] + x = setmetatable ({ "a string" }, { + __ipairs = function (x) + return function (x, n) + if n < len (x[1]) then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end, + }) + t = {} + for k, v in f (x) 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 ireverse: + - before: + fname = "ireverse" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - it returns a new list: + t = {1, 2, 5} + expect (f (t)).not_to_be (t) + - it reverses the elements relative to the original list: + expect (f {1, 2, "five"}).to_equal {"five", 2, 1} + - it ignores the dictionary part of a table: + expect (f {1, 2, "five"; a = "b", c = "d"}).to_equal {"five", 2, 1} + - it respects __ipairs metamethod: | + len = require "std.operator"["#"] + x = setmetatable ({ "a string" }, { + __ipairs = function (x) + return function (x, n) + if n < len (x[1]) then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end, + }) + t = f (x) + expect (f (x)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} + - it works for an empty list: + expect (f {}).to_equal {} + + +- describe lambda: + - before: + fname = "lambda" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong arguments types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("foo", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it diagnoses bad lambda string: + expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" + - it diagnoses an uncompilable expression: + expect (select (2, f "||+")).to_be "invalid lambda string '||+'" + expect (select (2, f "=")).to_be "invalid lambda string '='" + + - context with argument format: + - it returns a function: + expect (prototype (f "|x| 1+x")).to_be "function" + - it compiles to a working Lua function: + fn = f "||42" + expect (fn ()).to_be (42) + - it propagates argument values: + fn = f "|...| {...}" + expect (fn (1,2,3)).to_equal {1,2,3} + - context with expression format: + - it returns a function: + expect (prototype (f "=1")).to_be "function" + - it compiles to a working Lua function: + fn = f "=42" + expect (fn ()).to_be (42) + - it sets auto-argument values: + fn = f "=_1*_1" + expect (fn (42)).to_be (1764) + + +- describe memoize: + - before: + fname = "memoize" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + memfn = f (function (x) + if x then return {x} else return nil, "bzzt" end + end) + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, false)).to_error (msg (2, "function or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (f, f, false)).to_error (toomanyarg (this_module, fname, 2, 3)) + + - it propagates multiple return values: + expect (select (2, memfn (false))).to_be "bzzt" + - it returns the same object for the same arguments: + t = memfn (1) + expect (memfn (1)).to_be (t) + - it returns a different object for different arguments: + expect (memfn (1)).not_to_be (memfn (2)) + - it returns the same object for table valued arguments: + t = memfn {1, 2, 3} + expect (memfn {1, 2, 3}).to_be (t) + t = memfn {foo = "bar", baz = "quux"} + expect (memfn {foo = "bar", baz = "quux"}).to_be (t) + expect (memfn {baz = "quux", foo = "bar"}).to_be (t) + - it returns a different object for different table arguments: + expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2}) + expect (memfn {1, 2, 3}).not_to_be (memfn {3, 1, 2}) + expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2, 3, 4}) + - it accepts alternative normalization function: + normalize = function (...) return select ("#", ...) end + memfn = f (function (x) return {x} end, normalize) + expect (memfn "same").to_be (memfn "not same") + expect (memfn (1, 2)).to_be (memfn (false, "x")) + expect (memfn "one").not_to_be (memfn ("one", "two")) + + - describe monkey_patch: - before: - f = std.monkey_patch + f = M.monkey_patch io_mt = {} t = { io = { @@ -118,16 +466,167 @@ specify std: expect (f (false)). to_error "bad argument #1 to 'std.monkey_patch' (table or nil expected, got boolean)" - it installs std.io monkey patches: - expect (io_mt.readlines).to_be (std.io.readlines) - expect (io_mt.writelines).to_be (std.io.writelines) + expect (io_mt.readlines).to_be (M.io.readlines) + expect (io_mt.writelines).to_be (M.io.writelines) - it installs std.math monkey patches: - expect (t.math.floor).to_be (std.math.floor) + expect (t.math.floor).to_be (M.math.floor) - it installs std.string monkey patches: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" - expect (mt.__append).to_be (std.string.__append) - expect (mt.__concat).to_be (std.string.__concat) - expect (mt.__index).to_be (std.string.__index) - expect (t.tostring).to_be (std.string.tostring) + expect (mt.__append).to_be (M.string.__append) + expect (mt.__concat).to_be (M.string.__concat) + expect (mt.__index).to_be (M.string.__index) + expect (t.tostring).to_be (M.string.tostring) - it installs std.table monkey patches: - expect (t.table.sort).to_be (std.table.sort) + expect (t.table.sort).to_be (M.table.sort) + + +- describe pairs: + - before: + fname = "pairs" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - 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: | + x = setmetatable ({ "a string" }, { + __pairs = function (x) + return function (x, n) + if n < #x[1] then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end + }) + t = {} + for k, v in f (x) do t[k] = v end + expect (t).to_equal {"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: + fname = "require" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("module", false)).to_error (msg (2, "string or nil", "boolean")) + expect (f ("module", "min", false)). + to_error (msg (3, "string or nil", "boolean")) + expect (f ("module", "min", "too_big", false)). + to_error (msg (4, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("module", "min", "too_big", "pattern", false)). + to_error (toomanyarg (this_module, fname, 4, 5)) + + - it diagnoses non-existent module: + expect (f ("module-not-exists", "", "")).to_error "module-not-exists" + - it diagnoses module too old: + expect (f ("std", "9999", "9999")).to_error () + - it diagnoses module too new: + expect (f ("std", "0", "0")).to_error () + - 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", "41")).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: + std = require "std" + M._VERSION, M.version = M.version, M._VERSION + expect (f ("std", "41", "9999")).to_be (require "std") + M._VERSION, M.version = M.version, M._VERSION + - 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_error () + expect (f ("std", "1.3")).to_error () + expect (f ("std", "2.1.2")).to_error () + expect (f ("std", "2")).to_error () + expect (f ("std", "1.2.10")).to_error () + - it diagnoses module too new: + expect (f ("std", nil, "1.2.2")).to_error () + expect (f ("std", nil, "1.1")).to_error () + expect (f ("std", nil, "1.1.2")).to_error () + expect (f ("std", nil, "1")).to_error () + - 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) + + +- describe ripairs: + - before: + fname = "ripairs" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + + - 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 __ipairs metamethod: | + len = require "std.operator"["#"] + x = setmetatable ({ "a string" }, { + __ipairs = function (x) + return function (x, n) + if n < len (x[1]) then + return n+1, string.sub (x[1], n+1, n+1) + end + end, x, 0 + end, + }) + t = {} + for i, v in f (x) do t[i] = v end + expect (t).to_equal {"a", " ", "s", "t", "r", "i", "n", "g"} + t = {} + for _, v in f (x) 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 {} From d4125930f23f2d44c3bde8971bc75a4ed5740fc8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Jul 2014 13:16:34 +0100 Subject: [PATCH 305/703] refactor: move `std.string.tostring` to `std.tostring`. * lib/std/string.lua (render, tostring): Move from here... * lib/std/base.lua (render, tostring): ...to here, with argchecks removed. * lib/std/string.lua (render): Re-export base.render from here. (tostring): Re-export base.tostring with a deprecation notice. * lib/std.lua.in (tostring): Re-export base.tostring from here. (memoize): Simplify accordingly. * specs/debug_spec.yaml, specs/string_spec.yaml, specs/std_spec.yaml: Adjust accordingly. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++ lib/std.lua.in | 9 +++ lib/std/base.lua | 135 +++++++++++++++++++++++++++-------------- lib/std/string.lua | 72 ++++------------------ specs/debug_spec.yaml | 8 +-- specs/std_spec.yaml | 38 ++++++++++-- specs/string_spec.yaml | 21 +++++-- 7 files changed, 169 insertions(+), 118 deletions(-) diff --git a/NEWS b/NEWS index 9d8c8aa..13a2388 100644 --- a/NEWS +++ b/NEWS @@ -109,6 +109,10 @@ Stdlib NEWS - User visible changes name now gives a deprecation warning on first use, and will be removed entirely in some future release. + - `string.tostring` has been moved to `std.tostring`, the old name now + gives a deprecation warning on first use, and will be removed + entirely in some future release. + - `table.ripairs` has been moved to `std.ripairs`, the old name now gives a deprecation warning on first use, and will be removed entirely in some future release. diff --git a/lib/std.lua.in b/lib/std.lua.in index 1ed2782..d986326 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -198,6 +198,15 @@ export (M, "require (string, string?, string?, string?)", base.require) export (M, "ripairs (table)", base.ripairs) +--- Extend `tostring` to render table contents as a string. +-- @function tostring +-- @param x object to convert to string +-- @treturn string compact string rendering of *x* +-- @usage +-- print (std.tostring {foo="bar","baz"}) --> {1=baz,foo=bar} +export (M, "tostring (any?)", base.tostring) + + --- Overwrite core methods and metamethods with `std` enhanced versions. -- -- Loads all `std` submodules with a `monkey_patch` method, and runs diff --git a/lib/std/base.lua b/lib/std/base.lua index c2981a1..7972619 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -27,11 +27,75 @@ local operator = require "std.operator" +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + +--- Make a shallow copy of a table. +-- @tparam table t source table +-- @treturn table shallow copy of *t* +local function copy (t) + local new = {} + for k, v in pairs (t) do new[k] = v end + return new +end + + + +--[[ ======================== ]]-- +--[[ Documented in table.lua. ]]-- +--[[ ======================== ]]-- + + +local function getmetamethod (x, n) + local _, m = pcall (function (x) + return getmetatable (x)[n] + end, + x) + if type (m) ~= "function" then + m = nil + end + return m +end + + + --[[ ========================= ]]-- --[[ Documented in string.lua. ]]-- --[[ ========================= ]]-- +local function render (x, open, close, elem, pair, sep, roots) + local function stop_roots (x) + return roots[x] or render (x, open, close, elem, pair, sep, copy (roots)) + end + roots = roots or {} + if type (x) ~= "table" or getmetamethod (x, "__tostring") then + return elem (x) + else + local s = {} + s[#s + 1] = open (x) + roots[x] = elem (x) + + -- create a sorted list of keys + local ord = {} + for k, _ in pairs (x) do ord[#ord + 1] = k end + table.sort (ord, function (a, b) return tostring (a) < tostring (b) end) + + -- render x elements in order + local i, v = nil, nil + for _, j in ipairs (ord) do + local w = x[j] + s[#s + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) + i, v = j, w + end + s[#s + 1] = sep (x, i, v, nil, nil) .. close (x) + return table.concat (s) + end +end + + local function split (s, sep) sep = sep or "%s+" local b, len, t, patt = 0, #s, {}, "(.-)" .. sep @@ -45,7 +109,6 @@ local function split (s, sep) end - --[[ ====================== ]]-- --[[ Documented in std.lua. ]]-- --[[ ====================== ]]-- @@ -209,12 +272,33 @@ local function lambda (l) end +local function require_version (module, min, too_big, pattern) + local m = require (module) + if min then + assert (module_version (m, pattern) >= version_to_list (min)) + end + if too_big then + assert (module_version (m, pattern) < version_to_list (too_big)) + end + return m +end + + +local _tostring = _G.tostring + +local function tostring (x) + return render (x, lambda '="{"', lambda '="}"', _tostring, + lambda '=_4.."=".._5', + lambda '=_2 and _4 and "," or ""') +end + + + local function memoize (fn, normalize) if normalize == nil then -- Call require here, to avoid pulling in all of 'std.string' -- even when memoize is never called. - local stringify = require "std.string".tostring - normalize = function (...) return stringify {...} end + normalize = function (...) return tostring {...} end end return setmetatable ({}, { @@ -231,19 +315,6 @@ local function memoize (fn, normalize) end -local function require_version (module, min, too_big, pattern) - local m = require (module) - if min then - assert (module_version (m, pattern) >= version_to_list (min)) - end - if too_big then - assert (module_version (m, pattern) < version_to_list (too_big)) - end - return m -end - - - --[[ ============================= ]]-- --[[ Documented in functional.lua. ]]-- --[[ ============================= ]]-- @@ -264,24 +335,6 @@ end ---[[ ======================== ]]-- ---[[ Documented in table.lua. ]]-- ---[[ ======================== ]]-- - - -local function getmetamethod (x, n) - local _, m = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if type (m) ~= "function" then - m = nil - end - return m -end - - - --[[ ======================= ]]-- --[[ Documented in tree.lua. ]]-- --[[ ======================= ]]-- @@ -319,16 +372,6 @@ local toomanyarg_fmt = "too many arguments to '%s' (no more than %d expected, got %d)" ---- Make a shallow copy of a table. --- @tparam table t source table --- @treturn table shallow copy of *t* -local function copy (t) - local new = {} - for k, v in pairs (t) do new[k] = v end - return new -end - - --- Concatenate a table of strings using ", " and " or " delimiters. -- @tparam table alternatives a table of strings -- @treturn string string of elements from alternatives delimited by ", " @@ -798,6 +841,7 @@ return { pairs = __pairs, ripairs = ripairs, require = require_version, + tostring = tostring, -- functional.lua -- nop = nop, @@ -806,7 +850,8 @@ return { prototype = prototype, -- string.lua -- - split = split, + render = render, + split = split, -- table.lua -- getmetamethod = getmetamethod, diff --git a/lib/std/string.lua b/lib/std/string.lua index e896f13..dec381b 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -20,7 +20,7 @@ local export, getmetamethod, split = base.export, base.getmetamethod, base.split local _format = string.format -local _tostring = _G.tostring +local _tostring = base.tostring local M = { "std.string" } @@ -60,7 +60,7 @@ end -- local string = require "std.string".monkey_patch () -- concatenated = "foo" .. {"bar"} function M.__concat (s, o) - return M.tostring (s) .. M.tostring (o) + return _tostring (s) .. _tostring (o) end @@ -90,7 +90,7 @@ end -- DEPRECATED: Remove in first release following 2015-07-30. M.assert = base.deprecate (base.assert, nil, - "string.assert is deprecated, use lua.assert instead") + "std.string.assert is deprecated, use std.assert instead") --- Extend to work better with one argument. @@ -130,7 +130,7 @@ end) -- @return list of `{from, to; capt = {captures}}` -- @see std.string.tfind -- @usage --- for t in lua.elems (finds ("the target string", "%S+")) do +-- for t in std.elems (finds ("the target string", "%S+")) do -- print (tostring (t.capt)) -- end export (M, "finds (string, string, int?, boolean|:plain?)", function (s, p, i, ...) @@ -161,7 +161,7 @@ export (M, "split (string, string?)", split) -- DEPRECATED: Remove in first release following 2015-06-30. M.require_version = base.deprecate (base.require, nil, - "string.require_version is deprecated, use lua.require instead") + "std.string.require_version is deprecated, use std.require instead") --- Overwrite core methods and metamethods with `std` enhanced versions. @@ -175,8 +175,6 @@ M.require_version = base.deprecate (base.require, nil, -- @treturn table the module table -- @usage local string = require "std.string".monkey_patch () export (M, "monkey_patch (table?)", function (namespace) - (namespace or _G).tostring = M.tostring - local string_metatable = getmetatable "" string_metatable.__concat = M.__concat string_metatable.__index = M.__index @@ -408,39 +406,12 @@ end) -- @return string representation of *x* -- @usage -- function tostring (x) --- return render (x, lambda '="{"', lambda '="}"', string.tostring, +-- return render (x, lambda '="{"', lambda '="}"', tostring, -- lambda '=_4.."=".._5', lambda '= _4 and "," or ""', -- lambda '=","') -- end -render = export (M, "render (any?, func, func, func, func, func, table?)", -function (x, open, close, elem, pair, sep, roots) - local function stop_roots (x) - return roots[x] or render (x, open, close, elem, pair, sep, table.clone (roots)) - end - roots = roots or {} - if type (x) ~= "table" or getmetamethod (x, "__tostring") then - return elem (x) - else - local s = StrBuf {} - s = s .. open (x) - roots[x] = elem (x) - - -- create a sorted list of keys - local ord = {} - for k, _ in pairs (x) do ord[#ord + 1] = k end - table.sort (ord, function (a, b) return tostring (a) < tostring (b) end) - - -- render x elements in order - local i, v = nil, nil - for _, j in ipairs (ord) do - local w = x[j] - s = s .. sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) - i, v = j, w - end - s = s .. sep (x, i, v, nil, nil) .. close (x) - return s:tostring () - end -end) +local render = export (M, + "render (any?, func, func, func, func, func, table?)", base.render) --- Signature of render open table callback. @@ -461,7 +432,7 @@ end) -- @function render_element -- @param x element to render -- @treturn string element rendering --- @usage function element (e) return require "string".tostring (e) end +-- @usage function element (e) return require "std".tostring (e) end --- Signature of render pair callback. @@ -490,28 +461,9 @@ end) -- function separator (_, _, _, fk) return fk and "," or "" end ---- Extend `tostring` to render table contents as a string. --- @function tostring --- @param x object to convert to string --- @treturn string compact string rendering of *x* --- @usage --- local tostring = require "std.string".tostring --- print {foo="bar","baz"} --> {1=baz,foo=bar} -function M.tostring (x) - return render (x, - function () return "{" end, - function () return "}" end, - _tostring, - function (t, _, _, i, v) - return i .. "=" .. v - end, - function (_, i, _, j) - if i and j then - return "," - end - return "" - end) -end +-- DEPRECATED: Remove in first release following 2015-07-30. +M.tostring = base.deprecate (base.tostring, nil, + "std.string.tostring is deprecated, use std.tostring instead") --- Pretty-print a table, or other object. diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index f876bd9..c987c9d 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -403,7 +403,7 @@ specify std.debug: function mkwrap (x) local fmt = "%s" if type (x) == "string" then fmt = "%q" end - return string.format (fmt, require "std.string".tostring (x)) + return string.format (fmt, require "std".tostring (x)) end function mkdebug (debugp, ...) @@ -411,7 +411,7 @@ specify std.debug: _DEBUG = %s require "std.debug" (%s) ]], - require "std.string".tostring (debugp), + require "std".tostring (debugp), table.concat (require "std.list".map (mkwrap, {...}), ", ")) end @@ -441,7 +441,7 @@ specify std.debug: function mkwrap (x) local fmt = "%s" if type (x) == "string" then fmt = "%q" end - return string.format (fmt, require "std.string".tostring (x)) + return string.format (fmt, require "std".tostring (x)) end function mksay (debugp, ...) @@ -449,7 +449,7 @@ specify std.debug: _DEBUG = %s require "std.debug".say (%s) ]], - require "std.string".tostring (debugp), + require "std".tostring (debugp), table.concat (require "std.list".map (mkwrap, {...}), ", ")) end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index a6aec9c..6649d18 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -4,7 +4,7 @@ before: exported_apis = { "assert", "barrel", "case", "elems", "eval", "ielems", "ipairs", "ireverse", "lambda", "memoize", "monkey_patch", - "pairs", "require", "ripairs", "version", 1 } + "pairs", "require", "ripairs", "tostring", "version", 1 } M = require (this_module) @@ -101,7 +101,6 @@ specify std: expect (mt.__append).to_be (M.string.__append) expect (mt.__concat).to_be (M.string.__concat) expect (mt.__index).to_be (M.string.__index) - expect (t.tostring).to_be (M.string.tostring) - it installs std.table monkey patches: expect (t.table.sort).to_be (M.table.sort) - it scribbles into the supplied namespace: @@ -141,7 +140,7 @@ specify std: require_version = M.string.require_version, ripairs = M.ripairs, table = t.table, - tostring = M.string.tostring, + tostring = M.tostring, totable = M.table.totable, warn = M.io.warn, } @@ -465,6 +464,12 @@ specify std: - it diagnoses wrong argument types: | expect (f (false)). to_error "bad argument #1 to 'std.monkey_patch' (table or nil expected, got boolean)" + - it installs std monkey patches: + for _, v in ipairs (exported_apis) do + if type (M[v]) == "function" and v ~= "barrel" and v ~= "monkey_patch" then + expect (t[v]).to_be (M[v]) + end + end - it installs std.io monkey patches: expect (io_mt.readlines).to_be (M.io.readlines) expect (io_mt.writelines).to_be (M.io.writelines) @@ -476,7 +481,6 @@ specify std: expect (mt.__append).to_be (M.string.__append) expect (mt.__concat).to_be (M.string.__concat) expect (mt.__index).to_be (M.string.__index) - expect (t.tostring).to_be (M.string.tostring) - it installs std.table monkey patches: expect (t.table.sort).to_be (M.table.sort) @@ -630,3 +634,29 @@ specify std: t = {} for k, v in f {} do t[k] = v end expect (t).to_equal {} + + +- describe tostring: + - before: + f = M.tostring + + - it renders primitives exactly like system tostring: + expect (f (nil)).to_be (tostring (nil)) + expect (f (false)).to_be (tostring (false)) + expect (f (42)).to_be (tostring (42)) + expect (f (f)).to_be (tostring (f)) + expect (f "a string").to_be "a string" + - it renders empty tables as a pair of braces: + expect (f {}).to_be ("{}") + - it renders table array part compactly: + expect (f {"one", "two", "five"}). + to_be '{1=one,2=two,3=five}' + - it renders a table dictionary part compactly: + expect (f { one = true, two = 2, three = {3}}). + to_be '{one=true,three={1=3},two=2}' + - it renders table keys in table.sort order: + expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }). + to_be '{five=1,four=2,one=3,three=4,two=5}' + - it renders keys with invalid symbol names compactly: + expect (f { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }). + to_be '{?=1,[]=1,_=0,a-key=1,word=0}' diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 11bf049..12fb251 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -46,11 +46,11 @@ specify std.string: - it stringifies non-string arguments: argument = { "a table" } expect (subject .. argument). - to_be (string.format ("%s%s", subject, M.tostring (argument))) + to_be (string.format ("%s%s", subject, require "std".tostring (argument))) - it stringifies nil arguments: argument = nil expect (subject .. argument). - to_be (string.format ("%s%s", subject, M.tostring (argument))) + to_be (string.format ("%s%s", subject, require "std".tostring (argument))) - it does not perturb the original subject: original = subject newstring = subject .. " concatenate something" @@ -344,8 +344,6 @@ specify std.string: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" expect (mt.__index).to_be (M.__index) - - it installs the tostring function: - expect (t.tostring).to_be (M.tostring) - describe numbertosi: @@ -775,9 +773,22 @@ specify std.string: expect (subject).to_be (original) +# DEPRECATED: Remove in first release following 2015-07-30. - describe tostring: - before: - f = M.tostring + fname = "tostring" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {"std.string"}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "tostring is deprecated" + end + _, err = capture (f, {"std.string"}) + expect (err).to_be (nil) + - it renders primitives exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) expect (f (false)).to_be (tostring (false)) From b42f8b13a7d29739ecc8599217e51c3edab818e8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Jul 2014 14:51:49 +0100 Subject: [PATCH 306/703] refactor: move `table.metamethod` to `std.getmetamethod`. Be more in keeping with the style of core Lua. * lib/std/table.lua (metamethod): Deprecate. * lib/std.lua.in (getmetamethod): Export from here instead. * specs/table_spec.yaml, specs/std_spec.yaml: Adjust accordingly. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 4 ++++ lib/std.lua.in | 19 +++++++++++----- lib/std/table.lua | 24 +++++--------------- specs/std_spec.yaml | 53 +++++++++++++++++++++++++++++++++++++++---- specs/table_spec.yaml | 24 +++++++++++--------- 5 files changed, 85 insertions(+), 39 deletions(-) diff --git a/NEWS b/NEWS index 13a2388..a56a583 100644 --- a/NEWS +++ b/NEWS @@ -113,6 +113,10 @@ Stdlib NEWS - User visible changes gives a deprecation warning on first use, and will be removed entirely in some future release. + - `table.metamethod` has been moved to `std.getmetamethod`, the old + name now gives a deprecation warning on first use, and will be + removed entirely in some future release. + - `table.ripairs` has been moved to `std.ripairs`, the old name now gives a deprecation warning on first use, and will be removed entirely in some future release. diff --git a/lib/std.lua.in b/lib/std.lua.in index d986326..524316f 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -121,6 +121,15 @@ export (M, "ipairs (table)", base.ipairs) export (M, "ireverse (table)", base.ireverse) +--- Return named metamethod, if any, or nil. +-- @function getmetamethod +-- @tparam table t table to get metamethod of +-- @string n name of metamethod to get +-- @treturn function|nil metamethod function, or `nil` if no metamethod +-- @usage lookup = getmetamethod (require "std.object", "__index") +export (M, "getmetamethod (object|table, string)", base.getmetamethod) + + --- Compile a lambda string into a Lua function. -- -- A valid lambda string takes one of the following forms: @@ -246,6 +255,7 @@ export (M, "barrel (table?)", function (namespace) namespace = namespace or _G -- Older releases installed the following into _G by default. + namespace.metamethod = M.getmetamethod for v in base.ielems { "functional.bind", "functional.collect", "functional.compose", "functional.curry", "functional.filter", "functional.fold", @@ -256,18 +266,15 @@ export (M, "barrel (table?)", function (namespace) "string.pickle", "string.prettytostring", "string.render", "string.require_version", "string.tostring", - "table.metamethod", "table.pack", "table.totable", + "table.pack", "table.totable", "tree.ileaves", "tree.inodes", "tree.leaves", "tree.nodes", } do local module, method = v:match "^(.*)%.(.-)$" - if module then - namespace[method] = M[module][method] - else - namespace[v] = M[v] - end + namespace[method] = M[module][method] end + return monkey_patch (namespace) end) diff --git a/lib/std/table.lua b/lib/std/table.lua index 98c3051..2e68e36 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -13,8 +13,7 @@ local base = require "std.base" -local export, getmetamethod, ripairs = - base.export, base.getmetamethod, base.ripairs +local export, getmetamethod = base.export, base.getmetamethod local M = { "std.table" } @@ -197,13 +196,9 @@ export (M, "merge_select (table, table, [table], boolean|:nometa?)", merge_namedfields) ---- Return given metamethod, if any, or nil. --- @function metamethod --- @tparam object x object to get metamethod of --- @string n name of metamethod to get --- @treturn function|nil metamethod function, or `nil` if no metamethod --- @usage lookup = metamethod (require "std.object", "__index") -export (M, "metamethod (object|table, string)", getmetamethod) +-- DEPRECATED: Remove in first release following 2015-07-30. +M.metamethod = base.deprecate (getmetamethod, nil, + "table.metamethod is deprecated, use std.getmetamethod instead.") --- Make a table with a default value for unset keys. @@ -229,15 +224,8 @@ end -- DEPRECATED: Remove in first release following 2015-07-11. --- An iterator like ipairs, but in reverse. --- @function ripairs --- @tparam table t any table --- @treturn function iterator function --- @treturn table *t* --- @treturn number `#t + 1` --- @usage for i, v = ripairs (t) do ... end -M.ripairs = base.deprecate (ripairs, nil, - "table.ripairs is deprecated, use lua.ripairs instead.") +M.ripairs = base.deprecate (base.ripairs, nil, + "table.ripairs is deprecated, use std.ripairs instead.") --- Find the number of elements in a table. diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 6649d18..ddd3bb6 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -2,9 +2,10 @@ before: this_module = "std" global_table = "_G" - exported_apis = { "assert", "barrel", "case", "elems", "eval", "ielems", - "ipairs", "ireverse", "lambda", "memoize", "monkey_patch", - "pairs", "require", "ripairs", "tostring", "version", 1 } + exported_apis = { 1, "assert", "barrel", "case", "elems", "eval", + "getmetamethod", "ielems", "ipairs", "ireverse", "lambda", + "memoize", "monkey_patch", "pairs", "require", "ripairs", + "tostring", "version" } M = require (this_module) @@ -116,6 +117,7 @@ specify std: eval = M.eval, filter = M.functional.filter, fold = M.functional.fold, + getmetamethod = M.getmetamethod, id = M.functional.id, ielems = M.ielems, ileaves = M.tree.ileaves, @@ -128,7 +130,7 @@ specify std: map = M.functional.map, math = t.math, memoize = M.memoize, - metamethod = M.table.metamethod, + metamethod = M.getmetamethod, nodes = M.tree.nodes, op = M.functional.op, pack = M.table.pack, @@ -240,6 +242,49 @@ specify std: expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) +- describe getmetamethod: + - before: + fname = "getmetamethod" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "object or table")) + expect (f ({})).to_error (msg (2, "string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "object or table", "boolean")) + expect (f ({}, false)).to_error (msg (2, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, "foo", false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + + - context with a table: + - before: + method = function () end + t = setmetatable ({}, { _type = "table", _method = method }) + - it returns nil for missing metamethods: + expect (f (t, "not a metamethod on t")).to_be (nil) + - it returns nil for non-function metatable entries: + expect (f (t, "_type")).to_be (nil) + - it returns a method from the metatable: + expect (f (t, "_method")).to_be (method) + + - context with an object: + - before: + Object = require "std.object" + objmethod = function () end + obj = Object { + _type = "DerivedObject", + _method = objmethod, + } + - it returns nil for missing metamethods: + expect (f (obj, "not a metamethod on obj")).to_be (nil) + - it returns nil for non-function metatable entries: + expect (f (obj, "_type")).to_be (nil) + - it returns a method from the metatable: + expect (f (obj, "_method")).to_be (objmethod) + + - describe ielems: - before: fname = "ielems" diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index e08f1a7..5e43805 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -346,23 +346,25 @@ specify std.table: - describe metamethod: - before: - f = M.metamethod + fname = "metamethod" + f = M[fname] + Object = require "std.object" objmethod = function () end obj = Object { _type = "DerivedObject", _method = objmethod, } - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.metamethod' (object or table expected, got no value)" - expect (f (obj)). - to_error "bad argument #2 to 'std.table.metamethod' (string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.metamethod' (object or table expected, got boolean)" - expect (f (obj, false)). - to_error "bad argument #2 to 'std.table.metamethod' (string expected, got boolean)" + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {{}, subject}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain (fname .. " is deprecated") + end + _, err = capture (f, {{}, subject}) + expect (err).to_be (nil) + - it returns nil for missing metamethods: expect (f (obj, "not a method on obj")).to_be (nil) - it returns nil for non-function metatable entries: From 548e2fd9ef8e80b9a33e447effd92f000581f123 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Jul 2014 16:03:18 +0100 Subject: [PATCH 307/703] doc: improve LDocs for std.lua. * lib/std.lua.in: Tidy up and normalize LDocs. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 120 ++++++++++++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 524316f..8371c0d 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -1,23 +1,23 @@ --[[-- - Submodule lazy loader. + Lua Standard Libraries. - After requiring this module, simply referencing symbols in the submodule - hierarchy will load the necessary modules on demand. + This module contains a selection of improved Lua core functions, among + others. - Clients of older releases might be surprised by this new-found hygiene, - expecting the various changes that used to be automatically installed as - global symbols, or monkey patched into the core module tables and - metatables. Sometimes, it's still convenient to do that... when using - stdlib from the REPL, or in a prototype where you want to throw caution - to the wind and compatibility with other modules be damned, for example. - In that case, you can give stdlib permission to scribble all over your - namespaces with the various `monkey_patch` calls in the library. + Also, after requiring this module, simply referencing symbols in the + submodule hierarchy will load the necessary modules on demand. + + By default there are no changes to any global symbols, or monkey + patching of core module tables and metatables. However, sometimes it's + still convenient to do that: For example, when using stdlib from the + REPL, or in a prototype where you want to throw caution to the wind and + compatibility with other modules be damned. In that case, you can give + `stdlib` permission to scribble all over your namespaces by using the + various `monkey_patch` calls in the library. @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 Add tests for each function immediately after the function; - this also helps to check module dependencies. @todo pre-compile. @module std ]] @@ -31,15 +31,15 @@ local export = base.export local M = { "std" } ---- Extend to allow formatted arguments. +--- 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 --- assert (expected ~= nil, "100% unexpected!") --- assert (expected ~= nil, "%s unexpected!", expected) +-- std.assert (expected ~= nil, "100% unexpected!") +-- std.assert (expected ~= nil, "%s unexpected!", expected) export (M, "assert (any?, string?, any?*)", base.assert) @@ -52,7 +52,7 @@ export (M, "assert (any?, string?, any?*)", base.assert) -- @tparam table branches map possible matches to functions -- @return the return value from function with a matching key, or nil. -- @usage --- return case (type (object), { +-- return std.case (type (object), { -- table = function () return something end, -- string = function () return something else end, -- function (s) error ("unhandled type: "..s) end, @@ -61,7 +61,7 @@ export (M, "case (any?, #table)", base.case) --- An iterator over all elements of a sequence. --- If there is a `__pairs` metamethod, use that to iterate. +-- If *t* has a `__pairs` metamethod, use that to iterate. -- @function elems -- @tparam table t a table -- @treturn function iterator function @@ -70,63 +70,65 @@ export (M, "case (any?, #table)", base.case) -- @see ielems -- @see pairs -- @usage --- for v in elems {a = 1, b = 2, c = 5} do process (v) end +-- for value in std.elems {a = 1, b = 2, c = 5} do process (value) end export (M, "elems (table)", base.elems) ---- Evaluate a string. +--- Evaluate a string as Lua code. -- @function eval -- @string s string of Lua code -- @return result of evaluating `s` --- @usage eval "math.pow (2, 10)" +-- @usage std.eval "math.pow (2, 10)" export (M, "eval (string)", base.eval) --- An iterator over the integer keyed elements of a sequence. --- If there is an `__ipairs` metamethod, use that to iterate. +-- If *t* has an `__ipairs` metamethod, use that to iterate. -- @function ielems -- @tparam table t a table -- @treturn function iterator function --- @treturn list *l*, the list being iterated over +-- @treturn table *t*, the table being iterated over -- @treturn int *index*, the previous iteration index -- @see elems -- @see ipairs -- @usage --- for v in ielems {"a", "b", "c"} do process (v) end +-- for v in std.ielems {"a", "b", "c"} do process (v) end export (M, "ielems (table)", base.ielems) ---- An implementation of core ipairs that respects __ipairs even in Lua 5.1. +--- Enhance core `ipairs` to respect `__ipairs` even in Lua 5.1. -- @function ipairs -- @tparam table t a table -- @treturn function iterator function --- @treturn list *l*, the list being iterated over +-- @treturn table *t*, the table being iterated over -- @treturn int *index*, the previous iteration index -- @see ielems -- @see pairs -- @usage --- for i, v in ipairs {"a", "b", "c"} do process (v) end +-- for i, v in std.ipairs {"a", "b", "c"} do process (v) end export (M, "ipairs (table)", base.ipairs) ---- A new reversed list. +--- Return a new table with element order reversed. +-- If *t* has an `__ipairs` metamethod, use that to iterate. -- @function ireverse -- @tparam table t a table --- @treturn list a new list +-- @treturn table a new table with integer keyed elements in reverse +-- order with respect to *t* -- @see ielems -- @see ipairs -- @usage --- rielems = std.functional.compose (ireverse, ielems) +-- local rielems = std.functional.compose (std.ireverse, std.ielems) -- for e in rielems (l) do process (e) end export (M, "ireverse (table)", base.ireverse) ---- Return named metamethod, if any, or nil. +--- Return named metamethod, if any, otherwis `nil`. -- @function getmetamethod -- @tparam table t table to get metamethod of -- @string n name of metamethod to get -- @treturn function|nil metamethod function, or `nil` if no metamethod --- @usage lookup = getmetamethod (require "std.object", "__index") +-- @usage lookup = std.getmetamethod (require "std.object", "__index") export (M, "getmetamethod (object|table, string)", base.getmetamethod) @@ -141,14 +143,17 @@ export (M, "getmetamethod (object|table, string)", base.getmetamethod) -- The second form (starting with `=`) automatically assigns the first -- nine arguments to parameters `_1` through `_9` for use within the -- expression body. +-- +-- The results are memoized, so recompiling an previously compiled +-- lambda string is extremely fast. -- @function lambda -- @string s a lambda string -- @treturn table compiled lambda string, can be called like a function -- @usage -- -- The following are all equivalent: --- lambda "<" --- lambda "= _1 < _2" --- lambda "|a,b| a {1=baz,foo=bar} +-- -- {1=baz,foo=bar} +-- print (std.tostring {foo="bar","baz"}) export (M, "tostring (any?)", base.tostring) --- Overwrite core methods and metamethods with `std` enhanced versions. -- --- Loads all `std` submodules with a `monkey_patch` method, and runs --- them. And also, all `std` module functions except `std.barrel` and --- `std.monkey_patch` are also copied to the given namespace. +-- Write all functions from this module, except `std.barrel` and +-- `std.monkey_patch`, into the given namespace. -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table @@ -234,19 +241,15 @@ local monkey_patch = export (M, "monkey_patch (table?)", function (namespace) end end - require "std.io".monkey_patch (namespace) - require "std.math".monkey_patch (namespace) - require "std.string".monkey_patch (namespace) - require "std.table".monkey_patch (namespace) - return M end) --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). -- --- Scribble all over the given namespace, and apply all available --- `monkey_patch` functions. +-- Apply **all** `monkey_patch` functions. Additionally, for backwards +-- compatibility only, write a selection of sub-module functions into +-- the given namespace. -- @function barrel -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table module table @@ -256,6 +259,7 @@ export (M, "barrel (table?)", function (namespace) -- Older releases installed the following into _G by default. namespace.metamethod = M.getmetamethod + namespace.require_version = M.require for v in base.ielems { "functional.bind", "functional.collect", "functional.compose", "functional.curry", "functional.filter", "functional.fold", @@ -263,8 +267,7 @@ export (M, "barrel (table?)", function (namespace) "io.die", "io.warn", - "string.pickle", "string.prettytostring", - "string.render", "string.require_version", "string.tostring", + "string.pickle", "string.prettytostring", "string.render", "table.pack", "table.totable", @@ -274,6 +277,10 @@ export (M, "barrel (table?)", function (namespace) namespace[method] = M[module][method] end + require "std.io".monkey_patch (namespace) + require "std.math".monkey_patch (namespace) + require "std.string".monkey_patch (namespace) + require "std.table".monkey_patch (namespace) return monkey_patch (namespace) end) @@ -281,9 +288,9 @@ end) --- Module table. -- --- Lazy load 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. +-- In addition to the functions documented on this page, and a `version` +-- field, references to other submodule functions will be loaded on +-- demand. -- @table std -- @field version release version string M.version = "General Lua libraries / @VERSION@" @@ -298,7 +305,8 @@ return setmetatable (M, { -- to access a submodule, and then load it on demand. -- @function __index -- @string name submodule name - -- @return the submodule that was loaded to satisfy the missing `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 prototype = std.object.prototype From 1981539290dfde10b6e49e25db9947890e821fd0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 25 Jul 2014 16:46:31 +0100 Subject: [PATCH 308/703] spec: modernize and normalize std specs. * specs/std_spec.yaml: Reduce redundancy, and update to modern style with fully argchecked apis. * lib/std.lua.in (barrel): Scribble deprecated functions into global namespace. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 5 +- specs/std_spec.yaml | 160 +++++++++++++++++--------------------------- 2 files changed, 63 insertions(+), 102 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 8371c0d..4e5be99 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -258,8 +258,6 @@ export (M, "barrel (table?)", function (namespace) namespace = namespace or _G -- Older releases installed the following into _G by default. - namespace.metamethod = M.getmetamethod - namespace.require_version = M.require for v in base.ielems { "functional.bind", "functional.collect", "functional.compose", "functional.curry", "functional.filter", "functional.fold", @@ -268,8 +266,9 @@ export (M, "barrel (table?)", function (namespace) "io.die", "io.warn", "string.pickle", "string.prettytostring", "string.render", + "string.require_version", - "table.pack", "table.totable", + "table.metamethod", "table.pack", "table.totable", "tree.ileaves", "tree.inodes", "tree.leaves", "tree.nodes", } do diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index ddd3bb6..eb18ab7 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -1,4 +1,4 @@ -before: +before: | this_module = "std" global_table = "_G" @@ -7,10 +7,21 @@ before: "memoize", "monkey_patch", "pairs", "require", "ripairs", "tostring", "version" } + -- Tables with iterator metamethods used by various examples. + function str_iter (t) + return function (x, n) + if n < #x[1] then + return n+1, string.sub (x[1], n+1, n+1) + end + end, t, 0 + end + __pairs = setmetatable ({ "a string" }, { __pairs = str_iter }) + __ipairs = setmetatable ({ "a string" }, { __ipairs = str_iter }) + M = require (this_module) specify std: -- describe require: +- context when required: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). to_equal {} @@ -19,7 +30,7 @@ specify std: for k in pairs (M) do t[#t + 1] = k end expect (t).to_contain.a_permutation_of (exported_apis) -- describe lazy loading: +- context when lazy loading: - it has no submodules on initial load: for _, v in pairs (M) do expect (type (v)).not_to_be "table" @@ -65,7 +76,6 @@ specify std: - describe barrel: - before: - f = M.barrel io_mt = {} t = { io = { @@ -76,10 +86,18 @@ specify std: math = {}, table = {}, } + + fname = "barrel" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + f (t) - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.barrel' (table or nil expected, got boolean)" + + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it installs std.io monkey patches: expect (io_mt.readlines).to_be (M.io.readlines) expect (io_mt.writelines).to_be (M.io.writelines) @@ -130,7 +148,7 @@ specify std: map = M.functional.map, math = t.math, memoize = M.memoize, - metamethod = M.getmetamethod, + metamethod = M.table.metamethod, nodes = M.tree.nodes, op = M.functional.op, pack = M.table.pack, @@ -153,6 +171,7 @@ specify std: no = function () return false end default = function (s) return s end branches = { yes = yes, no = no, default } + fname = "case" msg = bind (badarg, {this_module, fname}) f = M[fname] @@ -204,18 +223,10 @@ specify std: end expect (t).to_contain.a_permutation_of {"foo", "baz", 42} - it respects __pairs metamethod: | - x = setmetatable ({ "a string" }, { - __pairs = function (x) - return function (x, n) - if n < #x[1] then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end - }) t = {} - for v in f (x) do t[#t + 1] = v end - expect (t).to_equal {"a", " ", "s", "t", "r", "i", "n", "g"} + 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 @@ -310,19 +321,9 @@ specify std: t[#t + 1] = e end expect (t).to_equal {"foo", 42} - - it respects __ipairs metamethod: | - len = require "std.operator"["#"] - x = setmetatable ({ "a string" }, { - __ipairs = function (x) - return function (x, n) - if n < len (x[1]) then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end, - }) + - it respects __ipairs metamethod: t = {} - for v in f (x) do t[#t + 1] = v end + for v in f (__ipairs) do t[#t + 1] = v end expect (t).to_equal {"a", " ", "s", "t", "r", "i", "n", "g"} - it works for an empty list: t = {} @@ -355,19 +356,9 @@ specify std: t[i] = v end expect (t).to_equal {"foo", 42} - - it respects __ipairs metamethod: | - len = require "std.operator"["#"] - x = setmetatable ({ "a string" }, { - __ipairs = function (x) - return function (x, n) - if n < len (x[1]) then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end, - }) + - it respects __ipairs metamethod: t = {} - for k, v in f (x) do t[k] = v end + for k, v in f (__ipairs) do t[k] = v end expect (t).to_equal {"a", " ", "s", "t", "r", "i", "n", "g"} - it works for an empty list: t = {} @@ -395,19 +386,8 @@ specify std: expect (f {1, 2, "five"}).to_equal {"five", 2, 1} - it ignores the dictionary part of a table: expect (f {1, 2, "five"; a = "b", c = "d"}).to_equal {"five", 2, 1} - - it respects __ipairs metamethod: | - len = require "std.operator"["#"] - x = setmetatable ({ "a string" }, { - __ipairs = function (x) - return function (x, n) - if n < len (x[1]) then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end, - }) - t = f (x) - expect (f (x)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} + - it respects __ipairs metamethod: + expect (f (__ipairs)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} - it works for an empty list: expect (f {}).to_equal {} @@ -455,6 +435,7 @@ specify std: fname = "memoize" msg = bind (badarg, {this_module, fname}) f = M[fname] + memfn = f (function (x) if x then return {x} else return nil, "bzzt" end end) @@ -494,7 +475,6 @@ specify std: - describe monkey_patch: - before: - f = M.monkey_patch io_mt = {} t = { io = { @@ -505,29 +485,24 @@ specify std: math = {}, table = {}, } + + fname = "monkey_patch" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + f (t) - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.monkey_patch' (table or nil expected, got boolean)" - - it installs std monkey patches: + + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + + - it installs std module functions: for _, v in ipairs (exported_apis) do if type (M[v]) == "function" and v ~= "barrel" and v ~= "monkey_patch" then expect (t[v]).to_be (M[v]) end end - - it installs std.io monkey patches: - expect (io_mt.readlines).to_be (M.io.readlines) - expect (io_mt.writelines).to_be (M.io.writelines) - - it installs std.math monkey patches: - expect (t.math.floor).to_be (M.math.floor) - - it installs std.string monkey patches: - # FIXME: string metatable monkey-patches leak out! - mt = getmetatable "" - expect (mt.__append).to_be (M.string.__append) - expect (mt.__concat).to_be (M.string.__concat) - expect (mt.__index).to_be (M.string.__index) - - it installs std.table monkey patches: - expect (t.table.sort).to_be (M.table.sort) - describe pairs: @@ -550,18 +525,10 @@ specify std: end expect (t).to_equal {"foo", bar = "baz", 42} - it respects __pairs metamethod: | - x = setmetatable ({ "a string" }, { - __pairs = function (x) - return function (x, n) - if n < #x[1] then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end - }) t = {} - for k, v in f (x) do t[k] = v end - expect (t).to_equal {"a", " ", "s", "t", "r", "i", "n", "g"} + 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 @@ -658,22 +625,12 @@ specify std: 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 __ipairs metamethod: | - len = require "std.operator"["#"] - x = setmetatable ({ "a string" }, { - __ipairs = function (x) - return function (x, n) - if n < len (x[1]) then - return n+1, string.sub (x[1], n+1, n+1) - end - end, x, 0 - end, - }) + - it respects __ipairs metamethod: t = {} - for i, v in f (x) do t[i] = v end + for i, v in f (__ipairs) do t[i] = v end expect (t).to_equal {"a", " ", "s", "t", "r", "i", "n", "g"} t = {} - for _, v in f (x) do t[#t + 1] = v end + for _, v in f (__ipairs) do t[#t + 1] = v end expect (t).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} - it works with the empty list: t = {} @@ -683,7 +640,12 @@ specify std: - describe tostring: - before: - f = M.tostring + fname = "tostring" + f = M[fname] + + - it diagnoses too many arguments: + expect (f (true, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) - it renders primitives exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) From 6088c168743a9ed551212f543d70533ac6e790f9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 29 Jul 2014 11:15:21 +0100 Subject: [PATCH 309/703] maint: reinstate LuaRocks init install bug workaround. Latest LuaRocks still has the bug where Lua source files ending in `init.lua` are installed to a subdirectory. Put back the workaround I removed prematurely. * lib/std/debug_init.lua: Move from here... * lib/std/debug_init/init.lua: ...to here. * local.mk (dist_luastd_DATA): Remove lib/std/debug_init.lua. (dist_luastddebug_DATA): Add lib/std/debug_init/init.lua. Signed-off-by: Gary V. Vaughan --- lib/std/{debug_init.lua => debug_init/init.lua} | 0 local.mk | 12 +++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) rename lib/std/{debug_init.lua => debug_init/init.lua} (100%) diff --git a/lib/std/debug_init.lua b/lib/std/debug_init/init.lua similarity index 100% rename from lib/std/debug_init.lua rename to lib/std/debug_init/init.lua diff --git a/local.mk b/local.mk index b790d2c..92338a3 100644 --- a/local.mk +++ b/local.mk @@ -65,7 +65,6 @@ dist_luastd_DATA = \ lib/std/base.lua \ lib/std/container.lua \ lib/std/debug.lua \ - lib/std/debug_init.lua \ lib/std/functional.lua \ lib/std/io.lua \ lib/std/list.lua \ @@ -83,6 +82,17 @@ dist_luastd_DATA = \ lib/std/vector.lua \ $(NOTHING_ELSE) +# For bugwards compatibility with LuaRocks 2.1, while ensuring that +# `require "std.debug_init"` continues to work, we have to install +# the former `$(luadir)/std/debug_init.lua` to `debug_init/init.lua`. +# When LuaRocks works again, move this file back to dist_luastd_DATA +# above and rename to debug_init.lua. + +luastddebugdir = $(luastddir)/debug_init + +dist_luastddebug_DATA = \ + lib/std/debug_init/init.lua \ + $(NOTHING_ELSE) # In order to avoid regenerating std.lua at configure time, which # causes the documentation to be rebuilt and hence requires users to From 86bc660ffef1fc2820c11879459edb690fab9b83 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 29 Jul 2014 11:43:39 +0100 Subject: [PATCH 310/703] std: `std.require` now matches last dot-delimited version number. * specs/std_spec.yaml (require): Specify behaviours when a version string contains more than one substring with dot-delimited digits. * lib/std/base.lua (module_version): Anchor the version matching pattern at the end of the string. * lib/std.lua.in (require): Improve LDocs accordingly. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 6 +++--- lib/std.lua.in | 8 ++++++-- lib/std/base.lua | 4 ++-- specs/std_spec.yaml | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index a56a583..4ddd4e7 100644 --- a/NEWS +++ b/NEWS @@ -138,11 +138,11 @@ Stdlib NEWS - User visible changes - `optparse.on` now works with `std.strict` enabled. - - `std.require` (nee `string.require_version`) now extracts the first + - `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 stdlib's "General Lua Libraries / 41" we now - correctly compare just the numeric part against specified version + 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 diff --git a/lib/std.lua.in b/lib/std.lua.in index 4e5be99..099d94d 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -192,13 +192,17 @@ export (M, "pairs (table)", base.pairs) --- 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 std = require ("std", "41") +-- `module._VERSION` (default: `"([%.%d]+)%D*$"`) +-- @usage +-- -- posix.version == "posix library for Lua 5.2 / 32" +-- posix = require ("posix", "29") export (M, "require (string, string?, string?, string?)", base.require) diff --git a/lib/std/base.lua b/lib/std/base.lua index 7972619..8994b44 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -125,11 +125,11 @@ end --- Extract a list of period delimited integer version components. -- @tparam table module returned from a `require` call -- @string pattern to capture version number from a string --- (default: `"%D*([%.%d]+)"`) +-- (default: `"([%.%d]+)%D*$"`) -- @treturn List a list of version components local function module_version (module, pattern) local version = module.version or module._VERSION - return version_to_list (version:match (pattern or "%D*([%.%d]+)")) + return version_to_list (version:match (pattern or "([%.%d]+)%D*$")) end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index eb18ab7..d719200 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -598,6 +598,23 @@ specify 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.2 / 41" + - after: + std.version = ver + - it diagnoses module too old: + expect (f ("std", "42")).to_error () + - it diagnoses module too new: + expect (f ("std", nil, "40")).to_error () + - 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: From c4f737fd191cfefdca62a7ad496601e0b00e848a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 31 Jul 2014 16:16:19 +0100 Subject: [PATCH 311/703] slingshot: sync with upstream for upload and moonscript support. * slingshot: Sync with upstream. * bootstrap.conf (slingshot_files): Delete removed ax_compare_version.m4. * README.md (Installation): Show moonscript rocks repo. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 9 ++++----- README.md | 2 +- bootstrap.conf | 1 - slingshot | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 30c24f3..4925c77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ env: - PACKAGE=stdlib - ROCKSPEC=$PACKAGE-git-1.rockspec - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - - LUAROCKS_BASE=luarocks-2.1.2 - LUAROCKS="$LUA $HOME/bin/luarocks" matrix: - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 LUA_SUFFIX=5.1 @@ -26,11 +25,11 @@ install: - sudo apt-get install liblua5.1-dev - sudo apt-get install lua5.2 - sudo apt-get install liblua5.2-dev - # Install a recent luarocks release locally for everything else. - - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz - - tar zxvpf $LUAROCKS_BASE.tar.gz + + # Install a luarocks beta locally for everything else. + - git clone --depth=1 https://github.com/keplerproject/luarocks.git # LuaRocks configure --with-lua argument is just a prefix! - - ( cd $LUAROCKS_BASE; + - ( cd luarocks; ./configure --prefix=$HOME --with-lua=/usr --lua-version=$LUA_SUFFIX --lua-suffix=$LUA_SUFFIX --with-lua-include=$LUA_INCDIR; diff --git a/README.md b/README.md index edab699..654d4a7 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Installation The simplest way to install stdlib is with [LuaRocks][]. To install the latest release (recommended): - luarocks install stdlib + luarocks --server=http://rocks.moonscript.org install stdlib To install current git master (for testing): diff --git a/bootstrap.conf b/bootstrap.conf index 5635e88..e20b124 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -53,7 +53,6 @@ slingshot_files=' build-aux/sanity.mk build-aux/specl.mk build-aux/update-copyright - m4/ax_compare_version.m4 m4/ax_lua.m4 m4/slingshot.m4 travis.yml.in diff --git a/slingshot b/slingshot index 39016bb..2218547 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 39016bbdb0c9a8730b522f5acb85aa4248a69814 +Subproject commit 221854778ca93195232d48e5b6bc659242bd65a6 From a91950537001b8d482d7d03f15b8b6c82ff9334d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 2 Aug 2014 13:25:23 +0100 Subject: [PATCH 312/703] specs: don't rely on `_G.arg[-1]:match "/lua[0-9.]*$"` When `specs/optparse_spec.yaml` came over from Specl, I forgot to upgrade the direct `hell.spawn` invocations to nicely abstracted `spec_helper.lua:luaproc` calls. * specs/optparse_spec.yaml (parser): Replace hell.spawn calls with luaproc calls, so that Lua interpreter is set correctly. Signed-off-by: Gary V. Vaughan --- specs/optparse_spec.yaml | 67 +++++++++++++++------------------------- 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index feae149..46bddc5 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -62,38 +62,28 @@ specify std.optparse: - describe parser: - before: | - f = os.tmpname () - - function parse (arglist) - local d = f:gsub ("/[^/]*$", "", 1) - local h = io.open (f, "w") - - h:write ([[ - package.path = "]] .. package.path .. [[" - local OptionParser = require 'std.optparse' - local help = [=[]] .. help .. [[]=] - help = help:match ("^[%s\n]*(.-)[%s\n]*$") - - local parser = OptionParser (help) - local arg, opts = parser:parse (_G.arg) - - o = {} - for k, v in pairs (opts) do - table.insert (o, k .. " = " .. tostring (v)) - end - if #o > 0 then - table.sort (o) - print ("opts = { " .. table.concat (o, ", ") .. " }") - end - if #arg > 0 then - print ("args = { " .. table.concat (arg, ", ") .. " }") - end - ]]) - h:close () - - return hell.spawn {arg[-1], f, unpack (arglist)} - end - - after: os.remove (f) + code = [[ + package.path = "]] .. package.path .. [[" + local OptionParser = require 'std.optparse' + local help = [=[]] .. help .. [[]=] + help = help:match ("^[%s\n]*(.-)[%s\n]*$") + + local parser = OptionParser (help) + local arg, opts = parser:parse (_G.arg) + + o = {} + for k, v in pairs (opts) do + table.insert (o, k .. " = " .. tostring (v)) + end + if #o > 0 then + table.sort (o) + print ("opts = { " .. table.concat (o, ", ") .. " }") + end + if #arg > 0 then + print ("args = { " .. table.concat (arg, ", ") .. " }") + end + ]] + parse = bind (luaproc, {code}) - it responds to --version with version text: expect (parse {"--version"}). @@ -286,13 +276,8 @@ specify std.optparse: - describe parser:on: - before: | - f = os.tmpname () - function parseargs (onargstr, arglist) - local d = f:gsub ("/[^/]*$", "", 1) - local h = io.open (f, "w") - - h:write ([[ + code = [[ package.path = "]] .. package.path .. [[" local OptionParser = require 'std.optparse' local help = [=[]] .. help .. [[]=] @@ -315,12 +300,10 @@ specify std.optparse: if #arg > 0 then print ("args = { " .. table.concat (arg, ", ") .. " }") end - ]]) - h:close () + ]] - return hell.spawn {arg[-1], f, unpack (arglist)} + return luaproc (code, arglist) end - - after: os.remove (f) - it recognises short options: expect (parseargs ([["x"]], {"-x"})). From 812a2355be28facc7fb2ed438a5648822596926f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 2 Aug 2014 20:03:17 +0100 Subject: [PATCH 313/703] maint: reinstate specl package.path workaround for luarocks bug. Now that we're supporting LuaRocks' `init.lua suffixed filenames get installed to an init installation directory` bug again, we must adjust Specl's in-tree package.path to accommodate. * specs/spec_helper.lua.in (package.path): Add "lib/?/init.lua". * local.mk (std_path): Likewise Signed-off-by: Gary V. Vaughan --- local.mk | 2 +- specs/spec_helper.lua.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/local.mk b/local.mk index 92338a3..8186560 100644 --- a/local.mk +++ b/local.mk @@ -21,7 +21,7 @@ ## Environment. ## ## ------------ ## -std_path = $(abs_srcdir)/lib/?.lua +std_path = $(abs_srcdir)/lib/?.lua;$(abs_srcdir)/lib/?/init.lua LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index 5cf495f..cfc761b 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -2,7 +2,7 @@ local hell = require "specl.shell" local inprocess = require "specl.inprocess" local std = require "specl.std" -package.path = std.package.normalize ("lib/?.lua", package.path) +package.path = std.package.normalize ("lib/?.lua", "lib/?/init.lua", package.path) -- Substitute configured LUA so that hell.spawn doesn't pick up From de013a608c650acebb67c9da074dc7d97a4c2e50 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 2 Aug 2014 21:26:48 +0100 Subject: [PATCH 314/703] list: specify list.transpose, and fix revealed bugs. * specs/list_spec.yaml (transpose): Specify behaviour of transpose method. * lib/std/list.lua (transpose): Handle empty list argument. Call list.map with correctly ordered arguments. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/list.lua | 12 +++++++----- specs/list_spec.yaml | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 4ddd4e7..29eb69e 100644 --- a/NEWS +++ b/NEWS @@ -136,6 +136,9 @@ Stdlib NEWS - User visible changes - `debug.trace` works with Lua 5.2.x again. + - `list.transpose` works again, and handles empty lists without + raising an error. + - `optparse.on` now works with `std.strict` enabled. - `std.require` (nee `string.require_version`) now extracts the last diff --git a/lib/std/list.lua b/lib/std/list.lua index 5a869e6..45fd5b9 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -483,11 +483,13 @@ local function transpose (ls) end end - local rs, len = List {}, #ls - for i = 1, math.max (unpack (map (ls, function (l) return #l end))) do - rs[i] = List {} - for j = 1, len do - rs[i][j] = ls[j][i] + local rs, len, dims = List {}, #ls, map (base.lambda "#", ls) + if #dims > 0 then + for i = 1, math.max (unpack (dims)) do + rs[i] = List {} + for j = 1, len do + rs[i][j] = ls[j][i] + end end end return rs diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 2107035..fb91001 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -769,6 +769,7 @@ specify std.list: - describe transpose: - before: + l = List {List {1, 2}, List {3, 4}, List {5, 6}} f = list.transpose - it diagnoses missing arguments: | @@ -779,6 +780,19 @@ specify std.list: to_error "bad argument #1 to 'std.list.transpose' (table or List expected, got boolean)" - context when called as a list object method: + - it returns a list object: | + expect (prototype (l:transpose ())).to_be "List" + - it returns the result in a new list object: | + expect (l:transpose ()):not_to_be (l) + - it does not perturb the argument list: | + m = l:transpose () + expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) + - it performs a matrix transpose operation: | + expect (l:transpose ()). + to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) + - it works for an empty list: | + l = List {} + expect (l:transpose ()).to_equal (List {}) - describe zip_with: From 78a50696c07f67cebd6f96ae00a6af343a4f48a8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 2 Aug 2014 21:47:38 +0100 Subject: [PATCH 315/703] list: specify list.zip_with, and fix revealed bugs. * specs/list_spec.yaml (zip_with): Specify behaviour of zip_with method. * lib/std/list.lua (zip_with): Call list.map with correctly * ordered arguments. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 2 ++ lib/std/list.lua | 4 ++-- specs/list_spec.yaml | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 29eb69e..5b9ca09 100644 --- a/NEWS +++ b/NEWS @@ -139,6 +139,8 @@ Stdlib NEWS - User visible changes - `list.transpose` works again, and handles empty lists without raising an error. + - `list.zip_with` no longer raises an argument error on every call. + - `optparse.on` now works with `std.strict` enabled. - `std.require` (nee `string.require_version`) now extracts the last diff --git a/lib/std/list.lua b/lib/std/list.lua index 45fd5b9..36a57e7 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -516,7 +516,7 @@ local function zip_with (ls, fn) end end - return map_with (transpose (ls), fn) + return map_with (fn, transpose (ls)) end @@ -714,7 +714,7 @@ List = Object { relems = relems, reverse = reverse, transpose = transpose, - zip_with = function (self, f) return zip_with (f, self) end, + zip_with = zip_with, }, diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index fb91001..a0491d5 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -797,7 +797,7 @@ specify std.list: - describe zip_with: - before: - l = List {} + l = List {List {1, 2}, List {3, 4}, List {5}} f = list.zip_with - it diagnoses missing arguments: | @@ -816,3 +816,17 @@ specify std.list: to_error "bad argument #2 to 'std.list.zip_with' (function expected, got boolean)" - context when called as a list object method: + - before: + fn = function (...) return tonumber (table.concat {...}) end + - it returns a list object: | + expect (prototype (l:zip_with (fn))).to_be "List" + - it returns the result in a new list object: | + expect (l:zip_with (fn)):not_to_be (l) + - it does not perturb the argument list: | + m = l:zip_with (fn) + expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) + - it combines column entries with a function: | + expect (l:zip_with (fn)).to_equal (List {135, 24}) + - it works for an empty list: | + l = List {} + expect (l:zip_with (fn)).to_equal (List {}) From de444e56824e4f1fbe175ef0789fed29502423ad Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 2 Aug 2014 22:22:21 +0100 Subject: [PATCH 316/703] list: specify shape method behaviours. * specs/list_spec.yaml (shape): Specify method behaviours. Signed-off-by: Gary V. Vaughan --- specs/list_spec.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index a0491d5..5a10dff 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -691,6 +691,7 @@ specify std.list: - describe shape: - before: + l = List {1, 2, 3, 4, 5, 6} f = list.shape - it diagnoses missing arguments: | @@ -705,6 +706,26 @@ specify std.list: to_error "bad argument #2 to 'std.list.shape' (List expected, got boolean)" - context when called as a list object method: + - it returns a list object: | + expect (prototype (l:shape {2, 3})).to_be "List" + - it returns the result in a new list object: | + expect (l:shape {2, 3}):not_to_be (l) + - it does not perturb the argument list: | + m = l:shape {2, 3} + expect (l).to_equal (List {1, 2, 3, 4, 5, 6}) + - it reshapes a list according to given dimensions: | + expect (l:shape {2, 3}). + to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) + expect (l:shape {3, 2}). + to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) + - it treats 0-valued dimensions as an indefinite number: + expect (l:shape {2, 0}). + to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) + expect (l:shape {0, 2}). + to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) + - it works for an empty list: | + l = List {} + expect (l:shape {0}).to_equal (List {}) - describe sub: From 591b14d3f5a60fe781af54bb3e5f2516885cfdf1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 2 Aug 2014 23:10:43 +0100 Subject: [PATCH 317/703] string: reference totable correctly in string.pickle. Closes #79. * lib/std/string.lua (totable): Set to table.totable. Reported by Simon Cozens. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/string.lua b/lib/std/string.lua index dec381b..5d1818d 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -18,6 +18,7 @@ local StrBuf = strbuf {} local export, getmetamethod, split = base.export, base.getmetamethod, base.split +local totable = table.totable local _format = string.format local _tostring = base.tostring From db6470d9d102dfaa68752d13c3fa057306a611ae Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 2 Aug 2014 23:18:07 +0100 Subject: [PATCH 318/703] refactor: simplify deprecation management. Closes #73. * specs/base_spec.yaml (DEPRECATED): Specify behaviours of an improved internal deprecation API. * specs/list_spec.yaml (elems, index_key, index_value, relems) (reverse): Specify default deprecation behaviours. * specs/string_spec.yaml (assert, require_version, tostring): Likewise. * specs/table_spec.yaml (clone_rename, metamethod, ripairs): Likewise. * lib/std/debug.lua (_DEBUG): Document new compat field. * lib/std/base.lua (_DEBUG): Set from debug_init.lua. (deprecate): Rename from this... (DEPRECATED): ...to this, and improve API. * lib/std/functional.lua (bind): Use it to mark the old bind API as deprecated since release 39. * lib/std/list.lua (elems, index_key, index_value, relems) (reverse): Collect in a new section and deprecate with the improved API. * lib/std/string.lua (assert, require_version, tostring): Likewise. * lib/std/table.lua (clone_rename, metamethod, ripairs): Likewise. * build-aux/sanity-cfg.mk (exclude_file_name_regexp): Don't choke on error specs in specs/list_spec.yaml * NEWS (Deprecations): Collect deprecated API NEWS for this release. Signed-off-by: Gary V. Vaughan --- NEWS | 55 +++-- build-aux/sanity-cfg.mk | 2 +- lib/std/base.lua | 44 ++-- lib/std/debug.lua | 9 +- lib/std/functional.lua | 4 +- lib/std/list.lua | 162 ++++++------- lib/std/string.lua | 38 +-- lib/std/table.lua | 56 ++--- specs/base_spec.yaml | 89 +++++-- specs/list_spec.yaml | 514 ++++++++++++++++++++++++++++------------ specs/string_spec.yaml | 10 +- specs/table_spec.yaml | 19 +- 12 files changed, 623 insertions(+), 379 deletions(-) diff --git a/NEWS b/NEWS index 5b9ca09..2850d89 100644 --- a/NEWS +++ b/NEWS @@ -63,24 +63,7 @@ Stdlib NEWS - User visible changes - New `std.ripairs` function for returning index & value pairs in reverse order, while respecting `__ipairs`, even on Lua 5.1. -** Incompatible changes: - - - `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. - - 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. - - - `io.catdir` now raises an error when called with no arguments, for - consistency with `io.catfile`. +** Deprecations: - `list.index_key` and `list.index_value` have been deprecated. These functions are not general enough to belong in lua-stdlib, because @@ -90,17 +73,11 @@ Stdlib NEWS - User visible changes use. After that, in some future release, they will be removed entirely. - - `list.reverse` has been deprecated in favour of the more general - and more accurately named `std.ireverse`. - - `list.relems` has been deprecated, in favour of the more idiomatic `functional.compose (std.ireverse, std.ielems)`. - - `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. + - `list.reverse` has been deprecated in favour of the more general + and more accurately named `std.ireverse`. - `string.assert` has been moved to `std.assert`, the old name now gives a deprecation warning on first use. @@ -121,6 +98,32 @@ Stdlib NEWS - User visible changes gives a deprecation warning on first use, and will be removed entirely in some future release. + +** Incompatible changes: + + - `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. + + 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. + + - `io.catdir` now raises an error when called with no arguments, for + consistency with `io.catfile`. + + - `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. + - 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. diff --git a/build-aux/sanity-cfg.mk b/build-aux/sanity-cfg.mk index 0a62393..069aad4 100644 --- a/build-aux/sanity-cfg.mk +++ b/build-aux/sanity-cfg.mk @@ -1,3 +1,3 @@ -exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua|specs/(base|debug|lua)_spec.yaml$$ +exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua|specs/(base|debug|list)_spec.yaml$$ EXTRA_DIST += build-aux/sanity-cfg.mk diff --git a/lib/std/base.lua b/lib/std/base.lua index 8994b44..79c68b2 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -355,14 +355,15 @@ end - - --[[ ================== ]]-- --[[ Argument Checking. ]]-- --[[ ================== ]]-- -local _ARGCHECK = require "std.debug_init"._ARGCHECK +local debug = require "std.debug_init" + +local _ARGCHECK = debug._ARGCHECK +local _DEBUG = debug._DEBUG local argcheck, argerror, argscheck -- forward declarations @@ -797,32 +798,35 @@ local function export (M, decl, fn, ...) end ---- Write a deprecation warning to stderr on first call. +--- Write a deprecation warning to stderr. +-- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; +-- if `_DEBUG.compat` is false, warn every time *fn* is called; +-- otherwise don't write any warnings, and run *fn* normally. -- @func fn deprecated function --- @string[opt] name function name for automatic warning message. --- @string[opt] warnmsg full specified warning message (overrides *name*) +-- @string version first deprecation release version +-- @string name function name for automatic warning message +-- @string[opt] extramsg additional warning text -- @return a function to show the warning on first call, and hand off to *fn* -- @usage funcname = deprecate (function (...) ... end, "funcname") -local function deprecate (fn, name, warnmsg) - argscheck ("std.base.deprecate", {"function", "string?", "string?"}, - {fn, name, warnmsg}) +local M = { "std.base" } - if not (name or warnmsg) then - error ("missing argument to 'std.base.deprecate' (2 or 3 arguments expected)", 2) - end +export (M, "DEPRECATED (string, string, [string], func)", +function (version, name, extramsg, fn) + if fn == nil then fn, extramsg = extramsg, nil end + extramsg = extramsg or "and will be removed entirely in a future release" + local warnmsg = string.format ("%s was deprecated in release %s, %s.", + name, version, extramsg) - warnmsg = warnmsg or (name .. " is deprecated, and will go away in a future release.") - local warnp = true + local compat = type (_DEBUG) == "table" and _DEBUG.compat or _DEBUG == false return function (...) - if warnp then + if not compat then local _, where = pcall (function () error ("", 4) end) - io.stderr:write ((string.gsub (where, "(^w%*%.%w*%:%d+)", "%1"))) - io.stderr:write (warnmsg .. "\n") - warnp = false + io.stderr:write (where .. warnmsg .. "\n") + if _DEBUG == true or (type (_DEBUG) == "table" and _DEBUG.compat == nil) then compat = true end end return fn (...) end -end +end) @@ -866,7 +870,7 @@ return { argscheck = argscheck, -- Maintenance -- - deprecate = deprecate, + DEPRECATED = M.DEPRECATED, export = export, toomanyarg_fmt = toomanyarg_fmt, } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 7344aaf..0b6447d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -68,9 +68,12 @@ local tabify = functional.compose ( -- (equivalent to {level = 1}), or as documented below. -- @class table -- @name _DEBUG --- @field[opt=true] argcheck honor argcheck and argscheck calls --- @field[opt=false] call do call trace debugging --- @field[opt=1] level debugging level +-- @tfield[opt=true] boolean argcheck honor argcheck and argscheck calls +-- @tfield[opt=false] boolean call do call trace debugging +-- @field[opt=nil] compat if `false`, always complain whenever a deprecated +-- api is called; if `nil` complain on first use of each deprecated api; +-- any other value disables deprecation warnings altogether +-- @tfield[opt=1] int level debugging level -- @usage _DEBUG = { argcheck = false, level = 9 } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index e8990cf..d7b43f6 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -26,8 +26,10 @@ local M = { "std.functional" } -- > =cube (2) -- 8 local bind = export (M, "bind (func, any?*)", function (f, ...) - local fix = {...} -- backwards compatibility with old API; DEPRECATED: remove in first release after 2015-04-21 + local fix = {...} if type (fix[1]) == "table" and fix[2] == nil then + base.DEPRECATED ("39", "`functional.bind` multi-argument", + "use a table of arguments as the second parameter instead", nop) fix = fix[1] end diff --git a/lib/std/list.lua b/lib/std/list.lua index 36a57e7..fffb3e7 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -91,19 +91,6 @@ local function compare (l, m) end --- DEPRECATED: Remove in first release following 2015-07-11. --- An iterator over the elements of a list. --- @static --- @function elems --- @tparam List l a list --- @treturn function iterator function which returns successive elements --- of `l` --- @treturn List `l` --- @return `true` -local elems = base.deprecate (ielems, nil, - "list.elems is deprecated, use lua.ielems instead.") - - --- Concatenate arguments into a list. -- @tparam List l a list -- @param ... tuple of lists @@ -219,19 +206,6 @@ local function foldl (fn, e, l) end --- DEPRECATED: Remove in first release following 2015-07-11 --- An iterator over the elements of a list, in reverse. --- @tparam List l a list --- @treturn function iterator function which returns precessive elements --- of the `l` --- @treturn List `l` --- @return `true` -local relems = base.deprecate (function (l) - return ielems (ireverse (l)) - end, nil, - "list.relems is deprecated, use lua.ipairs with lua.ireverse instead.") - - --- Fold a binary function through a list right associatively. -- @func fn binary function -- @param e element to place in right-most position @@ -245,46 +219,6 @@ local function foldr (fn, e, l) end --- DEPRECATED: Remove in first release following 2015-06-07. ---- Make an index of a list of tables on a given field --- @param f field --- @tparam List l list of tables `{t1, ..., tn}` --- @treturn table index `{t1[f]=1, ..., tn[f]=n}` -local index_key = base.deprecate (function (f, l) - argcheck ("std.list.index_key", 2, "List", l) - - local r = {} - for i, v in ipairs (l) do - local k = v[f] - if k then - r[k] = i - end - end - return r -end, nil, - "list.index_key is deprecated, use list.filter and table.invert instead.") - - --- DEPRECATED: Remove in first release following 2015-06-07. --- Copy a list of tables, indexed on a given field --- @param f field whose value should be used as index --- @tparam List l list of tables `{i1=t1, ..., in=tn}` --- @treturn table index `{t1[f]=t1, ..., tn[f]=tn}` -local index_value = base.deprecate (function (f, l) - argcheck ("std.list.index_value", 2, "List", l) - - local r = {} - for i, v in ipairs (l) do - local k = v[f] - if k then - r[k] = v - end - end - return r -end, nil, - "list.index_value is deprecated, use list.filter and table.invert instead.") - - --- Map a function over a list. -- @func fn map function -- @tparam List l a list @@ -357,16 +291,6 @@ local function rep (l, n) end --- DEPRECATED: Remove in first release following 2015-07-11 --- Reverse a list. --- @tparam List l a list --- @treturn List new list containing `{l[#l], ..., l[1]}` -local reverse = base.deprecate (function (l) - return List (ireverse (l)) - end, nil, - "list.reverse is deprecated, use lua.ireverse instead.") - - --- Shape a list according to a list of dimensions. -- -- Dimensions are given outermost first and items from the original @@ -543,12 +467,61 @@ local _functions = { zip_with = zip_with, } --- Deprecated and undocumented. -_functions.elems = elems -_functions.index_key = index_key -_functions.index_value = index_value -_functions.relems = relems -_functions.reverse = reverse + + +--[[ ============= ]]-- +--[[ Deprecations. ]]-- +--[[ ============= ]]-- + + +local DEPRECATED = base.DEPRECATED + + +_functions.elems = DEPRECATED ("41", "'list.elems'", + "use 'std.ielems' instead", base.ielems) + + +local function relems (l) return base.ielems (base.ireverse (l)) end + +_functions.relems = DEPRECATED ("41", "'list.relems'", + "use 'std.ielems' with 'std.ireverse' instead", relems) + + +local function index_key (f, l) + local r = {} + for i, v in ipairs (l) do + local k = v[f] + if k then + r[k] = i + end + end + return r +end + +_functions.index_key = DEPRECATED ("41", "'list.index_key'", + "use 'list.filter' with 'table.invert' instead", index_key) + + +local function index_value (f, l) + local r = {} + for i, v in ipairs (l) do + local k = v[f] + if k then + r[k] = v + end + end + return r +end + +_functions.index_value = DEPRECATED ("41", "'list.index_value'", + "use 'list.filter' with 'table.invert' instead", index_value) + + +local function reverse (l) return List (ireverse (l)) end + +_functions.reverse = DEPRECATED ("41", "'list.reverse'", + "use 'std.ireverse' instead", reverse) + List = Object { @@ -704,17 +677,20 @@ List = Object { -- @treturn List new list containing `{self[2], ..., self[#self]}` tail = tail, - -- For backwards compatibility with pre-Object era lists, but - -- undocumented so that new code doesn't get tangled up in it. - depair = depair, - elems = ielems, - index_key = function (self, f) return index_key (f, self) end, - index_value = function (self, f) return index_value (f, self) end, - map_with = function (self, f) return map_with (f, self) end, - relems = relems, - reverse = reverse, - transpose = transpose, - zip_with = zip_with, + ------ + depair = DEPRECATED ("38", "'list:depair'", depair), + map_with = DEPRECATED ("38", "'list:map_with'", + function (self, f) return map_with (f, self) end), + transpose = DEPRECATED ("38", "'list:transpose'", transpose), + zip_with = DEPRECATED ("38", "'list:zip_with'", zip_with), + + elems = DEPRECATED ("41", "'list:elems'", base.ielems), + index_key = DEPRECATED ("41", "'list:index_key'", + function (self, f) return index_key (f, self) end), + index_value = DEPRECATED ("41", "'list:index_value'", + function (self, f) return index_value (f, self) end), + relems = DEPRECATED ("41", "'list:relems'", relems), + reverse = DEPRECATED ("41", "'list:reverse'", reverse), }, diff --git a/lib/std/string.lua b/lib/std/string.lua index 5d1818d..c6723e5 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -89,11 +89,6 @@ end --[[ ================= ]]-- --- DEPRECATED: Remove in first release following 2015-07-30. -M.assert = base.deprecate (base.assert, nil, - "std.string.assert is deprecated, use std.assert instead") - - --- Extend to work better with one argument. -- If only one argument is passed, no formatting is attempted. -- @function format @@ -160,11 +155,6 @@ end) export (M, "split (string, string?)", split) --- DEPRECATED: Remove in first release following 2015-06-30. -M.require_version = base.deprecate (base.require, nil, - "std.string.require_version is deprecated, use std.require instead") - - --- Overwrite core methods and metamethods with `std` enhanced versions. -- -- Adds auto-stringification to `..` operator on core strings, and @@ -462,11 +452,6 @@ local render = export (M, -- function separator (_, _, _, fk) return fk and "," or "" end --- DEPRECATED: Remove in first release following 2015-07-30. -M.tostring = base.deprecate (base.tostring, nil, - "std.string.tostring is deprecated, use std.tostring instead") - - --- Pretty-print a table, or other object. -- @function prettytostring -- @param x object to convert to string @@ -560,6 +545,29 @@ function M.pickle (x) end + +--[[ ============= ]]-- +--[[ Deprecations. ]]-- +--[[ ============= ]]-- + + +local DEPRECATED = base.DEPRECATED + + +M.assert = DEPRECATED ("41", "'string.assert'", + "use 'std.assert' instead", base.assert) + + +M.require_version = DEPRECATED ("41", "'string.require_version'", + "use 'std.require' instead", base.require) + + +M.tostring = DEPRECATED ("41", "'string.tostring'", + "use 'std.tostring' instead", base.tostring) + + + + for k, v in pairs (string) do M[k] = M[k] or v end diff --git a/lib/std/table.lua b/lib/std/table.lua index 2e68e36..98cdb73 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -93,23 +93,6 @@ local clone = export (M, "clone (table, [table], boolean|:nometa?)", function (...) return merge_allfields ({}, ...) end) --- DEPRECATED: Remove in first release following 2015-04-15. --- Clone a table, renaming some keys. --- @function clone_rename --- @tparam table map table `{old_key=new_key, ...}` --- @tparam table t source table --- @treturn table copy of *t* -M.clone_rename = base.deprecate (function (map, t) - local r = clone (t) - for i, v in pairs (map) do - r[v] = t[i] - r[i] = nil - end - return r - end, nil, - "table.clone_rename is deprecated, use the new `map` argument to table.clone instead.") - - --- Make a partial clone of a table. -- -- Like `clone`, but does not copy any fields by default. @@ -196,11 +179,6 @@ export (M, "merge_select (table, table, [table], boolean|:nometa?)", merge_namedfields) --- DEPRECATED: Remove in first release following 2015-07-30. -M.metamethod = base.deprecate (getmetamethod, nil, - "table.metamethod is deprecated, use std.getmetamethod instead.") - - --- Make a table with a default value for unset keys. -- @function new -- @param[opt=nil] x default entry value @@ -223,11 +201,6 @@ function M.pack (...) end --- DEPRECATED: Remove in first release following 2015-07-11. -M.ripairs = base.deprecate (base.ripairs, nil, - "table.ripairs is deprecated, use std.ripairs instead.") - - --- Find the number of elements in a table. -- @function size -- @tparam table t any table @@ -306,6 +279,35 @@ export (M, "values (table)", function (t) end) + +--[[ ============= ]]-- +--[[ Deprecations. ]]-- +--[[ ============= ]]-- + + +local DEPRECATED = base.DEPRECATED + +M.clone_rename = DEPRECATED ("39", "'table.clone_rename'", + "use the new `map` argument to 'table.clone' instead", + function (map, t) + local r = clone (t) + for i, v in pairs (map) do + r[v] = t[i] + r[i] = nil + end + return r + end) + + +M.metamethod = DEPRECATED ("41", "'table.metamethod'", + "use 'std.getmetamethod' instead", base.getmetamethod) + + +M.ripairs = DEPRECATED ("41", "'table.ripairs'", + "use 'std.ripairs' instead", base.ripairs) + + + for k, v in pairs (table) do M[k] = M[k] or v end diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 7d3bdca..08efbec 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -6,58 +6,95 @@ before: nop = M.nop specify std.base: -- describe deprecate: +- describe DEPRECATED: - before: | function runscript (body, name, args) return luaproc ( - "require 'std.base'.deprecate (function (...)" .. + "require 'std.base'.DEPRECATED ('0', '" .. (name or "runscript") .. "', function (...)" .. " " .. body .. - " end, '" .. (name or "runscript") .. "') " .. + " end) " .. "('" .. table.concat (args or {}, "', '") .. "')" ) end - fname = "deprecate" - -- Don't depend on functional.bind for base specs! - msg = function (...) return badarg (this_module, fname, ...) end + fname = "DEPRECATED" + msg = bind (badarg, {this_module, fname}) f = M[fname] - - it diagnoses missing arguments: | - expect (f ()).to_error (msg (1, "function")) - expect (f (function () end)). - to_error "missing argument to 'std.base.deprecate' (2 or 3 arguments expected)" + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + expect (f "version").to_error (msg (2, "string")) + expect (f ("version", "name")).to_error (msg (3, "string or function")) + expect (f ("version", "name", "extramsg")).to_error (msg (4, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f ("version", false)).to_error (msg (2, "string", "boolean")) + expect (f ("version", "name", false)). + to_error (msg (3, "string or function", "boolean")) + expect (f ("version", "name", "extramsg", false)). + to_error (msg (4, "function", "boolean")) + - it diagnoses too many arguments: | + expect (f ("version", "name", "extramsg", nop, false)). + to_error (toomanyarg (this_module, fname, 4, 5)) + pending "issue #76" + expect (f ("version", "name", nop, false)). + to_error (toomanyarg (this_module, fname, 3, 4)) - it returns a function: - f = M.deprecate (function () end, "clone_rename") - expect (type (f)).to_be "function" + expect (type (f ("0", "deprecated", nop))).to_be "function" + expect (f ("0", "deprecated", nop)).not_to_be (nop) - context with deprecated function: - it executes the deprecated function: expect (runscript 'error "oh noes!"').to_contain_error "oh noes!" - it passes arguments to the deprecated function: expect (runscript ("print (table.concat ({...}, ', '))", nil, {"foo", "bar", "baz"})).to_output "foo, bar, baz\n" - - it returns deprecated function results: + - it returns deprecated function results: | script = [[ - deprecate = require "std.base".deprecate - fn = deprecate (function () return "foo", "bar", "baz" end, "fn") + DEPRECATED = require "std.base".DEPRECATED + fn = DEPRECATED ("0", "fn", function () return "foo", "bar", "baz" end) print (fn ()) ]] expect (luaproc (script)).to_output "foo\tbar\tbaz\n" - it writes a warning to stderr: expect (runscript 'error "oh noes!"'). - to_contain_error "deprecated, and will go away" + to_match_error "deprecated.*, and will be removed" + - it writes the version string to stderr: + expect (runscript 'error "oh noes!"'). + to_contain_error "in release 0" - it writes the call location to stderr: | expect (runscript 'error "oh noes!"'). to_match_error "^%S+:1: " - - it does not repeat the warning on additional calls: - script = [[ - deprecate = require "std.base".deprecate - fn = deprecate (function () error "oh noes!" end, "fn") - fn () -- line 3 - fn () -- line 4 - ]] - expect (luaproc (script)). - not_to_match_error "^%S+:3:.*deprecated.*\n%S+:4:.*deprecated" + - context with _DEBUG: + - before: | + script = [[ + DEPRECATED = require "std.base".DEPRECATED + fn = DEPRECATED ("0", "fn", function () io.stderr:write "oh noes!\n" end) + fn () -- line 3 + fn () -- line 4 + ]] + - it warns only on first call by default: + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" + - it warns only on first call with _DEBUG set to true: + script = "_DEBUG = true " .. script + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" + - it warns only on first call with _DEBUG.compat unset: + script = "_DEBUG = {} " .. script + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" + - it does not warn at all when set to false: | + script = "_DEBUG = false " .. script + expect (luaproc (script)).not_to_match_error "%d:.*deprecated" + - it does not warn at all when compat field is set to true: | + script = "_DEBUG = { compat = true } " .. script + expect (luaproc (script)).not_to_match_error "%d:.*deprecated" + - it warns on every call with compat field set to false: + script = "_DEBUG = { compat = false } " .. script + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" + - describe export: - before: @@ -111,7 +148,7 @@ specify std.base: M = { "base_spec.yaml" } f (M, "export (any?)", mkmagic) expect (M.export ()).to_be (MAGIC) - - it stores the passed function when _ARGCHECK is disabled: + - it stores the passed function when _ARGCHECK is disabled: | script = [[ _DEBUG = false local debug = require "std.debug_init" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 5a10dff..fa04dd1 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1,6 +1,9 @@ before: - list = require "std.list" - List = list {} + this_module = "std.list" + + M = require (this_module) + + List = M {} l = List {"foo", "bar", "baz"} @@ -56,7 +59,7 @@ specify std.list: - describe append: - before: - f = list.append + f = M.append - it diagnoses missing arguments: | expect (f ()). to_error "bad argument #1 to 'std.list.append' (List expected, got no value)" @@ -90,7 +93,7 @@ specify std.list: - describe compare: - before: a, b = List {"foo", "bar"}, List {"foo", "baz"} - f = list.compare + f = M.compare - it diagnoses missing arguments: | expect (f ()). to_error "bad argument #1 to 'std.list.compare' (List expected, got no value)" @@ -162,7 +165,7 @@ specify std.list: - describe concat: - before: l = List {"foo", "bar"} - f = list.concat + f = M.concat - it diagnoses missing arguments: | expect (f ()). to_error "bad argument #1 to 'std.list.concat' (List expected, got no value)" @@ -204,7 +207,7 @@ specify std.list: - describe cons: - before: - f = list.cons + f = M.cons - it diagnoses missing arguments: | expect (f ()). to_error "bad argument #1 to 'std.list.cons' (List expected, got no value)" @@ -228,23 +231,48 @@ specify std.list: - describe depair: - before: t = {"first", "second", third = 4} - l = list.enpair (t) - f = list.depair - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.depair' (List or table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.depair' (List or table expected, got boolean)" - expect (f (List {0})). - to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got number at index 1)" - expect (f (List {{}})). - to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got 0-tuple at index 1)" - expect (f (List {{"a"}})). - to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got 1-tuple at index 1)" - expect (f (List {{"a", "b"}, ""})). - to_error "bad argument #1 to 'std.list.depair' (List or table of pairs expected, got string at index 2)" - - context when called as a list object method: + l = M.enpair (t) + + fname = "depair" + + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "List or table", "boolean")) + expect (f (List {0})). + to_error (msg (1, "List or table of pairs", "number at index 1")) + expect (f (List {{}})). + to_error (msg (1, "List or table of pairs", "0-tuple at index 1")) + expect (f (List {{"a"}})). + to_error (msg (1, "List or table of pairs", "1-tuple at index 1")) + expect (f (List {{"a", "b"}, ""})). + to_error (msg (1, "List or table of pairs", "string at index 2")) + + - it returns a primitive table: + expect (prototype (f (l))).to_be "table" + - it works with an empty list: + l = List {} + expect (f (l)).to_equal {} + - it is the inverse of enpair: + expect (f (l)).to_equal (t) + + - context as a list object method: + - before: + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:depair' was deprecated" + end + expect (select (2, capture (f, {l}))).to_be (nil) + - it returns a primitive table: expect (prototype (l:depair ())).to_be "table" - it works with an empty list: @@ -255,9 +283,12 @@ specify std.list: - describe elems: + - before: + fname = "elems" + - context as a module function: - before: - f = list.elems + f = M[fname] - it writes a deprecation warning to standard error on first call: | -- Unwrap functable @@ -266,7 +297,7 @@ specify std.list: _, err = capture (f, {{}}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "elems is deprecated" + expect (err).to_contain "'list.elems' was deprecated" end expect (select (2, capture (f, {{}}))).to_be (nil) @@ -280,6 +311,18 @@ specify std.list: expect (t).to_equal {} - context as an object method: + - before: + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:elems' was deprecated" + end + _, err = capture (f, {l}) + expect (err).to_be (nil) + - it is an iterator over list members: t = {} for e in l:elems () do table.insert (t, e) end @@ -293,28 +336,33 @@ specify std.list: - describe enpair: - before: t = {"first", "second", third = 4} - f = list.enpair - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.enpair' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.enpair' (table expected, got boolean)" - - it returns a list object: - expect (prototype (f (t))).to_be "List" - - it works for an empty table: - expect (f {}).to_equal (List {}) - - it turns a table into a list of pairs: - expect (f (t)). - to_equal (List {List {1, "first"}, List {2, "second"}, List {"third", 4}}) + fname = "enpair" + + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: | + expect (f (false)).to_error (msg (1, "table", "boolean")) + + - it returns a list object: + expect (prototype (f (t))).to_be "List" + - it works for an empty table: + expect (f {}).to_equal (List {}) + - it turns a table into a list of pairs: + expect (f (t)). + to_equal (List {List {1, "first"}, List {2, "second"}, List {"third", 4}}) - describe filter: - before: l = List {"foo", "bar", "baz", "quux"} p = function (e) return (e:match "a" ~= nil) end - f = list.filter + f = M.filter - it diagnoses missing arguments: | expect (f ()). @@ -341,7 +389,7 @@ specify std.list: - describe flatten: - before: l = List {List {List {"one"}}, "two", List {List {"three"}, "four"}} - f = list.flatten + f = M.flatten - it diagnoses missing arguments: | expect (f ()). @@ -366,7 +414,7 @@ specify std.list: - before: op = require "std.functional".op l = List {1, 10, 100} - f = list.foldl + f = M.foldl - it diagnoses missing arguments: | expect (f ()). @@ -391,7 +439,7 @@ specify std.list: - before: op = require "std.functional".op l = List {1, 10, 100} - f = list.foldr + f = M.foldr - it diagnoses missing arguments: | expect (f ()). @@ -414,26 +462,24 @@ specify std.list: - describe index_key: - before: - f = list.index_key - - - it writes a deprecation warning to standard error on first call: | - f = list.index_key.call - _, err = capture (f, {1, List {{1}}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "index_key is deprecated" - end - _, err = capture (f, {1, List {{1}}}) - expect (err).to_be (nil) + fname = "index_key" - - it diagnoses missing arguments: | - expect (f ()). - to_be "bad argument #2 to 'std.list.index_key' (List expected, got no value)" - - it diagnose wrong argument types: | - expect (f (false, false)). - to_be "bad argument #2 to 'std.list.index_key' (List expected, got boolean)" + - context as a module function: + - before: + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {1, List {{1}}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list.index_key' was deprecated" + end + _, err = capture (f, {1, List {{1}}}) + expect (err).to_be (nil) - - context when called as a module function: - it makes a map of matched table field values to table list offsets: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} t = f ("a", l) @@ -453,29 +499,59 @@ specify std.list: expect (f (2, l)).to_equal {3, 1} expect (f (3, l)).to_equal {nil, nil, 3} + - context as an object method: + - before: + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l, 1}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:index_key' was deprecated" + end + _, err = capture (f, {l, 1}) + expect (err).to_be (nil) + + - it makes a map of matched table field values to table list offsets: + l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} + t = l:index_key "a" + expect (t).to_equal {b = 1, x = 3} + for k, v in pairs (t) do + expect (k).to_equal (l[v]["a"]) + end + - it captures only the last matching list offset: + l = List {{a = "b"}, {a = "x"}, {a = "b"}} + t = l:index_key "a" + expect (t.b).not_to_be (1) + expect (t.x).to_be (2) + expect (t.b).to_be (3) + - it produces incomplete indices when faced with repeated matching table values: + l = List {{1, 2, 3}, {2}, {2, 1, 3, 2, 1}} + expect (l:index_key (1)).to_equal {1, 3} + expect (l:index_key (2)).to_equal {3, 1} + expect (l:index_key (3)).to_equal {nil, nil, 3} + - describe index_value: - before: - f = list.index_value - - - it writes a deprecation warning to standard error on first call: | - f = list.index_value.call - _, err = capture (f, {1, List {{1}}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "index_value is deprecated" - end - _, err = capture (f, {1, List {{1}}}) - expect (err).to_be (nil) + fname = "index_value" - - it diagnoses missing arguments: | - expect (f ()). - to_be "bad argument #2 to 'std.list.index_value' (List expected, got no value)" - - it diagnose wrong argument types: | - expect (f (false, false)). - to_be "bad argument #2 to 'std.list.index_value' (List expected, got boolean)" + - context as a module function: + - before: + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {1, List {{1}}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list.index_value' was deprecated" + end + _, err = capture (f, {1, List {{1}}}) + expect (err).to_be (nil) - - context when called as a module function: - it makes a table of matched table field values to table list references: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} t = f ("a", l) @@ -495,12 +571,45 @@ specify std.list: expect (f (2, l)).to_equal {l[3], l[1]} expect (f (3, l)).to_equal {nil, nil, l[3]} + - context as an object method: + - before: + l = List {{1}} + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l, 1}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:index_value' was deprecated" + end + _, err = capture (f, {l, 1}) + expect (err).to_be (nil) + + - it makes a table of matched table field values to table list references: + l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} + t = l:index_value "a" + expect (t).to_equal {b = l[1], x = l[3]} + for k, v in pairs (t) do + expect (k).to_equal (v["a"]) + end + - it captures only the last matching list offset: + l = List {{a = "b"}, {a = "x"}, {a = "b"}} + t = l:index_value "a" + expect (t.b).not_to_be (l[1]) + expect (t.x).to_be (l[2]) + expect (t.b).to_be (l[3]) + - it produces incomplete indices when faced with repeated matching table values: + l = List {{1, 2, 3}, {2}, {2, 1, 3, 2, 1}} + expect (l:index_value (1)).to_equal {l[1], l[3]} + expect (l:index_value (2)).to_equal {l[3], l[1]} + expect (l:index_value (3)).to_equal {nil, nil, l[3]} + - describe map: - before: l = List {1, 2, 3, 4, 5} sq = function (n) return n * n end - f = list.map + f = M.map - it diagnoses missing arguments: | expect (f ()). @@ -534,7 +643,7 @@ specify std.list: - before: l = List {List {1, 2, 3}, List {4, 5}} n = function (...) return select ("#", ...) end - f = list.map_with + f = M.map_with - it diagnoses missing arguments: | expect (f ()). @@ -575,7 +684,7 @@ specify std.list: {first = 1, second = 2, third = 3}, {first = "1st", second = "2nd", third = "3rd"}, } - f = list.project + f = M.project - it diagnoses missing arguments: | expect (f (f)). @@ -605,39 +714,61 @@ specify std.list: - describe relems: - before: - f = list.relems - - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f.call, {{}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "relems is deprecated" - end - _, err = capture (f.call, {{}}) - expect (err).to_be (nil) - - - it is a reverse iterator over list members: - t = {} - for e in List.relems (l) do table.insert (t, e) end - expect (t).to_equal {"baz", "bar", "foo"} - - it works for an empty list: - t = {} - for e in List.relems (List {}) do table.insert (t, e) end - expect (t).to_equal {} - - it can be called from the list module: - t = {} - for e in List.relems (l) do table.insert (t, e) end - expect (t).to_equal {"baz", "bar", "foo"} - - it can be called as a list object method: - t = {} - for e in l:relems () do table.insert (t, e) end - expect (t).to_equal {"baz", "bar", "foo"} + fname = "relems" + + - context as a module function: + - before: + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {{}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list.relems' was deprecated" + end + _, err = capture (f, {{}}) + expect (err).to_be (nil) + + - it is a reverse iterator over list members: + t = {} + for e in f (l) do table.insert (t, e) end + expect (t).to_equal {"baz", "bar", "foo"} + - it works for an empty list: + t = {} + for e in f (List {}) do table.insert (t, e) end + expect (t).to_equal {} + + - context as an object method: + - before: + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:relems' was deprecated" + end + _, err = capture (f, {l}) + expect (err).to_be (nil) + + - it is a reverse iterator over list members: + t = {} + for e in l:relems () do table.insert (t, e) end + expect (t).to_equal {"baz", "bar", "foo"} + - it works for an empty list: + t, l = {}, List {} + for e in l:relems () do table.insert (t, e) end + expect (t).to_equal {} + - describe rep: - before: l = List {"foo", "bar"} - f = list.rep + f = M.rep - it diagnoses missing arguments: | expect (f ()). @@ -664,18 +795,50 @@ specify std.list: - describe reverse: - before: l = List {"foo", "bar", "baz", "quux"} - f = list.reverse - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f.call, {{}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "reverse is deprecated" - end - _, err = capture (f.call, {{}}) - expect (err).to_be (nil) + fname = "reverse" + + - context as a module function: + - before: + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {{}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list.reverse' was deprecated" + end + _, err = capture (f, {{}}) + expect (err).to_be (nil) + + - it returns a list object: + expect (prototype (f (l))).to_be "List" + - it works for an empty list: + l = List {} + expect (f (l)).to_equal (List {}) + - it makes a new reversed list: + m = l + expect (f (l)). + to_equal (List {"quux", "baz", "bar", "foo"}) + expect (l).to_equal (List {"foo", "bar", "baz", "quux"}) + expect (l).to_be (m) - context when called as a list object method: + - before: + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:reverse' was deprecated" + end + _, err = capture (f, {l}) + expect (err).to_be (nil) + - it returns a list object: expect (prototype (l:reverse ())).to_be "List" - it works for an empty list: @@ -692,7 +855,7 @@ specify std.list: - describe shape: - before: l = List {1, 2, 3, 4, 5, 6} - f = list.shape + f = M.shape - it diagnoses missing arguments: | expect (f ()). @@ -731,7 +894,7 @@ specify std.list: - describe sub: - before: l = List {1, 2, 3, 4, 5, 6, 7} - f = list.sub + f = M.sub - it diagnoses missing arguments: | expect (f ()). @@ -766,7 +929,7 @@ specify std.list: - describe tail: - before: l = List {1, 2, 3, 4, 5, 6, 7} - f = list.tail + f = M.tail - it diagnoses missing arguments: | expect (f ()). @@ -791,16 +954,36 @@ specify std.list: - describe transpose: - before: l = List {List {1, 2}, List {3, 4}, List {5, 6}} - f = list.transpose + fname = "transpose" - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.transpose' (table or List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.transpose' (table or List expected, got boolean)" + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table or List")) + - it diagnoses wrong argument types: | + expect (f (false)).to_error (msg (1, "table or List", "boolean")) + + - it transposes rows and columns: + expect (f (l)).to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) + - it works for an empty list: + expect (f (List {})).to_equal (List {}) + + - context as a list object method: + - before: + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:transpose' was deprecated" + end + _, err = capture (f, {l}) + expect (err).to_be (nil) - - context when called as a list object method: - it returns a list object: | expect (prototype (l:transpose ())).to_be "List" - it returns the result in a new list object: | @@ -819,35 +1002,62 @@ specify std.list: - describe zip_with: - before: l = List {List {1, 2}, List {3, 4}, List {5}} - f = list.zip_with + fn = function (...) return tonumber (table.concat {...}) end - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.zip_with' (List expected, got no value)" - expect (f (l)). - to_error "bad argument #2 to 'std.list.zip_with' (function expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.zip_with' (List expected, got boolean)" - expect (f (List {{}}, f)). - to_error "bad argument #1 to 'std.list.zip_with' (List of Lists expected, got table at index 1)" - expect (f (List {List {}, false}, f)). - to_error "bad argument #1 to 'std.list.zip_with' (List of Lists expected, got boolean at index 2)" - expect (f (l, false)). - to_error "bad argument #2 to 'std.list.zip_with' (function expected, got boolean)" + fname = "zip_with" + f = l[fname] - - context when called as a list object method: + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: | + expect (f ()).to_error (msg (1, "List")) + expect (f (l)).to_error (msg (2, "function")) + - it diagnoses wrong argument types: | + expect (f (false)).to_error (msg (1, "List", "boolean")) + expect (f (List {{}}, f)). + to_error (msg (1, "List of Lists", "table at index 1")) + expect (f (List {List {}, false}, f)). + to_error (msg (1, "List of Lists", "boolean at index 2")) + expect (f (l, false)).to_error (msg (2, "function", "boolean")) + + - it returns a list object: | + expect (prototype (f (l, fn))).to_be "List" + - it returns the result in a new list object: | + expect (f (l, fn)):not_to_be (l) + - it does not perturb the argument list: | + m = f (l, fn) + expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) + - it combines column entries with a function: | + expect (f (l, fn)).to_equal (List {135, 24}) + - it works for an empty list: | + l = List {} + expect (f (l, fn)).to_equal (List {}) + + - context as a list object method: - before: - fn = function (...) return tonumber (table.concat {...}) end + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l, fn}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:zip_with' was deprecated" + end + _, err = capture (f, {l, fn}) + expect (err).to_be (nil) + - it returns a list object: | - expect (prototype (l:zip_with (fn))).to_be "List" + expect (prototype (f (l, fn))).to_be "List" - it returns the result in a new list object: | - expect (l:zip_with (fn)):not_to_be (l) + expect (f (l, fn)):not_to_be (l) - it does not perturb the argument list: | - m = l:zip_with (fn) + m = f (l, fn) expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) - it combines column entries with a function: | - expect (l:zip_with (fn)).to_equal (List {135, 24}) + expect (f (l, fn)).to_equal (List {135, 24}) - it works for an empty list: | l = List {} - expect (l:zip_with (fn)).to_equal (List {}) + expect (f (l, fn)).to_equal (List {}) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 12fb251..5d3adb3 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -67,7 +67,7 @@ specify std.string: _, err = capture (f, {"std.string"}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "assert is deprecated" + expect (err).to_contain "'string.assert' was deprecated" end _, err = capture (f, {"std.string"}) expect (err).to_be (nil) @@ -583,18 +583,16 @@ specify std.string: to_be ('Vector ("any", {a, b, ' .. tostring (a[3]) .. ', e})') -# DEPRECATED: Remove in first release following 2015-06-30. - describe require_version: - before: fname = "require_version" - msg = bind (badarg, {this_module, "require"}) f = M[fname] - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {"std.string"}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "require_version is deprecated" + expect (err).to_contain "'string.require_version' was deprecated" end _, err = capture (f, {"std.string"}) expect (err).to_be (nil) @@ -773,18 +771,16 @@ specify std.string: expect (subject).to_be (original) -# DEPRECATED: Remove in first release following 2015-07-30. - describe tostring: - before: fname = "tostring" - msg = bind (badarg, {this_module, fname}) f = M[fname] - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {"std.string"}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "tostring is deprecated" + expect (err).to_contain "'string.tostring' was deprecated" end _, err = capture (f, {"std.string"}) expect (err).to_be (nil) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 5e43805..7a5fd6c 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -84,13 +84,15 @@ specify std.table: - describe clone_rename: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } - f = M.clone_rename + + fname = "clone_rename" + f = M[fname] - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {{}, subject}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "clone_rename is deprecated" + expect (err).to_contain "'table.clone_rename' was deprecated" end _, err = capture (f, {{}, subject}) expect (err).to_be (nil) @@ -346,9 +348,6 @@ specify std.table: - describe metamethod: - before: - fname = "metamethod" - f = M[fname] - Object = require "std.object" objmethod = function () end obj = Object { @@ -356,11 +355,14 @@ specify std.table: _method = objmethod, } + fname = "metamethod" + f = M[fname] + - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {{}, subject}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain (fname .. " is deprecated") + expect (err).to_contain "'table.metamethod' was deprecated" end _, err = capture (f, {{}, subject}) expect (err).to_be (nil) @@ -441,13 +443,14 @@ specify std.table: - describe ripairs: - before: - f = M.ripairs + fname = "ripairs" + f = M[fname] - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {{}, subject}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "ripairs is deprecated" + expect (err).to_contain "'table.ripairs' was deprecated" end _, err = capture (f, {{}, subject}) expect (err).to_be (nil) From 0ef8b673cf3439118a8ba17ad439eb5c87c0f48f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 4 Aug 2014 10:54:41 +0100 Subject: [PATCH 319/703] refactor: factor away math.pow. Upcoming Lua 5.3 deprecates `math.pow` in favour of the `^` operator. We can easily eliminate stdlib's references to `math.pow` for future compatibility. * lib/std/operator.lua ("^"): Use `^` rather than `math.pow`. * lib/std/functional.lua (bind, fold): Use "^" lambda function instead of `math.pow` in LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 4 ++-- lib/std/operator.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index d7b43f6..6377045 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -22,7 +22,7 @@ local M = { "std.functional" } -- @tparam table t {p1=a1, ..., pn=an} table of parameters to bind to given arguments -- @return function with *pi* already bound -- @usage --- > cube = bind (math.pow, {[2] = 3}) +-- > cube = bind (std.lambda "^", {[2] = 3}) -- > =cube (2) -- 8 local bind = export (M, "bind (func, any?*)", function (f, ...) @@ -152,7 +152,7 @@ end) -- @return result -- @see std.list.foldl -- @see std.list.foldr --- @usage fold (math.pow, 1, std.elems, {2, 3, 4}) +-- @usage fold (std.lambda "^", 1, std.elems, {2, 3, 4}) export (M, "fold (func, any, func, any*)", function (f, d, i, ...) local r = d for e in i (...) do diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 40bf7ee..e5e4a7c 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -49,7 +49,7 @@ return { ["*"] = function (a, b) return a * b end, ["/"] = function (a, b) return a / b end, ["%"] = function (a, b) return a % b end, - ["^"] = function (a, b) return math.pow (a, b) end, + ["^"] = function (a, b) return a ^ b end, ["and"] = function (a, b) return a and b end, ["or"] = function (a, b) return a or b end, ["not"] = function (a) return not a end, From cae487befc01d2258b212c95c20ab4edcc0f7ee9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 5 Aug 2014 13:17:26 +0100 Subject: [PATCH 320/703] std: remove __ipairs support in favour of 1..#t iteration. * specs/std_spec.yaml (ipairs, ireverse, ripairs): Adjust specifications to verify treatment of __len metamethod, and ignore __ipairs metamethod. * lib/base.lua (ipairs): Using __len if available, or # operator otherwise, iterate over elments 1..#t. (unwrap__ipairs): Remove. Simplifications above make this function superfluous. (ripairs, ireverse): Adjust accordingly. * lib/std/vector.lua (core_metatable.__ipairs) (alien_functions.__ipairs): Remove. * lib/std.lua.in (ipairs, ireverse, ripairs): Update LDocs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 26 ++++++++--- lib/std.lua.in | 20 ++++++--- lib/std/base.lua | 104 ++++++++++++++++++++++---------------------- lib/std/vector.lua | 25 ----------- specs/std_spec.yaml | 44 +++++++++++-------- 5 files changed, 112 insertions(+), 107 deletions(-) diff --git a/NEWS b/NEWS index 2850d89..9714bbf 100644 --- a/NEWS +++ b/NEWS @@ -51,17 +51,33 @@ Stdlib NEWS - User visible changes table.sort (t, std.lambda "<") - - New `std.ipairs` and `std.pairs` functions, that respect `__ipairs` - and `__pairs` metamethods, even on Lua 5.1. + - New `std.ipairs` function that respects `__len` metamethod, while always + iterating from index 1 through #t. This makes it easy to add metamethods + to a table that don't stop at the first `nil` value, among other things: + + function maxn (t) + return math.max ( + unpack (filter (lambda '|e|type(e)=="number"', pairs, t)) + ) + end + + function processallargs (x, ...) + for i, v in ipairs (setmetatable ({x, ...}, { __len = maxn })) do + process (i, v) + end + end - New `std.ielems` and `std.elems` functions for iterating sequences cleanly, - while respecting `__ipairs` and `__pairs` metamethods respectively. + while respecting `__len` and `__pairs` metamethods respectively. - New `std.ireverse` function for reversing the array part of any - table, while respecting `__ipairs`, even on Lua 5.1. + table, while respecting `__len`. + + - New `std.pairs` function that respects `__pairs` metamethod, even on + Lua 5.1. - New `std.ripairs` function for returning index & value pairs in - reverse order, while respecting `__ipairs`, even on Lua 5.1. + reverse order, while respecting `__len`. ** Deprecations: diff --git a/lib/std.lua.in b/lib/std.lua.in index 099d94d..a96f0e2 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -83,7 +83,7 @@ export (M, "eval (string)", base.eval) --- An iterator over the integer keyed elements of a sequence. --- If *t* has an `__ipairs` metamethod, use that to iterate. +-- If *t* has a `__len` metamethod, iterate up to the index it returns. -- @function ielems -- @tparam table t a table -- @treturn function iterator function @@ -96,7 +96,11 @@ export (M, "eval (string)", base.eval) export (M, "ielems (table)", base.ielems) ---- Enhance core `ipairs` to respect `__ipairs` even in Lua 5.1. +--- An iterator over elements `1..#t`, respecting `__len` metamethod. +-- +-- Unlike Lua 5.1, which ignores `__len`, and Lua 5.2, which looks for +-- and uses `__ipairs`, this iterator always iterates over elements with +-- integer keys starting at 1, up to and including `#t`. -- @function ipairs -- @tparam table t a table -- @treturn function iterator function @@ -105,12 +109,16 @@ export (M, "ielems (table)", base.ielems) -- @see ielems -- @see pairs -- @usage --- for i, v in std.ipairs {"a", "b", "c"} do process (v) end +-- -- don't stop at first nil +-- args = {"first", "second", nil, "last"} +-- setmetatable (args, { __len = std.lambda "=4" }) +-- for i, v in std.ipairs (args) do process (i, v) end export (M, "ipairs (table)", base.ipairs) --- Return a new table with element order reversed. --- If *t* has an `__ipairs` metamethod, use that to iterate. +-- Apart from the order of the elments returned, this function follows +-- the same rules as @{ipairs} for determining first and last elements. -- @function ireverse -- @tparam table t a table -- @treturn table a new table with integer keyed elements in reverse @@ -207,8 +215,8 @@ export (M, "require (string, string?, string?, string?)", base.require) --- An iterator like ipairs, but in reverse. --- If *t* has an `__ipairs` metamethod, use that to prefetch element --- pairs. +-- Apart from the order of the elments 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 diff --git a/lib/std/base.lua b/lib/std/base.lua index 79c68b2..353882d 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -27,6 +27,54 @@ local operator = require "std.operator" +--[[ ====================== ]]-- +--[[ Documented in std.lua. ]]-- +--[[ ====================== ]]-- + + +local _len = operator["#"] +local _pairs = pairs + + +-- Respect __pairs metamethod, even in Lua 5.1. +local function pairs (t) + return ((getmetatable (t) or {}).__pairs or _pairs) (t) +end + + +-- Iterate over keys 1..#l, like Lua 5.3. +local function ipairs (l) + local len = _len (l) + + return function (l, n) + n = n + 1 + if n <= len then + return n, l[n] + end + end, l, 0 +end + + +local function ripairs (t) + return function (t, n) + n = n - 1 + if n > 0 then + return n, t[n] + end + end, t, _len (t) + 1 +end + + +-- Be careful not to compact holes from `t` when reversing. +local function ireverse (t) + local r, len = {}, _len (t) + for i = 1, len do r[len - i + 1] = t[i] end + return r +end + + + + --[[ ================= ]]-- --[[ Helper Functions. ]]-- --[[ ================= ]]-- @@ -133,25 +181,6 @@ local function module_version (module, pattern) end ---- Unwrap proxy table or generator from __ipairs metamethod. --- If *t* has an __ipairs metamethod, populate a new table by calling --- it and return that, otherwise pass the argument through unchanged. --- @tparam table t a table --- @treturn table either *t*, or unwrapped proxy from *t*'s __ipairs --- metamethod -local function unwrap__ipairs (t) - local __ipairs = (getmetatable (t) or {}).__ipairs - if __ipairs then - -- Two passes in case __ipairs fetches from a proxy or similar, - -- where #t might not be accurate. - local r = {} - for i, v in __ipairs (t) do r[i] = v end - t = r - end - return t -end - - --- Iterator adaptor for discarding first value from core iterator function. -- @func factory iterator to be wrapped -- @param ... *factory* arguments @@ -173,44 +202,13 @@ local function wrapiterator (factory, ...) end -local function __ipairs (l) - return ((getmetatable (l) or {}).__ipairs or ipairs) (l) -end - - -local function __pairs (t) - return ((getmetatable (t) or {}).__pairs or pairs) (t) -end - - local function elems (t) return wrapiterator ((getmetatable (t) or {}).__pairs or pairs, t) end local function ielems (l) - return wrapiterator ((getmetatable (l) or {}).__ipairs or ipairs, l) -end - - -local function ireverse (t) - t = unwrap__ipairs (t) - - local r = {} - for i = #t, 1, -1 do r[#r + 1] = t[i] end - return r -end - - -local function ripairs (t) - t = unwrap__ipairs (t) - - return function (t, n) - n = n - 1 - if n > 0 then - return n, t[n] - end - end, t, #t + 1 + return wrapiterator (ipairs, l) end @@ -838,11 +836,11 @@ return { eval = eval, elems = elems, ielems = ielems, - ipairs = __ipairs, + ipairs = ipairs, ireverse = ireverse, lambda = lambda, memoize = memoize, - pairs = __pairs, + pairs = pairs, ripairs = ripairs, require = require_version, tostring = tostring, diff --git a/lib/std/vector.lua b/lib/std/vector.lua index e1ab107..264d574 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -295,21 +295,6 @@ core_metatable = { end, - --- Iterate consecutively over all elements with `ipairs (vector)`. - -- @function __ipairs - -- @treturn function iterator function - -- @usage for index, anelement in ipairs (anvector) do ... end - __ipairs = function (self) - local i, n = 0, self.length - return function () - i = i + 1 - if i <= n then - return i, self.buffer[i] - end - end - end, - - --- Return the `n`th element in this vector. -- @function __index -- @int n 1-based index, or negative to index starting from the right @@ -481,16 +466,6 @@ local alien_functions = { alien_metatable = { _type = "Vector", - __ipairs = function (self) - local i, n = 0, self.length - return function () - i = i + 1 - if i <= n then - return i, self.buffer:get ((i - 1) * self.size + 1, self.type) - end - end - end, - __index = function (self, n) argcheck ("__index", 2, "int|string", n) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index d719200..8b5a53e 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -8,15 +8,23 @@ before: | "tostring", "version" } -- Tables with iterator metamethods used by various examples. - function str_iter (t) - return function (x, n) - if n < #x[1] then - return n+1, string.sub (x[1], n+1, n+1) - end - end, t, 0 - end - __pairs = setmetatable ({ "a string" }, { __pairs = str_iter }) - __ipairs = setmetatable ({ "a string" }, { __ipairs = str_iter }) + __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) @@ -321,9 +329,9 @@ specify std: t[#t + 1] = e end expect (t).to_equal {"foo", 42} - - it respects __ipairs metamethod: + - it respects __len metamethod: t = {} - for v in f (__ipairs) do t[#t + 1] = v end + 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 = {} @@ -356,9 +364,9 @@ specify std: t[i] = v end expect (t).to_equal {"foo", 42} - - it respects __ipairs metamethod: + - it respects __len metamethod: t = {} - for k, v in f (__ipairs) do t[k] = v end + 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 = {} @@ -386,8 +394,8 @@ specify std: expect (f {1, 2, "five"}).to_equal {"five", 2, 1} - it ignores the dictionary part of a table: expect (f {1, 2, "five"; a = "b", c = "d"}).to_equal {"five", 2, 1} - - it respects __ipairs metamethod: - expect (f (__ipairs)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} + - it respects __len metamethod: + expect (f (__index)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} - it works for an empty list: expect (f {}).to_equal {} @@ -642,12 +650,12 @@ specify std: 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 __ipairs metamethod: + - it respects __len metamethod: t = {} - for i, v in f (__ipairs) do t[i] = v end + 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 (__ipairs) do t[#t + 1] = v end + 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 = {} From 3a0ec6025e7b279080937a6cd8fdd8516abd25ec Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 5 Aug 2014 17:21:34 +0100 Subject: [PATCH 321/703] specs: capture list.map_with deprecation warning. * specs/list_spec.yaml (map_with): Modernize specifications for argument checking, split module function and object method specifications, and capture deprecation warning for list:map_with on first invocation. Signed-off-by: Gary V. Vaughan --- specs/list_spec.yaml | 86 +++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index fa04dd1..2d92365 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -642,39 +642,68 @@ specify std.list: - describe map_with: - before: l = List {List {1, 2, 3}, List {4, 5}} - n = function (...) return select ("#", ...) end - f = M.map_with + fn = function (...) return select ("#", ...) end - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.map_with' (function expected, got no value)" - expect (f (f)). - to_error "bad argument #2 to 'std.list.map_with' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.map_with' (function expected, got boolean)" - expect (f (f, false)). - to_error "bad argument #2 to 'std.list.map_with' (List expected, got boolean)" - expect (f (f, List {{}})). - to_error "bad argument #2 to 'std.list.map_with' (List of Lists expected, got table at index 1)" - expect (f (f, List {List {}, false})). - to_error "bad argument #2 to 'std.list.map_with' (List of Lists expected, got boolean at index 2)" + fname = "map_with" + + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (fn)).to_error (msg (2, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (fn, false)).to_error (msg (2, "List", "boolean")) + expect (f (fn, List {{}})). + to_error (msg (2, "List of Lists", "table at index 1")) + expect (f (fn, List {List {}, false})). + to_error (msg (2, "List of Lists", "boolean at index 2")) - - context when called as a list object method: - it returns a list object: - m = l:map_with (n) + m = f (fn, l) expect (prototype (m)).to_be "List" + - it creates a new list: + o = l + m = f (fn, l) + expect (l).to_equal (o) + expect (m).not_to_equal (o) + expect (l).to_equal (List {List {1, 2, 3}, List {4, 5}}) + - it maps a function over a list: + expect (f (fn, l)).to_equal (List {3, 2}) - it works for an empty list: l = List {} - expect (l:map_with (n)).to_equal (List {}) + expect (f (fn, l)).to_equal (List {}) + + - context when called as a list object method: + - before: + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {l, fn}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'list:map_with' was deprecated" + end + _, err = capture (f, {l, fn}) + expect (err).to_be (nil) + + - it returns a list object: + m = f (l, fn) + expect (prototype (m)).to_be "List" - it creates a new list: o = l - m = l:map_with (n) + m = f (l, fn) expect (l).to_equal (o) expect (m).not_to_equal (o) expect (l).to_equal (List {List {1, 2, 3}, List {4, 5}}) - it maps a function over a list: - expect (l:map_with (n)).to_equal (List {3, 2}) + expect (f (l, fn)).to_equal (List {3, 2}) + - it works for an empty list: + l = List {} + expect (f (l, fn)).to_equal (List {}) - describe project: @@ -1005,17 +1034,16 @@ specify std.list: fn = function (...) return tonumber (table.concat {...}) end fname = "zip_with" - f = l[fname] - context as a module function: - before: msg = bind (badarg, {this_module, fname}) f = M[fname] - - it diagnoses missing arguments: | + - it diagnoses missing arguments: expect (f ()).to_error (msg (1, "List")) expect (f (l)).to_error (msg (2, "function")) - - it diagnoses wrong argument types: | + - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "List", "boolean")) expect (f (List {{}}, f)). to_error (msg (1, "List of Lists", "table at index 1")) @@ -1023,16 +1051,16 @@ specify std.list: to_error (msg (1, "List of Lists", "boolean at index 2")) expect (f (l, false)).to_error (msg (2, "function", "boolean")) - - it returns a list object: | + - it returns a list object: expect (prototype (f (l, fn))).to_be "List" - - it returns the result in a new list object: | + - it returns the result in a new list object: expect (f (l, fn)):not_to_be (l) - - it does not perturb the argument list: | + - it does not perturb the argument list: m = f (l, fn) expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) - - it combines column entries with a function: | + - it combines column entries with a function: expect (f (l, fn)).to_equal (List {135, 24}) - - it works for an empty list: | + - it works for an empty list: l = List {} expect (f (l, fn)).to_equal (List {}) From 5f32545122cea731b225e268931650fa0a706da9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 5 Aug 2014 18:00:07 +0100 Subject: [PATCH 322/703] refactor: use std.ipairs and std.pairs everywhere internally. * lib/std/container.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/set.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua, lib/std/vector.lua: Import and use `base.ipairs` and `base.pairs` everywhere * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 6 ++++++ lib/std/container.lua | 4 ++-- lib/std/functional.lua | 2 +- lib/std/io.lua | 3 ++- lib/std/list.lua | 3 ++- lib/std/optparse.lua | 4 ++++ lib/std/package.lua | 3 ++- lib/std/set.lua | 2 +- lib/std/string.lua | 4 ++-- lib/std/table.lua | 3 ++- lib/std/tree.lua | 1 + lib/std/vector.lua | 4 ++-- 12 files changed, 27 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 9714bbf..42e1a3c 100644 --- a/NEWS +++ b/NEWS @@ -67,6 +67,9 @@ Stdlib NEWS - User visible changes end end + All of stdlib's implementation now uses `std.ipairs` rather than `ipairs` + internally. + - New `std.ielems` and `std.elems` functions for iterating sequences cleanly, while respecting `__len` and `__pairs` metamethods respectively. @@ -76,6 +79,9 @@ Stdlib NEWS - User visible changes - 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. + - New `std.ripairs` function for returning index & value pairs in reverse order, while respecting `__len`. diff --git a/lib/std/container.lua b/lib/std/container.lua index 7ada779..dab4711 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -69,8 +69,8 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" -local argcheck, export, prototype = - base.argcheck, base.export, base.prototype +local argcheck, export, ipairs, pairs, prototype = + base.argcheck, base.export, base.ipairs, base.pairs, base.prototype local M = { "std.container" } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 6377045..2b2bcff 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -10,7 +10,7 @@ local base = require "std.base" -local export, nop = base.export, base.nop +local export, nop, pairs = base.export, base.nop, base.pairs local M = { "std.functional" } diff --git a/lib/std/io.lua b/lib/std/io.lua index 0edbe3c..951d2e7 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -17,8 +17,9 @@ local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } +local ipairs, pairs = base.ipairs, base.pairs local argerror, export, leaves, split = - base.argerror, base.export, base.leaves, base.split + base.argerror, base.export, base.leaves, base.split local M = { "std.io" } diff --git a/lib/std/list.lua b/lib/std/list.lua index fffb3e7..01f1d43 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -34,8 +34,9 @@ local func = require "std.functional" local object = require "std.object" +local ipairs, pairs = base.ipairs, base.pairs local argcheck, argerror, argscheck, ielems, prototype, ireverse = - base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype, base.ireverse + base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype, base.ireverse local Object = object {} diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 14ec65d..c0fd491 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -74,6 +74,10 @@ ]=] +local base = require "std.base" + +local ipairs, pairs = base.ipairs, base.pairs + local OptionParser -- forward declaration diff --git a/lib/std/package.lua b/lib/std/package.lua index dfad1d5..8a244fe 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -17,7 +17,8 @@ local catfile = require "std.io".catfile local invert = require "std.table".invert local escape_pattern = require "std.string".escape_pattern -local export, split = base.export, base.split +local export, ipairs, pairs, split = + base.export, base.ipairs, base.pairs, base.split local M = { "std.package" } diff --git a/lib/std/set.lua b/lib/std/set.lua index cbd6061..4be577b 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -15,7 +15,7 @@ local base = require "std.base" local container = require "std.container" local Container = container {} -local ielems, prototype = base.ielems, base.prototype +local ielems, pairs, prototype = base.ielems, base.pairs, base.prototype local Set -- forward declaration diff --git a/lib/std/string.lua b/lib/std/string.lua index c6723e5..b644f28 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -16,8 +16,8 @@ local table = require "std.table" local StrBuf = strbuf {} -local export, getmetamethod, split = - base.export, base.getmetamethod, base.split +local export, getmetamethod, pairs, split = + base.export, base.getmetamethod, base.pairs, base.split local totable = table.totable local _format = string.format diff --git a/lib/std/table.lua b/lib/std/table.lua index 98cdb73..9737283 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -13,7 +13,8 @@ local base = require "std.base" -local export, getmetamethod = base.export, base.getmetamethod +local export, getmetamethod, ipairs, pairs = + base.export, base.getmetamethod, base.ipairs, base.pairs local M = { "std.table" } diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 659d6be..52f85f8 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -16,6 +16,7 @@ local container = require "std.container" local func = require "std.functional" local Container = container {} +local ipairs, pairs = base.ipairs, base.pairs local ielems, base_leaves, prototype = base.ielems, base.leaves, base.prototype local fold, op = func.fold, func.op diff --git a/lib/std/vector.lua b/lib/std/vector.lua index 264d574..25018a8 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -41,8 +41,8 @@ local Container = container {} local typeof = type -local argcheck, argscheck, prototype = - base.argcheck, base.argscheck, base.prototype +local argcheck, argscheck, pairs, prototype = + base.argcheck, base.argscheck, base.pairs, base.prototype --[[ ================= ]]-- From 9946b613114dfd1d9133f59e74c419d516bd87f1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 5 Aug 2014 22:32:32 +0100 Subject: [PATCH 323/703] refactor: break apart base.DEPRECATED for component reuse. * lib/std/base.lua (DEPRECATIONMSG, getcompat, setcompat): New functions, factored out of... (DEPRECATED): ...here. Simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 70 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 353882d..59092ac 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -796,31 +796,74 @@ local function export (M, decl, fn, ...) end +-- Required for exported function argument check failures: +local M = { "std.base" } + + +-- Whether to show a deprecation warning the next time a give key is set. +local compat = {} + + +--- Determine whether *key* will show a deprecation warning on next access. +-- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; +-- if `_DEBUG.compat` is false, warn every time *fn* is called; +-- otherwise don't write any warnings, and run *fn* normally. +-- @function setcompat +-- @param key unique identifier for a deprecated API. +local setcompat = export (M, "setcompat (any)", function (key) + compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat == nil) or _DEBUG == true +end) + + +--- Get the deprecation warning status for *key*. +-- @function getcompat +-- @treturn boolean whether to show a deprecation warning. +local getcompat = export (M, "getcompat (any)", function (key) + if compat[key] == nil then + -- Whether to warn on first access. + compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat) or _DEBUG == false + end + return compat[key] +end) + + +--- Format a deprecation warning message. +-- @function DEPRECATIONMSG +-- @string version first deprecation release version +-- @string name function name for automatic warning message +-- @string[opt] extramsg additional warning text +-- @int level call stack level to blame for the error +-- @treturn string deprecation warning message +local DEPRECATIONMSG = export (M, "DEPRECATIONMSG (string, string, [string], int)", +function (version, name, extramsg, level) + if level == nil then level, extramsg = extramsg, nil end + extramsg = extramsg or "and will be removed entirely in a future release" + + local _, where = pcall (function () error ("", level + 3) end) + return (where .. string.format ("%s was deprecated in release %s, %s.\n", + name, version, extramsg)) +end) + + --- Write a deprecation warning to stderr. -- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; -- if `_DEBUG.compat` is false, warn every time *fn* is called; -- otherwise don't write any warnings, and run *fn* normally. --- @func fn deprecated function +-- @function DEPRECATED -- @string version first deprecation release version -- @string name function name for automatic warning message -- @string[opt] extramsg additional warning text +-- @func fn deprecated function -- @return a function to show the warning on first call, and hand off to *fn* -- @usage funcname = deprecate (function (...) ... end, "funcname") -local M = { "std.base" } - -export (M, "DEPRECATED (string, string, [string], func)", +export (M, "DEPRECATED (string, string, [string], func)", function (version, name, extramsg, fn) if fn == nil then fn, extramsg = extramsg, nil end - extramsg = extramsg or "and will be removed entirely in a future release" - local warnmsg = string.format ("%s was deprecated in release %s, %s.", - name, version, extramsg) - local compat = type (_DEBUG) == "table" and _DEBUG.compat or _DEBUG == false return function (...) - if not compat then - local _, where = pcall (function () error ("", 4) end) - io.stderr:write (where .. warnmsg .. "\n") - if _DEBUG == true or (type (_DEBUG) == "table" and _DEBUG.compat == nil) then compat = true end + if not getcompat (fn) then + io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) + setcompat (fn) end return fn (...) end @@ -869,6 +912,9 @@ return { -- Maintenance -- DEPRECATED = M.DEPRECATED, + DEPRECATIONMSG = M.DEPRECATIONMSG, export = export, + getcompat = getcompat, + setcompat = setcompat, toomanyarg_fmt = toomanyarg_fmt, } From 1b290ee40f638b03a4dd4b3c4f5b8faa6cdd2479 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 5 Aug 2014 23:06:51 +0100 Subject: [PATCH 324/703] list: exchange parameter order for list.cons. Closes #72. * specs/list_spec.yaml (cons): Modernize specifications. Specify deprecation warning behaviour when calling cons with arguments in legacy order. * lib/std/list.lua (cons): If arguments appear to be in the wrong order, issue a deprecation warning and rewrite them into the correct order. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 9 +++++++ lib/std/list.lua | 17 ++++++++++--- specs/list_spec.yaml | 59 +++++++++++++++++++++++++++++++++----------- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index 42e1a3c..d52ed9f 100644 --- a/NEWS +++ b/NEWS @@ -140,6 +140,15 @@ Stdlib NEWS - User visible changes - `io.catdir` now raises an error when called with no arguments, for consistency with `io.catfile`. + - `list.cons` now expects arguments in lisp-like order: + + newlist = list.cons (newhead, oldlist) + + If legacy ordered arguments are detected, a deprecation warning is + raised in the first instance, and arguments are reordered before + continuing. List object method `list:cons (newhead)` calls remain + as before. + - `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, diff --git a/lib/std/list.lua b/lib/std/list.lua index 01f1d43..ff492f0 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -117,13 +117,22 @@ end --- Prepend an item to a list. +-- @function cons -- @tparam List l a list -- @param x item -- @treturn List new list containing `{x, unpack (l)}` -local function cons (l, x) - argscheck ("std.list.cons", {"List", "any"}, {l, x}) +local function cons (x, l) + if prototype (x) == "List" and prototype (l) ~= "List" then + if not base.getcompat (cons) then + io.stderr:write (base.DEPRECATIONMSG ("41", + "'std.list.cons' with list argument first", 2)) + base.setcompat (cons) + end + x, l = l, x + end + argscheck ("std.list.cons", {"any", "List?"}, {x, l}) - return List {x, unpack (l)} + return List {x, unpack (l or {})} end @@ -595,7 +604,7 @@ List = Object { -- @function cons -- @param x item -- @treturn List new list containing `{x, unpack (self)}` - cons = cons, + cons = function (self, x) return cons (x, self) end, ------ -- Filter a list according to a predicate. diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 2d92365..23b4490 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -207,25 +207,54 @@ specify std.list: - describe cons: - before: - f = M.cons - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.cons' (List expected, got no value)" - expect (f (l)). - to_error "bad argument #2 to 'std.list.cons' (any value expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.cons' (List expected, got boolean)" - - context when called as a list object method: + fname = "cons" + + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {l, "head"}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list.cons' with list argument first was deprecated" + end + _, err = capture (f, {l, "head"}) + expect (err).to_be (nil) + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "any value")) + - it diagnoses wrong argument types: + expect (f ("head", false)).to_error (msg (2, "List or nil", "boolean")) + - it returns a list object: - l = l:cons "quux" - expect (prototype (l)).to_be "List" + expect (prototype (f ("head", l))).to_be "List" + - it prepends an item to a list: + expect (f ("head", l)). + to_equal (List {"head", "foo", "bar", "baz"}) - it works for empty lists: l = List {} - expect (l:cons "quux").to_equal (List {"quux"}) + expect (f ("head", l)).to_equal (List {"head"}) + - it supports single argument call: + expect (f "head").to_equal (List {"head"}) + + - context as a list object method: + - before: + msg = bind (badarg, {this_module, fname}) + f = l[fname] + + - it returns a list object: + expect (prototype (f (l, "head"))).to_be "List" - it prepends an item to a list: - expect (l:cons "quux"). - to_equal (List {"quux", "foo", "bar", "baz"}) + expect (f (l, "head")). + to_equal (List {"head", "foo", "bar", "baz"}) + - it works for empty lists: + l = List {} + expect (f (l, "head")).to_equal (List {"head"}) - describe depair: From 35640d6c7566b700ecf09cdf047c940105d7ba82 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 11:32:21 +0100 Subject: [PATCH 325/703] base: propagate environments through export argcheck wrappers. * lib/std/base.lua (debug): Rename from this... (debug_init): ...to this. (_ARGCHECK, _DEBUG): Adjust accordingly. (getfenv, setfenv): Compatibility functions for Lua 5.2. (export): When returning argchecking wrapper function, propagate the wrapper's function environment to the inner function. * specs/base_spec.yaml (export): Adjust mkmagic not to rely on out-of-scope MAGIC table. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 45 +++++++++++++++++++++++++++++++++++++++++--- specs/base_spec.yaml | 17 ++++++++--------- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 59092ac..2310c2f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -358,10 +358,10 @@ end --[[ ================== ]]-- -local debug = require "std.debug_init" +local debug_init = require "std.debug_init" -local _ARGCHECK = debug._ARGCHECK -local _DEBUG = debug._DEBUG +local _ARGCHECK = debug_init._ARGCHECK +local _DEBUG = debug_init._DEBUG local argcheck, argerror, argscheck -- forward declarations @@ -681,6 +681,42 @@ end --[[ ============ ]]-- +-- Lua 5.1 requires 'debug.setfenv' to change environment of C funcs; +-- Lua 5.2 implementation below works on C or Lua funcs unchanged. +-- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html +local setfenv = debug.setfenv or function(f, t) + -- Unwrap functable: + if type (f) == "table" then + f = f.call or (getmetatable (f) or {}).__call + end + + local name + local up = 0 + repeat + up = up + 1 + name = debug.getupvalue (f, up) + until name == '_ENV' or name == nil + if name then + debug.upvaluejoin (f, up, function () return name end, 1) + debug.setupvalue (f, up, t) + end + return f +end + + +-- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html +local getfenv = getfenv or function(f) + f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) + local name, val + local up = 0 + repeat + up = up + 1 + name, val = debug.getupvalue(f, up) + until name == '_ENV' or name == nil + return val +end + + --- Export a function definition, optionally with argument type checking. -- In addition to checking that each argument type matches the corresponding -- element in the *types* table with `argcheck`, if the final element of @@ -786,6 +822,9 @@ local function export (M, decl, fn, ...) error (string.format (toomanyarg_fmt, name, max, argc), 2) end + -- Propagate outer environment to inner function. + setfenv (inner, getfenv (1)) + return inner (...) end end diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 08efbec..cc50d76 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -112,8 +112,7 @@ specify std.base: ]], tostring (debugp), tostring (level)) end - MAGIC = { "unique!" } - mkmagic = function () return MAGIC end + mkmagic = function () return "MAGIC" end fname = "export" msg = function (...) return badarg (this_module, fname, ...) end @@ -147,7 +146,7 @@ specify std.base: - it stores a function in supplied table at the specified key: M = { "base_spec.yaml" } f (M, "export (any?)", mkmagic) - expect (M.export ()).to_be (MAGIC) + expect (M.export ()).to_be "MAGIC" - it stores the passed function when _ARGCHECK is disabled: | script = [[ _DEBUG = false @@ -174,7 +173,7 @@ specify std.base: expect (chk ({1}, 2, nop, "", false)). to_error (toomanyarg (fake_module, fname, 1, 5)) - it accepts correct argument types: - expect (chk ({1})).to_equal (MAGIC) -- should be .to_be? + expect (chk ({1})).to_be "MAGIC" - context when checking multi-argument function: - before: fake_module = "base_spec.yaml" @@ -193,7 +192,7 @@ specify std.base: expect (chk ({}, nop, false)). to_error (toomanyarg (fake_module, fname, 2, 3)) - it accepts correct argument types: - expect (chk ({}, nop)).to_equal (MAGIC) -- should be .to_be? + expect (chk ({}, nop)).to_be "MAGIC" - context when checking optional argument function: - before: fake_module = "base_spec.yaml" @@ -208,8 +207,8 @@ specify std.base: expect (chk (1, nop)). to_error (toomanyarg (fake_module, fname, 1, 2)) - it accepts correct argument types: - expect (chk ()).to_equal (MAGIC) -- should be .to_be? - expect (chk (1)).to_equal (MAGIC) -- should be .to_be? + expect (chk ()).to_be "MAGIC" + expect (chk (1)).to_be "MAGIC" - context when checking optional multi-argument function: - before: fake_module = "base_spec.yaml" @@ -227,8 +226,8 @@ specify std.base: expect (chk (1, "two", nop)). to_error (toomanyarg (fake_module, fname, 2, 3)) - it accepts correct argument types: - expect (chk ("two")).to_equal (MAGIC) -- should be .to_be? - expect (chk (1, "two")).to_equal (MAGIC) -- should be .to_be? + expect (chk ("two")).to_be "MAGIC" + expect (chk (1, "two")).to_be "MAGIC" - context when checking self on an object method: - before: fake_module = "base_spec.yaml" From 9d96f7f3e3227a85f186fffa6044905b533b56e4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 11:59:54 +0100 Subject: [PATCH 326/703] base: unwrap functables before calling debug.setenv on Lua 5.1. * lib/std/base.lua (setfenv): Unwrap functables unconditionally, before delegating to `debug.setfenv` or Lua 5.2 emulation. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 2310c2f..3da5f98 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -682,25 +682,33 @@ end -- Lua 5.1 requires 'debug.setfenv' to change environment of C funcs; --- Lua 5.2 implementation below works on C or Lua funcs unchanged. --- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html -local setfenv = debug.setfenv or function(f, t) +local _setfenv = debug.setfenv + + +local function setfenv (f, t) -- Unwrap functable: if type (f) == "table" then f = f.call or (getmetatable (f) or {}).__call end - local name - local up = 0 - repeat - up = up + 1 - name = debug.getupvalue (f, up) - until name == '_ENV' or name == nil - if name then - debug.upvaluejoin (f, up, function () return name end, 1) - debug.setupvalue (f, up, t) + if _setfenv then + return _setfenv (f, t) + + else + -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html + local name + local up = 0 + repeat + up = up + 1 + name = debug.getupvalue (f, up) + until name == '_ENV' or name == nil + if name then + debug.upvaluejoin (f, up, function () return name end, 1) + debug.setupvalue (f, up, t) + end + + return f end - return f end From 962090004f27583adc110caf52329b1d5ad2b6be Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 11:46:32 +0100 Subject: [PATCH 327/703] functional: report bind api deprecations correctly. * specs/functional.yaml (bind): Specify deprecation warning behaviour when called with the legacy multi-argument parameter passing. * lib/std/functional.lua (bind): Use new getcompat/setcompat internal apis to report deprecation of legacy calling convention. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 11 ++++++++--- specs/functional_spec.yaml | 12 ++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 2b2bcff..84d5fa8 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -25,12 +25,17 @@ local M = { "std.functional" } -- > cube = bind (std.lambda "^", {[2] = 3}) -- > =cube (2) -- 8 -local bind = export (M, "bind (func, any?*)", function (f, ...) +local bind; bind = export (M, "bind (func, any?*)", function (f, ...) local fix = {...} if type (fix[1]) == "table" and fix[2] == nil then - base.DEPRECATED ("39", "`functional.bind` multi-argument", - "use a table of arguments as the second parameter instead", nop) fix = fix[1] + else + if not base.getcompat (bind) then + io.stderr:write (base.DEPRECATIONMSG ("39", + "multi-argument 'std.functional.bind'", + "use a table of arguments as the second parameter instead", 2)) + base.setcompat (bind) + end end return function (...) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 538f3da..4e64f1e 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -29,6 +29,18 @@ specify std.functional: expect (f (false)). to_error (badarg (this_module, fname, 1, "function", "boolean")) + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {badarg, this_module, fname}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "multi-argument 'std.functional.bind' was deprecated" + end + _, err = capture (f, {badarg, this_module, fname}) + expect (err).to_be (nil) + - it does not affect normal operation if no arguments are bound: expect (f (math.min, {}) (2, 3, 4)).to_be (2) - it takes the extra arguments into account: From 76ecd784d380840b3f9df1844b292d7dfb116f10 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 12:17:21 +0100 Subject: [PATCH 328/703] maint: use fully qualified api names in all deprecation messages. * specs/list_spec.yaml (elems, index_key, index_value, relems) (reverse, :depair, :map_with, :transpose, :zip_with): Specify fully qualified api name in deprecation message. * specs/string_spec.yaml (assert, require_version, tostring): Likewise. * specs/table_spec.yaml (clone_rename, metamethod, ripairs): Likewise. * lib/std/list.lua (elems, index_key, index_value, relems) (reverse, :depair, :map_with, :transpose, :zip_with): Add `std.` prefix to deprecation messages. * lib/std/string.lua (assert, require_version, tostring): Likewise. * lib/table.lua (clone_rename, metamethod, ripairs): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 34 +++++++++++++++++----------------- lib/std/string.lua | 6 +++--- lib/std/table.lua | 8 ++++---- specs/list_spec.yaml | 28 ++++++++++++++-------------- specs/string_spec.yaml | 6 +++--- specs/table_spec.yaml | 6 +++--- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index ff492f0..980ec39 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -487,14 +487,14 @@ local _functions = { local DEPRECATED = base.DEPRECATED -_functions.elems = DEPRECATED ("41", "'list.elems'", +_functions.elems = DEPRECATED ("41", "'std.list.elems'", "use 'std.ielems' instead", base.ielems) local function relems (l) return base.ielems (base.ireverse (l)) end -_functions.relems = DEPRECATED ("41", "'list.relems'", - "use 'std.ielems' with 'std.ireverse' instead", relems) +_functions.relems = DEPRECATED ("41", "'std.list.relems'", + "compose 'std.ielems' and 'std.ireverse' instead", relems) local function index_key (f, l) @@ -508,8 +508,8 @@ local function index_key (f, l) return r end -_functions.index_key = DEPRECATED ("41", "'list.index_key'", - "use 'list.filter' with 'table.invert' instead", index_key) +_functions.index_key = DEPRECATED ("41", "'std.list.index_key'", + "compose 'std.list.filter' and 'std.table.invert' instead", index_key) local function index_value (f, l) @@ -523,13 +523,13 @@ local function index_value (f, l) return r end -_functions.index_value = DEPRECATED ("41", "'list.index_value'", - "use 'list.filter' with 'table.invert' instead", index_value) +_functions.index_value = DEPRECATED ("41", "'std.list.index_value'", + "compose 'std.list.filter' and 'std.table.invert' instead", index_value) local function reverse (l) return List (ireverse (l)) end -_functions.reverse = DEPRECATED ("41", "'list.reverse'", +_functions.reverse = DEPRECATED ("41", "'std.list.reverse'", "use 'std.ireverse' instead", reverse) @@ -688,19 +688,19 @@ List = Object { tail = tail, ------ - depair = DEPRECATED ("38", "'list:depair'", depair), - map_with = DEPRECATED ("38", "'list:map_with'", + depair = DEPRECATED ("38", "'std.list:depair'", depair), + map_with = DEPRECATED ("38", "'std.list:map_with'", function (self, f) return map_with (f, self) end), - transpose = DEPRECATED ("38", "'list:transpose'", transpose), - zip_with = DEPRECATED ("38", "'list:zip_with'", zip_with), + transpose = DEPRECATED ("38", "'std.list:transpose'", transpose), + zip_with = DEPRECATED ("38", "'std.list:zip_with'", zip_with), - elems = DEPRECATED ("41", "'list:elems'", base.ielems), - index_key = DEPRECATED ("41", "'list:index_key'", + elems = DEPRECATED ("41", "'std.list:elems'", base.ielems), + index_key = DEPRECATED ("41", "'std.list:index_key'", function (self, f) return index_key (f, self) end), - index_value = DEPRECATED ("41", "'list:index_value'", + index_value = DEPRECATED ("41", "'std.list:index_value'", function (self, f) return index_value (f, self) end), - relems = DEPRECATED ("41", "'list:relems'", relems), - reverse = DEPRECATED ("41", "'list:reverse'", reverse), + relems = DEPRECATED ("41", "'std.list:relems'", relems), + reverse = DEPRECATED ("41", "'std.list:reverse'", reverse), }, diff --git a/lib/std/string.lua b/lib/std/string.lua index b644f28..99944bd 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -554,15 +554,15 @@ end local DEPRECATED = base.DEPRECATED -M.assert = DEPRECATED ("41", "'string.assert'", +M.assert = DEPRECATED ("41", "'std.string.assert'", "use 'std.assert' instead", base.assert) -M.require_version = DEPRECATED ("41", "'string.require_version'", +M.require_version = DEPRECATED ("41", "'std.string.require_version'", "use 'std.require' instead", base.require) -M.tostring = DEPRECATED ("41", "'string.tostring'", +M.tostring = DEPRECATED ("41", "'std.string.tostring'", "use 'std.tostring' instead", base.tostring) diff --git a/lib/std/table.lua b/lib/std/table.lua index 9737283..7f31ddf 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -288,8 +288,8 @@ end) local DEPRECATED = base.DEPRECATED -M.clone_rename = DEPRECATED ("39", "'table.clone_rename'", - "use the new `map` argument to 'table.clone' instead", +M.clone_rename = DEPRECATED ("39", "'std.table.clone_rename'", + "use the new `map` argument to 'std.table.clone' instead", function (map, t) local r = clone (t) for i, v in pairs (map) do @@ -300,11 +300,11 @@ M.clone_rename = DEPRECATED ("39", "'table.clone_rename'", end) -M.metamethod = DEPRECATED ("41", "'table.metamethod'", +M.metamethod = DEPRECATED ("41", "'std.table.metamethod'", "use 'std.getmetamethod' instead", base.getmetamethod) -M.ripairs = DEPRECATED ("41", "'table.ripairs'", +M.ripairs = DEPRECATED ("41", "'std.table.ripairs'", "use 'std.ripairs' instead", base.ripairs) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 23b4490..7bce7c8 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -298,7 +298,7 @@ specify std.list: _, err = capture (f, {l}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:depair' was deprecated" + expect (err).to_contain "'std.list:depair' was deprecated" end expect (select (2, capture (f, {l}))).to_be (nil) @@ -326,7 +326,7 @@ specify std.list: _, err = capture (f, {{}}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list.elems' was deprecated" + expect (err).to_contain "'std.list.elems' was deprecated" end expect (select (2, capture (f, {{}}))).to_be (nil) @@ -347,7 +347,7 @@ specify std.list: _, err = capture (f, {l}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:elems' was deprecated" + expect (err).to_contain "'std.list:elems' was deprecated" end _, err = capture (f, {l}) expect (err).to_be (nil) @@ -504,7 +504,7 @@ specify std.list: _, err = capture (f, {1, List {{1}}}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list.index_key' was deprecated" + expect (err).to_contain "'std.list.index_key' was deprecated" end _, err = capture (f, {1, List {{1}}}) expect (err).to_be (nil) @@ -536,7 +536,7 @@ specify std.list: _, err = capture (f, {l, 1}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:index_key' was deprecated" + expect (err).to_contain "'std.list:index_key' was deprecated" end _, err = capture (f, {l, 1}) expect (err).to_be (nil) @@ -576,7 +576,7 @@ specify std.list: _, err = capture (f, {1, List {{1}}}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list.index_value' was deprecated" + expect (err).to_contain "'std.list.index_value' was deprecated" end _, err = capture (f, {1, List {{1}}}) expect (err).to_be (nil) @@ -609,7 +609,7 @@ specify std.list: _, err = capture (f, {l, 1}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:index_value' was deprecated" + expect (err).to_contain "'std.list:index_value' was deprecated" end _, err = capture (f, {l, 1}) expect (err).to_be (nil) @@ -714,7 +714,7 @@ specify std.list: _, err = capture (f, {l, fn}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:map_with' was deprecated" + expect (err).to_contain "'std.list:map_with' was deprecated" end _, err = capture (f, {l, fn}) expect (err).to_be (nil) @@ -785,7 +785,7 @@ specify std.list: _, err = capture (f, {{}}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list.relems' was deprecated" + expect (err).to_contain "'std.list.relems' was deprecated" end _, err = capture (f, {{}}) expect (err).to_be (nil) @@ -807,7 +807,7 @@ specify std.list: _, err = capture (f, {l}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:relems' was deprecated" + expect (err).to_contain "'std.list:relems' was deprecated" end _, err = capture (f, {l}) expect (err).to_be (nil) @@ -867,7 +867,7 @@ specify std.list: _, err = capture (f, {{}}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list.reverse' was deprecated" + expect (err).to_contain "'std.list.reverse' was deprecated" end _, err = capture (f, {{}}) expect (err).to_be (nil) @@ -892,7 +892,7 @@ specify std.list: _, err = capture (f, {l}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:reverse' was deprecated" + expect (err).to_contain "'std.list:reverse' was deprecated" end _, err = capture (f, {l}) expect (err).to_be (nil) @@ -1037,7 +1037,7 @@ specify std.list: _, err = capture (f, {l}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:transpose' was deprecated" + expect (err).to_contain "'std.list:transpose' was deprecated" end _, err = capture (f, {l}) expect (err).to_be (nil) @@ -1101,7 +1101,7 @@ specify std.list: _, err = capture (f, {l, fn}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'list:zip_with' was deprecated" + expect (err).to_contain "'std.list:zip_with' was deprecated" end _, err = capture (f, {l, fn}) expect (err).to_be (nil) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 5d3adb3..69f402c 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -67,7 +67,7 @@ specify std.string: _, err = capture (f, {"std.string"}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'string.assert' was deprecated" + expect (err).to_contain "'std.string.assert' was deprecated" end _, err = capture (f, {"std.string"}) expect (err).to_be (nil) @@ -592,7 +592,7 @@ specify std.string: _, err = capture (f, {"std.string"}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'string.require_version' was deprecated" + expect (err).to_contain "'std.string.require_version' was deprecated" end _, err = capture (f, {"std.string"}) expect (err).to_be (nil) @@ -780,7 +780,7 @@ specify std.string: _, err = capture (f, {"std.string"}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'string.tostring' was deprecated" + expect (err).to_contain "'std.string.tostring' was deprecated" end _, err = capture (f, {"std.string"}) expect (err).to_be (nil) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 7a5fd6c..bff934d 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -92,7 +92,7 @@ specify std.table: _, err = capture (f, {{}, subject}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'table.clone_rename' was deprecated" + expect (err).to_contain "'std.table.clone_rename' was deprecated" end _, err = capture (f, {{}, subject}) expect (err).to_be (nil) @@ -362,7 +362,7 @@ specify std.table: _, err = capture (f, {{}, subject}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'table.metamethod' was deprecated" + expect (err).to_contain "'std.table.metamethod' was deprecated" end _, err = capture (f, {{}, subject}) expect (err).to_be (nil) @@ -450,7 +450,7 @@ specify std.table: _, err = capture (f, {{}, subject}) if err ~= nil then -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'table.ripairs' was deprecated" + expect (err).to_contain "'std.table.ripairs' was deprecated" end _, err = capture (f, {{}, subject}) expect (err).to_be (nil) From bdb47bf8a92a92c22fa889232c89813189c0dccd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 12:30:21 +0100 Subject: [PATCH 329/703] maint: Update AUTHORS file. * AUTHORS: Update. Signed-off-by: Gary V. Vaughan --- AUTHORS | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0cbc134..9a36600 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,9 +6,13 @@ be on it, please tell the mailing list (see README for the address). 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 and maintains the standard libraries project, -wrote many of the libraries, and integrated code from other authors. +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. @@ -21,8 +25,3 @@ private standard library. Johann Hibschman supplied the code on which math.floor and math.round were based. - -Diego Nehab wrote the original version of the mbox parser module. - -Gary V. Vaughan contributed table key support to the tree module, and -the memoize implementation. From ec0668ade68d78215f4265799b99c6fe9615c931 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 12:31:05 +0100 Subject: [PATCH 330/703] maint: bump copyright years. * README.md: Bump copyright years to include 2014. Signed-off-by: Gary V. Vaughan --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 654d4a7..da259ce 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ by the [stdlib project][github] This is a collection of Lua libraries for Lua 5.1 and 5.2. The -libraries are copyright by their authors 2000-2013 (see the AUTHORS +libraries are copyright by their authors 2000-2014 (see the AUTHORS file for details), and released under the MIT license (the same license as Lua itself). There is no warranty. From 850d4d44891f69c317c598b9915159b608754869 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 13:20:47 +0100 Subject: [PATCH 331/703] functional: collect creates tables from multi-return iterators. * specs/functional_spec.yaml (collect): Differentiate behaviours of calling collect with single return versus multiple return iterators. * lib/std/functional.lua (collect): Inject new elements into the collected values table using key:value pairs when the iterator returns more than one value. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 21 +++++++++++++-------- lib/std/functional.lua | 11 ++++++----- specs/functional_spec.yaml | 9 +++++++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index d52ed9f..d7c8fcd 100644 --- a/NEWS +++ b/NEWS @@ -137,6 +137,19 @@ Stdlib NEWS - User visible changes need to remove the previously ignored arguments that correspond to the fixed argument positions in the `bind` invocation. + - `functional.collect` still makes a list from the results from an + iterator that returns single values, but when an iterator returns + multiple values it now makes 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`. @@ -155,14 +168,6 @@ Stdlib NEWS - User visible changes passing a non-string will now raise an error as specified in the api documentation. - - 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. - ** Bug fixes: diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 84d5fa8..df42715 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -61,14 +61,15 @@ end) -- @see filter -- @see map -- @usage --- > =collect (std.list.relems, List {"a", "b", "c"}) +-- > =collect (compose (std.ireverse, std.ielems), {"a", "b", "c"}) -- {"c", "b", "a"} export (M, "collect (func, any*)", function (i, ...) - local t = {} - for e in i (...) do - t[#t + 1] = e + local r = {} + for k, v in i (...) do + if v == nil then k, v = #r + 1, k end + r[k] = v end - return t + return r end) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 4e64f1e..7402b05 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -60,6 +60,8 @@ specify std.functional: - describe collect: - before: + base = require "std.base" + fname = "collect" msg = M.bind (badarg, {this_module, fname}) f = M[fname] @@ -69,8 +71,11 @@ specify std.functional: - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "function", "boolean")) - - it collects iterator results: - expect (f (ipairs, {"a", "b", "c"})).to_equal {1, 2, 3} + - it collects a list of single return value iterator results: + expect (f (base.ielems, {"a", "b", "c"})).to_equal {"a", "b", "c"} + - it collects a table of key:value iterator results: + t = {"first", second="two", last=3} + expect (f (pairs, t)).to_equal (t) - describe compose: From ec9cbf7c073d8b6b1165501f5a1b2960c13fbda9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 15:11:30 +0100 Subject: [PATCH 332/703] functional: filter supports multi-parameter predicates. * specs/functional_spec.yaml (filter): Specify behaviours when called with an iterator that returns multiple values, and predicate that accepts multiple parameters. * lib/std/functional.lua (filter): Collect all results from iterator function, and propagate them all to the predicate call. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 ++++---- lib/std/functional.lua | 21 +++++++++++++++------ specs/functional_spec.yaml | 31 ++++++++++++++++++------------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index d7c8fcd..a48911f 100644 --- a/NEWS +++ b/NEWS @@ -137,10 +137,10 @@ Stdlib NEWS - User visible changes need to remove the previously ignored arguments that correspond to the fixed argument positions in the `bind` invocation. - - `functional.collect` still makes a list from the results from an - iterator that returns single values, but when an iterator returns - multiple values it now makes a table with key:value pairs taken from - the first two returned values of each iteration. + - `functional.collect` and `functional.filter` still makes a list from + the results from an iterator that returns single values, but when an + iterator returns multiple values it now makes 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 diff --git a/lib/std/functional.lua b/lib/std/functional.lua index df42715..7f44d00 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -136,16 +136,25 @@ end) -- @treturn table elements e for which `p (e)` is not falsey. -- @see collect -- @usage --- > filter (std.lambda "|e| e%2==0", std.elems, {1, 2, 3, 4}) +-- > filter (std.lambda "|e|e%2==0", std.elems, {1, 2, 3, 4}) -- {2, 4} export (M, "filter (func, func, any*)", function (p, i, ...) - local t = {} - for e in i (...) do - if p (e) then - table.insert (t, e) + local r = {} -- new results table + local fn, state, k = i (...) + local t = {fn (state, k)} -- table of iteration 1 + + while t[1] ~= nil do -- until iterator returns nil + k = t[1] + if p (unpack (t)) then -- pass all iterator results to p + if t[2] ~= nil then + r[k] = t[2] -- k,v = t[1],t[2] + else + r[#r + 1] = k -- k,v = #r + 1,t[1] + end end + t = {fn (state, k)} -- maintain loop invariant end - return t + return r end) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 7402b05..9c253c4 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -1,4 +1,6 @@ before: | + base = require "std.base" + this_module = "std.functional" global_table = "_G" @@ -60,8 +62,6 @@ specify std.functional: - describe collect: - before: - base = require "std.base" - fname = "collect" msg = M.bind (badarg, {this_module, fname}) f = M[fname] @@ -142,23 +142,28 @@ specify std.functional: expect (f (false)).to_error (msg (1, "function", "boolean")) expect (f (f, false)).to_error (msg (2, "function", "boolean")) + - it works with an empty table: + expect (f (M.id, pairs, {})).to_equal {} - it iterates through element keys: - expect (f (M.id, ipairs, elements)).to_equal {1, 2, 3, 4, 5} - expect (f (M.id, pairs, inverse)).to_contain.a_permutation_of (elements) - - it passes each iterated element to filter function: + expect (f (M.id, base.ielems, elements)).to_equal {"a", "b", "c", "d", "e"} + expect (f (M.id, base.elems, inverse)).to_contain.a_permutation_of {1, 2, 3, 4, 5} + - it passes all iteration result values to filter predicate: t = {} - f (function (e) t[#t + 1] = e end, pairs, inverse) - expect (t).to_contain.a_permutation_of (elements) - - it returns a table of filtered keys: - expect (f (function (e) return e % 2 == 0 end, ipairs, elements)). - to_equal {2, 4} - expect (f (function (e) return e:match "[aeiou]" end, pairs, inverse)). - to_contain.a_permutation_of {"a", "e"} + f (function (k, v) t[k] = v end, pairs, elements) + expect (t).to_equal (elements) + - it returns a list of filtered single return value iterator results: + expect (f (function (e) return e:match "[aeiou]" end, base.ielems, elements)). + to_equal {"a", "e"} + - it returns a table of filtered key:value iterator results: + t = {"first", second=2, last="three"} + expect (f (function (k, v) return type (v) == "string" end, pairs, t)). + to_equal {"first", last="three"} + expect (f (function (k, v) return k % 2 == 0 end, ipairs, elements)). + to_equal {[2]="b", [4]="d"} - describe fold: - before: - base = require "std.base" fname = "fold" msg = M.bind (badarg, {this_module, fname}) f = M[fname] From d1a3adea5387ea57a0b38b45ecd0523984ac1bff Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 15:21:10 +0100 Subject: [PATCH 333/703] doc: add LDoc Type section for callback function signatures. * lib/std.lua.in (normalizecb): Document signature of memoize callback function. (memoize): Set type of *normalize* argument to new `normalizecb` signature. * lib/std/functional.lua (predicate): Document signature for a predicate function. (filter): Set type of *p* argument to new `predicate` signature. * lib/std/io.lua (fileprocessor): Document signature of process_files callback function. (process_files): Set type of *fn* to new `fileprocessor` function. * lib/std/string.lua (opentablecb, closetablecb, elementcb) (paircb, separatorcb): Document signature for render callback functions. (render): Set type of callback functions accordingly. * lib/std/table.lua (comparator): Document signature of sort comparison function. (sort): Set type of *c* argument to new `comparator` signature. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 24 ++++++--- lib/std/functional.lua | 15 +++++- lib/std/io.lua | 30 ++++++----- lib/std/string.lua | 115 +++++++++++++++++++++-------------------- lib/std/table.lua | 17 +++++- 5 files changed, 123 insertions(+), 78 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index a96f0e2..3f2ff37 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -172,20 +172,14 @@ export (M, "lambda (string)", base.memoize (base.lambda, base.lambda "|s|s")) -- by default). You can specify a more sophisticated function if memoize -- should handle complicated argument equivalencies. -- @function memoize --- @func fn function with no side effects --- @func normalize[opt] function to normalize arguments +-- @func fn pure function: a function with no side effects +-- @tparam[opt] normalizecb normalize function to normalize arguments -- @treturn functable memoized function -- @usage -- local fast = memoize (function (...) --[[ slow code ]] end) export (M, "memoize (func, func?)", base.memoize) ---- Signature of `memoize` *normalize* parameter. --- @function memoize_normalize --- @param ... arguments --- @treturn string normalized arguments - - --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. -- @function pairs -- @tparam table t a table @@ -329,3 +323,17 @@ return setmetatable (M, { end end, }) + + + +--- Types +-- @section Types + +--- Signature of a @{memoize} argument normalization callback function. +-- @function normalizecb +-- @param ... arguments +-- @treturn string normalized arguments +-- @see memoize +-- @usage +-- local normalizecb = function (name, value, props) return name end +-- local intern = std.memoize (mksymbol, normalizecb) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 7f44d00..6da6df8 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -15,7 +15,6 @@ local export, nop, pairs = base.export, base.nop, base.pairs local M = { "std.functional" } - --- Partially apply a function. -- @function bind -- @func f function to apply partially @@ -130,7 +129,7 @@ end) --- Filter an iterator with a predicate. -- @function filter --- @func p predicate +-- @tparam predicate p predicate function -- @func i iterator -- @param ... iterator arguments -- @treturn table elements e for which `p (e)` is not falsey. @@ -224,3 +223,15 @@ M.op = require "std.operator" return M + +--- Types +-- @section Types + +--- Signature of a @{filter} predicate function. +-- @function predicate +-- @param ... arguments +-- @treturn boolean `true` if the predicate condition succeeds +-- @see filter +-- @usage +-- local predicate = std.lambda '|k,v|type(v)=="string"' +-- local strvalues = filter (predicate, std.pairs, {name="Roberto", id=12345}) diff --git a/lib/std/io.lua b/lib/std/io.lua index 951d2e7..fdd3a7e 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -172,14 +172,6 @@ export (M, "shell (string)", function (c) end) ------- --- Signature of @{process_files} callback function. --- @function process_files_callback --- @string[opt] filename filename --- @int[opt] i argument number of *filename* --- @see process_files - - --- 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 @@ -187,10 +179,9 @@ end) -- filenames were given, behave as if a single `-` was passed. -- @todo Make the file list an argument to the function. -- @function process_files --- @tparam function fn @{process_files_callback} function for each file --- argument --- @see process_files_callback --- @usage #! /usr/bin/env lua +-- @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) @@ -271,3 +262,18 @@ for k, v in pairs (io) do end return 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/lib/std/string.lua b/lib/std/string.lua index 99944bd..62a4abf 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -355,9 +355,6 @@ export (M, "trim (string, string?)", function (s, r) end) ---- Stringification Functions --- @section Stringification - -- Write pretty-printing based on: -- -- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators @@ -388,11 +385,11 @@ end) -- detection will not work. -- @function render -- @param x object to convert to string --- @tparam render_open_table open open table rendering function --- @tparam render_close_table close close table rendering function --- @tparam render_element elem element rendering function --- @tparam render_pair pair pair rendering function --- @tparam render_separator sep separator rendering function +-- @tparam opentablecb open open table rendering function +-- @tparam closetablecb close close table rendering function +-- @tparam elementcb elem element rendering function +-- @tparam paircb pair pair rendering function +-- @tparam separatorcb sep separator rendering function -- @tparam[opt] table roots accumulates table references to detect recursion -- @return string representation of *x* -- @usage @@ -405,53 +402,6 @@ local render = export (M, "render (any?, func, func, func, func, func, table?)", base.render) ---- Signature of render open table callback. --- @function render_open_table --- @tparam table t table about to be rendered --- @treturn string open table rendering --- @usage function open (t) return "{" end - - ---- Signature of render close table callback. --- @function render_close_table --- @tparam table t table just rendered --- @treturn string close table rendering --- @usage function close (t) return "}" end - - ---- Signature of render element callback. --- @function render_element --- @param x element to render --- @treturn string element rendering --- @usage function element (e) return require "std".tostring (e) end - - ---- Signature of render pair callback. --- Trying to re-render *key* or *value* here will break recursion --- detection, use *strkey* and *strvalue* pre-rendered values instead. --- @function render_pair --- @tparam table t table containing pair being rendered --- @param key key part of key being rendered --- @param value value part of key being rendered --- @string keystr prerendered *key* --- @string valuestr prerendered *value* --- @treturn string pair rendering --- @usage --- function pair (_, _, _, key, value) return key .. "=" .. value end - - ---- Signature of render separator callback. --- @function render_separator --- @tparam table t table currently being rendered --- @param pk *t* key preceding separator, or `nil` for first key --- @param pv *t* value preceding separator, or `nil` for first value --- @param fk *t* key following separator, or `nil` for last key --- @param fv *t* value following separator, or `nil` for last value --- @treturn string separator rendering --- @usage --- function separator (_, _, _, fk) return fk and "," or "" end - - --- Pretty-print a table, or other object. -- @function prettytostring -- @param x object to convert to string @@ -573,3 +523,58 @@ for k, v in pairs (string) do end return M + + + +--- Types +-- @section Types + +--- Signature of @{render} open table callback. +-- @function opentablecb +-- @tparam table t table about to be rendered +-- @treturn string open table rendering +-- @see render +-- @usage function open (t) return "{" end + + +--- Signature of @{render} close table callback. +-- @function closetablecb +-- @tparam table t table just rendered +-- @treturn string close table rendering +-- @see render +-- @usage function close (t) return "}" end + + +--- Signature of @{render} element callback. +-- @function elementcb +-- @param x element to render +-- @treturn string element rendering +-- @see render +-- @usage function element (e) return require "std".tostring (e) end + + +--- Signature of @{render} pair callback. +-- Trying to re-render *key* or *value* here will break recursion +-- detection, use *strkey* and *strvalue* pre-rendered values instead. +-- @function paircb +-- @tparam table t table containing pair being rendered +-- @param key key part of key being rendered +-- @param value value part of key being rendered +-- @string keystr prerendered *key* +-- @string valuestr prerendered *value* +-- @treturn string pair rendering +-- @see render +-- @usage +-- function pair (_, _, _, key, value) return key .. "=" .. value end + + +--- Signature of @{render} separator callback. +-- @function separatorcb +-- @tparam table t table currently being rendered +-- @param pk *t* key preceding separator, or `nil` for first key +-- @param pv *t* value preceding separator, or `nil` for first value +-- @param fk *t* key following separator, or `nil` for last key +-- @param fv *t* value following separator, or `nil` for last value +-- @treturn string separator rendering +-- @usage +-- function separator (_, _, _, fk) return fk and "," or "" end diff --git a/lib/std/table.lua b/lib/std/table.lua index 7f31ddf..44b0ba0 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -222,7 +222,7 @@ local _sort = table.sort --- Make table.sort return its result. -- @function sort -- @tparam table t unsorted table --- @func[opt] c comparator function if passed, otherwise standard +-- @tparam[opt=std.operator["<"]] comparator c ordering function callback -- lua `<` operator -- @return *t* with keys sorted accordind to *c* -- @usage table.concat (sort (object)) @@ -314,3 +314,18 @@ for k, v in pairs (table) do end return 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) From 8e2211f61529ad4f0ab6de714cc0f32dc0bcd51c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 18:06:43 +0100 Subject: [PATCH 334/703] functional: fold supports multi-return iterators. * specs/functional_spec.yaml (fold): Specify behaviour with iterators that return multiple values. * lib/std/functional.lua (fold): Collect all values returned by iterator and operate on the last one of those. * specs/container_spec.yaml (construction): Fold dereferences automatically, no need to manually dereference any more. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/functional.lua | 12 +++++++++--- specs/container_spec.yaml | 2 +- specs/functional_spec.yaml | 10 +++++++--- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index a48911f..9b1429c 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,9 @@ Stdlib NEWS - User visible changes - New `functional.nop` function, for use where a function is required but no work should be done. + - `functional.collect`, `functional.filter` and `functional.fold` now + work with standard multi-return iterators, such as `std.ipairs`. + - `std` module now collects stdlib functions that do not really belong in specific type modules: including `std.case`, `std.eval`, and `std.memoize` (original access points exported by earlier releases will diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 6da6df8..7015e63 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -166,11 +166,17 @@ end) -- @return result -- @see std.list.foldl -- @see std.list.foldr --- @usage fold (std.lambda "^", 1, std.elems, {2, 3, 4}) +-- @usage +-- --> 2 ^ 3 ^ 4 ==> 4096 +-- fold (std.lambda "^", 2, std.ipairs, {3, 4}) export (M, "fold (func, any, func, any*)", function (f, d, i, ...) + local fn, state, k = i (...) + local t = {fn (state, k)} + local r = d - for e in i (...) do - r = f (r, e) + while t[1] ~= nil do + r = f (r, t[#t]) + t = {fn (state, t[1])} end return r end) diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 03e06ca..5c0bc41 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -53,7 +53,7 @@ specify std.container: fold = require "std.functional".fold functions = { count = function (bag) - return fold (function (r, k) return r + bag[k] end, 0, pairs, bag) + return fold (function (r, k) return r + k end, 0, pairs, bag) end, } Bag = Container { diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 9c253c4..02efbd6 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -176,14 +176,18 @@ specify std.functional: expect (f (false)).to_error (msg (1, "function", "boolean")) expect (f (f, 1, false)).to_error (msg (3, "function", "boolean")) - - it calls a binary function over element keys: + - it works with an empty table: + expect (f (M.op["+"], 2, ipairs, {})).to_be (2) + - it calls a binary function over single return value iterator results: expect (f (M.op["+"], 2, base.ielems, {3})). to_be (2 + 3) expect (f (M.op["*"], 2, base.ielems, {3, 4})). to_be (2 * 3 * 4) + - it calls a binary function over key:value iterator results: + expect (f (M.op["+"], 2, ipairs, {3})).to_be (2 + 3) + expect (f (M.op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) - it folds elements from left to right: - expect (f (math.pow, 2, base.ielems, {3, 4})). - to_be (math.pow (math.pow (2, 3), 4)) + expect (f (M.op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) - describe id: From 06cdd5f84ef3e3d76f291e80ec6de3a44948f6f3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 18:47:22 +0100 Subject: [PATCH 335/703] functional: map supports key:value remapping functions. * specs/functional_spec.yaml (map): Specify behaviour of passing all iteration return values to mapping function; and remapping when mapping function returns a key:value pair. * lib/std/functional.lua (map): Collect all iteration return values and propagate them to the mapping callback function. If there are two values returned from the callback, treat them as a key and value for setting in the results table. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 12 ++++++++---- lib/std/functional.lua | 18 ++++++++++++------ specs/functional_spec.yaml | 29 ++++++++++++++++++++--------- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index 9b1429c..32854ef 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,9 @@ Stdlib NEWS - User visible changes - `functional.collect`, `functional.filter` and `functional.fold` now work with standard multi-return iterators, such as `std.ipairs`. + - `functional.map` now supports a mapping function that returns a key, + value pair for the results table. + - `std` module now collects stdlib functions that do not really belong in specific type modules: including `std.case`, `std.eval`, and `std.memoize` (original access points exported by earlier releases will @@ -140,10 +143,11 @@ Stdlib NEWS - User visible changes need to remove the previously ignored arguments that correspond to the fixed argument positions in the `bind` invocation. - - `functional.collect` and `functional.filter` still makes a list from - the results from an iterator that returns single values, but when an - iterator returns multiple values it now makes a table with key:value - pairs taken from the first two returned values of each iteration. + - `functional.collect`, `functional.filter` and `functional.map` still + makes a list from the results from an iterator that returns single + values, but when an iterator returns multiple values it now makes 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 diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 7015e63..ac6fd51 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -202,14 +202,20 @@ end -- > map (function (e) return e % 2 end, std.elems, {1, 2, 3, 4}) -- {1, 0, 1, 0} export (M, "map (func, func, any*)", function (f, i, ...) - local t = {} - for e in i (...) do - local r = f (e) - if r ~= nil then - table.insert (t, r) + local fn, state, k = i (...) + local t = {fn (state, k)} + + local r = {} + while t[1] ~= nil do + k = t[1] + local d, v = f (unpack (t)) + if v == nil then d, v = #r + 1, d end + if v ~= nil then + r[d] = v end + t = {fn (state, k)} end - return t + return r end) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 02efbd6..bbfc2b0 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -215,18 +215,29 @@ specify std.functional: expect (f (false)).to_error (msg (1, "function", "boolean")) expect (f (f, false)).to_error (msg (2, "function", "boolean")) - - it iterates through element keys: - expect (f (M.id, ipairs, elements)).to_equal {1, 2, 3, 4, 5} + - it works with an empty table: + expect (f (M.id, ipairs, {})).to_equal {} + - it iterates through elements: + expect (f (M.id, ipairs, elements)).to_equal (elements) expect (f (M.id, pairs, inverse)).to_contain.a_permutation_of (elements) - - it passes each iterated element to map function: + - it passes all iteration result values to map function: t = {} - f (function (e) t[#t + 1] = e end, pairs, inverse) - expect (t).to_contain.a_permutation_of (elements) - - it returns a table of mapped keys: - expect (f (function (e) return e % 2 end, ipairs, elements)). - to_equal {1, 0, 1, 0, 1} - expect (f (function (e) return e .. "x" end, pairs, inverse)). + f (function (k, v) t[k] = v end, pairs, elements) + expect (t).to_equal (elements) + - it returns a list of mapped single return value iterator results: + expect (f (function (e) return e:match "[aeiou]" end, base.ielems, elements)). + to_equal {"a", "e"} + expect (f (function (e) return e .. "x" end, base.elems, elements)). to_contain.a_permutation_of {"ax", "bx", "cx", "dx", "ex"} + - it returns a table of mapped key:value iterator results: + t = {"first", second=2, last="three"} + expect (f (function (k, v) return type (v) == "string" end, pairs, t)). + to_contain.a_permutation_of {true, false, true} + expect (f (function (k, v) return k % 2 == 0 end, ipairs, elements)). + to_equal {false, true, false, true, false} + - it supports key:value results from mapping function: + expect (f (function (k, v) return v, k end, pairs, elements)). + to_equal (inverse) - describe nop: From edd7db3f8e59a2ea30b74c47293898744d3b5b9f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 19:07:28 +0100 Subject: [PATCH 336/703] functional: rename fold to reduce. * specs/functional_spec.yaml (fold): Remove argument checking specifications. Add deprecation warning specification. (reduce): Specify identical behaviour to old fold api. * lib/std/functional.lua (fold): Rename from this... (reduce): ...to this. (fold): Show a deprecation warning on first use. * specs/container_spec.yaml (construction): Adjust. * lib/std/list.lua (foldl, foldr): Use functional.reduce instead of deprecated functional.fold. * lib/std.lua.in (barrel): Install _G.fold from `std.functional.reduce`. * specs/std_spec.yaml (barrel): Adjust. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 ++ lib/std.lua.in | 7 +++-- lib/std/functional.lua | 63 +++++++++++++++++++++++--------------- lib/std/list.lua | 6 ++-- specs/container_spec.yaml | 4 +-- specs/functional_spec.yaml | 44 +++++++++++++++++++++----- specs/std_spec.yaml | 2 +- 7 files changed, 88 insertions(+), 41 deletions(-) diff --git a/NEWS b/NEWS index 32854ef..7d31dc3 100644 --- a/NEWS +++ b/NEWS @@ -93,6 +93,9 @@ Stdlib NEWS - User visible changes ** Deprecations: + - `functional.fold` has been deprecated, in favour of the more commonly + used name, `functional.reduce`. + - `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 diff --git a/lib/std.lua.in b/lib/std.lua.in index 3f2ff37..fa84906 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -266,8 +266,8 @@ export (M, "barrel (table?)", function (namespace) -- Older releases installed the following into _G by default. for v in base.ielems { "functional.bind", "functional.collect", "functional.compose", - "functional.curry", "functional.filter", "functional.fold", - "functional.id", "functional.map", "functional.op", + "functional.curry", "functional.filter", "functional.id", + "functional.map", "functional.op", "io.die", "io.warn", @@ -282,6 +282,9 @@ export (M, "barrel (table?)", function (namespace) namespace[method] = M[module][method] end + -- Support fold, even though we renamed to reduce in v41. + namespace.fold = M.functional.reduce + require "std.io".monkey_patch (namespace) require "std.math".monkey_patch (namespace) require "std.string".monkey_patch (namespace) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index ac6fd51..92da118 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -157,31 +157,6 @@ export (M, "filter (func, func, any*)", function (p, i, ...) end) ---- Fold a binary function into an iterator. --- @function fold --- @func f function --- @param d initial first argument --- @func i iterator --- @param ... iterator arguments --- @return result --- @see std.list.foldl --- @see std.list.foldr --- @usage --- --> 2 ^ 3 ^ 4 ==> 4096 --- fold (std.lambda "^", 2, std.ipairs, {3, 4}) -export (M, "fold (func, any, func, any*)", function (f, d, i, ...) - local fn, state, k = i (...) - local t = {fn (state, k)} - - local r = d - while t[1] ~= nil do - r = f (r, t[#t]) - t = {fn (state, t[1])} - end - return r -end) - - --- Identity function. -- @function id -- @param ... @@ -227,6 +202,31 @@ end) M.nop = nop +--- Fold a binary function into an iterator. +-- @function reduce +-- @func f function +-- @param d initial first argument +-- @func i iterator +-- @param ... iterator arguments +-- @return result +-- @see std.list.foldl +-- @see std.list.foldr +-- @usage +-- --> 2 ^ 3 ^ 4 ==> 4096 +-- reduce (std.lambda "^", 2, std.ipairs, {3, 4}) +local reduce = export (M, "reduce (func, any, func, any*)", function (f, d, i, ...) + local fn, state, k = i (...) + local t = {fn (state, k)} + + local r = d + while t[1] ~= nil do + r = f (r, t[#t]) + t = {fn (state, t[1])} + end + return r +end) + + -- For backwards compatibility. export (M, "case (any?, #table)", base.case) export (M, "eval (string)", base.eval) @@ -234,6 +234,19 @@ export (M, "memoize (func, func?)", base.memoize) M.op = require "std.operator" + +--[[ ============= ]]-- +--[[ Deprecations. ]]-- +--[[ ============= ]]-- + + +local DEPRECATED = base.DEPRECATED + +M.fold = DEPRECATED ("41", "'std.functional.fold'", + "use 'std.functional.reduce' instead", reduce) + + + return M --- Types diff --git a/lib/std/list.lua b/lib/std/list.lua index 980ec39..8c83074 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -212,7 +212,7 @@ end -- @see std.list:foldl local function foldl (fn, e, l) argscheck ("std.list.foldl", {"function", "any?", "List"}, {fn, e, l}) - return func.fold (fn, e, ielems, l) + return func.reduce (fn, e, ielems, l) end @@ -224,8 +224,8 @@ end -- @see std.list:foldr local function foldr (fn, e, l) argscheck ("std.list.foldr", {"function", "any?", "List"}, {fn, e, l}) - return List (func.fold (function (x, y) return fn (y, x) end, - e, ielems, ireverse (l))) + return List (func.reduce (function (x, y) return fn (y, x) end, + e, ielems, ireverse (l))) end diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 5c0bc41..7bca7a8 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -50,10 +50,10 @@ specify std.container: expect (getmetatable (things)._baz).to_be "quux" - context with module functions: - before: - fold = require "std.functional".fold + reduce = require "std.functional".reduce functions = { count = function (bag) - return fold (function (r, k) return r + k end, 0, pairs, bag) + return reduce (function (r, k) return r + k end, 0, pairs, bag) end, } Bag = Container { diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index bbfc2b0..07066c0 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -165,16 +165,16 @@ specify std.functional: - describe fold: - before: fname = "fold" - msg = M.bind (badarg, {this_module, fname}) f = M[fname] - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (f)).to_error (msg (2, "any value")) - expect (f (f, 1)).to_error (msg (3, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, 1, false)).to_error (msg (3, "function", "boolean")) + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {M.id, 1, ipairs, {}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.functional.fold' was deprecated" + end + _, err = capture (f, {M.id, 1, ipairs, {}}) + expect (err).to_be (nil) - it works with an empty table: expect (f (M.op["+"], 2, ipairs, {})).to_be (2) @@ -249,3 +249,31 @@ specify std.functional: expect (f (1, 2, 3, nil, "str", {}, f)).to_be (nil) - it returns no values: expect (f (1, "two", false)).to_be (nil) + + +- describe reduce: + - before: + fname = "reduce" + msg = M.bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (f)).to_error (msg (2, "any value")) + expect (f (f, 1)).to_error (msg (3, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, 1, false)).to_error (msg (3, "function", "boolean")) + + - it works with an empty table: + expect (f (M.op["+"], 2, ipairs, {})).to_be (2) + - it calls a binary function over single return value iterator results: + expect (f (M.op["+"], 2, base.ielems, {3})). + to_be (2 + 3) + expect (f (M.op["*"], 2, base.ielems, {3, 4})). + to_be (2 * 3 * 4) + - it calls a binary function over key:value iterator results: + expect (f (M.op["+"], 2, ipairs, {3})).to_be (2 + 3) + expect (f (M.op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) + - it reduces elements from left to right: + expect (f (M.op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 8b5a53e..ce2644a 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -142,7 +142,7 @@ specify std: elems = M.elems, eval = M.eval, filter = M.functional.filter, - fold = M.functional.fold, + fold = M.functional.reduce, getmetamethod = M.getmetamethod, id = M.functional.id, ielems = M.ielems, From 5b044eec6af654f4e49925d15eb6f60464f90d49 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 21:55:52 +0100 Subject: [PATCH 337/703] refactor: move case back to std.functional. * specs/std_spec.yaml (case): Move from here... * specs/functional_spec.yaml (case): ...to here. * lib/std/base.lua (case): Move implementation from here.. * lib/std.lua.in (case): ...and argcheck wrapper from here... * lib/std/functional.lua (case): ...to here. * lib/std/package.lua (path_sub): Decouple from std.functional by comparing manually rather than using functional.case. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 17 -------------- lib/std/base.lua | 6 ----- lib/std/functional.lua | 21 ++++++++++++++++- lib/std/package.lua | 13 ++++++----- specs/functional_spec.yaml | 38 +++++++++++++++++++++++++++++++ specs/std_spec.yaml | 46 ++++---------------------------------- 6 files changed, 69 insertions(+), 72 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index fa84906..65cd31f 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -43,23 +43,6 @@ local M = { "std" } export (M, "assert (any?, string?, any?*)", base.assert) ---- A rudimentary case statement. --- Match `with` against keys in `branches` table, and return the result --- of running the function in the table value for the matching key, or --- the first non-key value function if no key matches. --- @function case --- @param with expression to match --- @tparam table branches map possible matches to functions --- @return the return value from function with a matching key, or nil. --- @usage --- return std.case (type (object), { --- table = function () return something end, --- string = function () return something else end, --- function (s) error ("unhandled type: "..s) end, --- }) -export (M, "case (any?, #table)", base.case) - - --- An iterator over all elements of a sequence. -- If *t* has a `__pairs` metamethod, use that to iterate. -- @function elems diff --git a/lib/std/base.lua b/lib/std/base.lua index 3da5f98..08ae622 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -218,12 +218,6 @@ local function assert (expect, f, arg1, ...) end -local function case (with, branches) - local f = branches[with] or branches[1] - if f then return f (with) end -end - - local function eval (s) return loadstring ("return " .. s)() end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 92da118..12e7667 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -52,6 +52,26 @@ local bind; bind = export (M, "bind (func, any?*)", function (f, ...) end) +--- A rudimentary case statement. +-- Match `with` against keys in `branches` table, and return the result +-- of running the function in the table value for the matching key, or +-- the first non-key value function if no key matches. +-- @function case +-- @param with expression to match +-- @tparam table branches map possible matches to functions +-- @return the return value from function with a matching key, or nil. +-- @usage +-- return case (type (object), { +-- table = function () return something end, +-- string = function () return something else end, +-- function (s) error ("unhandled type: " .. s) end, +-- }) +export (M, "case (any?, #table)", function (with, branches) + local f = branches[with] or branches[1] + if f then return f (with) end +end) + + --- Collect the results of an iterator. -- @function collect -- @func i iterator @@ -228,7 +248,6 @@ end) -- For backwards compatibility. -export (M, "case (any?, #table)", base.case) export (M, "eval (string)", base.eval) export (M, "memoize (func, func?)", base.memoize) M.op = require "std.operator" diff --git a/lib/std/package.lua b/lib/std/package.lua index 8a244fe..38d1fb0 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -12,7 +12,6 @@ local base = require "std.base" -local case = require "std.functional".case local catfile = require "std.io".catfile local invert = require "std.table".invert local escape_pattern = require "std.string".escape_pattern @@ -38,11 +37,13 @@ local M = { "std.package" } -- for `/` and `?` local function pathsub (path) return path:gsub ("%%?.", function (capture) - return case (capture, { - ["?"] = function () return M.path_mark end, - ["/"] = function () return M.dirsep end, - function (s) return s:gsub ("^%%", "", 1) end, - }) + if capture == "?" then + return M.path_mark + elseif capture == "/" then + return M.dirsep + else + return capture:gsub ("^%%", "", 1) + end end) end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 07066c0..bf9f69e 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -60,6 +60,44 @@ specify std.functional: expect (f (math.pow, nil, 3) (2)).to_be (8) +- describe case: + - before: + yes = function () return true end + no = function () return false end + default = function (s) return s end + branches = { yes = yes, no = no, default } + + fname = "case" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (2, "non-empty table")) + - it diagnoses wrong argument types: + expect (f ("no", false)). + to_error (msg (2, "non-empty table", "boolean")) + - it diagnoses too many arguments: + expect (f (1, {2}, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + + - it matches against branch keys: + expect (f ("yes", branches)).to_be (true) + expect (f ("no", branches)).to_be (false) + - it has a default for unmatched keys: + expect (f ("none", branches)).to_be "none" + - it returns nil for unmatched keys with no default: + expect (f ("none", { yes = yes, no = no })).to_be (nil) + - it evaluates `with` exactly once: + s = "prince" + function acc () s = s .. "s"; return s end + expect (f (acc (), { + prince = function () return "one" end, + princes = function () return "many" end, + princess = function () return "one" end, + function () return "gibberish" end, + })).to_be "many" + + - describe collect: - before: fname = "collect" diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index ce2644a..c32820e 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -2,10 +2,10 @@ before: | this_module = "std" global_table = "_G" - exported_apis = { 1, "assert", "barrel", "case", "elems", "eval", - "getmetamethod", "ielems", "ipairs", "ireverse", "lambda", - "memoize", "monkey_patch", "pairs", "require", "ripairs", - "tostring", "version" } + exported_apis = { 1, "assert", "barrel", "elems", "eval", "getmetamethod", + "ielems", "ipairs", "ireverse", "lambda", "memoize", + "monkey_patch", "pairs", "require", "ripairs", "tostring", + "version" } -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -173,44 +173,6 @@ specify std: warn = M.io.warn, } -- describe case: - - before: - yes = function () return true end - no = function () return false end - default = function (s) return s end - branches = { yes = yes, no = no, default } - - fname = "case" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (2, "non-empty table")) - - it diagnoses wrong argument types: - expect (f ("no", false)). - to_error (msg (2, "non-empty table", "boolean")) - - it diagnoses too many arguments: - expect (f (1, {2}, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) - - - it matches against branch keys: - expect (f ("yes", branches)).to_be (true) - expect (f ("no", branches)).to_be (false) - - it has a default for unmatched keys: - expect (f ("none", branches)).to_be "none" - - it returns nil for unmatched keys with no default: - expect (f ("none", { yes = yes, no = no })).to_be (nil) - - it evaluates `with` exactly once: - s = "prince" - function acc () s = s .. "s"; return s end - expect (f (acc (), { - prince = function () return "one" end, - princes = function () return "many" end, - princess = function () return "one" end, - function () return "gibberish" end, - })).to_be "many" - - - describe elems: - before: fname = "elems" From 6465393d32c906d46f3070741a626dac8a095c07 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 22:28:31 +0100 Subject: [PATCH 338/703] refactor: move memoize back to std.functional. * specs/std_spec.yaml (memoize): Move from here... * specs/functional_spec.yaml (memoize): ...to here. * lib/std.lua.in (memoize): Move from here... * lib/std/functional.lua (memoize): ...to here. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 17 ++++++------- lib/std.lua.in | 29 ---------------------- lib/std/functional.lua | 35 ++++++++++++++++++++++---- specs/functional_spec.yaml | 43 ++++++++++++++++++++++++++++++++ specs/std_spec.yaml | 51 ++------------------------------------ 5 files changed, 83 insertions(+), 92 deletions(-) diff --git a/NEWS b/NEWS index 7d31dc3..6d644e7 100644 --- a/NEWS +++ b/NEWS @@ -27,24 +27,23 @@ Stdlib NEWS - User visible changes relational operators, `<`, `<=`, `>` and `>=`. The `#` operator respects the `__len` metamethod, if any, even on Lua 5.1. - - New `functional.nop` function, for use where a function is required - but no work should be done. - - `functional.collect`, `functional.filter` and `functional.fold` now work with standard multi-return iterators, such as `std.ipairs`. - `functional.map` now supports a mapping function that returns a key, value pair for the results table. - - `std` module now collects stdlib functions that do not really belong - in specific type modules: including `std.case`, `std.eval`, and - `std.memoize` (original access points exported by earlier releases will - be preserved for the forseeable future). - - - `std.memoize` now propagates multiple return values correctly. + - `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. + + - `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.lambda` function for compiling lambda strings: table.sort (t, std.lambda "|a,b| a Date: Thu, 7 Aug 2014 22:45:51 +0100 Subject: [PATCH 339/703] refactor: functional.eval issues a deprecation warning. * specs/functional_spec.yaml (eval): Specify deprecation warning. * lib/std/functional.lua (eval): Deprecated. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 38 ++++++++++++++++++++++++++------------ lib/std/functional.lua | 6 +++++- specs/functional_spec.yaml | 22 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index 6d644e7..29068e9 100644 --- a/NEWS +++ b/NEWS @@ -92,8 +92,27 @@ Stdlib NEWS - User visible changes ** Deprecations: - - `functional.fold` has been deprecated, in favour of the more commonly - used name, `functional.reduce`. + - 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. + + - By default, deprecated APIs will issue a warning to stderr on first + use only. However, you can turn off these warnings entirely with: + + _DEBUG = { compat = true } + + Or, you can issue the warnings on every use with: + + _DEBUG = { compat = false } + + The `_DEBUG` global must be set before requiring any stdlib modules. + + - `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. - `list.index_key` and `list.index_value` have been deprecated. These functions are not general enough to belong in lua-stdlib, because @@ -110,24 +129,19 @@ Stdlib NEWS - User visible changes and more accurately named `std.ireverse`. - `string.assert` has been moved to `std.assert`, the old name now - gives a deprecation warning on first use. + gives a deprecation warning. - `string.require_version` has been moved to `std.require`, the old - name now gives a deprecation warning on first use, and will be - removed entirely in some future release. + name now gives a deprecation warning. - `string.tostring` has been moved to `std.tostring`, the old name now - gives a deprecation warning on first use, and will be removed - entirely in some future release. + gives a deprecation warning. - `table.metamethod` has been moved to `std.getmetamethod`, the old - name now gives a deprecation warning on first use, and will be - removed entirely in some future release. + name now gives a deprecation warning. - `table.ripairs` has been moved to `std.ripairs`, the old name now - gives a deprecation warning on first use, and will be removed - entirely in some future release. - + gives a deprecation warning. ** Incompatible changes: diff --git a/lib/std/functional.lua b/lib/std/functional.lua index b3319a2..963af5a 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -263,7 +263,6 @@ end) -- For backwards compatibility. -export (M, "eval (string)", base.eval) M.op = require "std.operator" @@ -275,6 +274,11 @@ M.op = require "std.operator" local DEPRECATED = base.DEPRECATED + +M.eval = DEPRECATED ("41", "'std.functional.eval'", + "use 'std.eval' instead", base.eval) + + M.fold = DEPRECATED ("41", "'std.functional.fold'", "use 'std.functional.reduce' instead", reduce) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 28dd0ab..730264b 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -165,6 +165,28 @@ specify std.functional: expect (bin (10)).to_be (math.pow (2, 10)) +- describe eval: + - before: + fname = "eval" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + _, err = capture (f, {"42"}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.functional.eval' was deprecated" + end + _, err = capture (f, {"42"}) + expect (err).to_be (nil) + + - it diagnoses invalid lua: + # Some internal error when eval tries to call uncompilable "=" code. + expect (f "=").to_error () + - it evaluates a string of lua code: + expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) + + - describe filter: - before: elements = {"a", "b", "c", "d", "e"} From 3aeec3a85d4465bf30bf2291dc7c6bf0f931ac3b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Aug 2014 22:56:36 +0100 Subject: [PATCH 340/703] specs: add exported api specification to functional_spec.yaml. * specs/functional_spec.yaml (std.functional): Specify exported apis. Signed-off-by: Gary V. Vaughan --- specs/functional_spec.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 730264b..e802171 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -1,9 +1,13 @@ -before: | +before: base = require "std.base" this_module = "std.functional" global_table = "_G" + exported_apis = { 1, "bind", "case", "collect", "compose", "curry", "eval", + "filter", "fold", "id", "map", "memoize", "nop", "op", + "reduce" } + M = require (this_module) specify std.functional: @@ -12,6 +16,10 @@ specify std.functional: - 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 via the std module: - it does not touch the global table: From bc7368fc058c2a04949479cefc8c53a4e0c19134 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 11 Aug 2014 16:48:30 +0100 Subject: [PATCH 341/703] functional: process non-function branch values with case. * specs/functional_spec.yaml (case): Specify behaviours with new functable and non-callable branch values. * lib/std/functional.lua (case): Call functables as if they were regular functions, and return non-callable values directly. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 5 +++++ lib/std/functional.lua | 37 ++++++++++++++++++++++++++++++------- specs/functional_spec.yaml | 12 +++++++++++- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index 29068e9..613b412 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,11 @@ Stdlib NEWS - User visible changes relational operators, `<`, `<=`, `>` and `>=`. The `#` operator respects the `__len` metamethod, if any, even on Lua 5.1. + - `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` and `functional.fold` now work with standard multi-return iterators, such as `std.ipairs`. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 963af5a..d03a9ef 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -15,6 +15,24 @@ local export, nop, pairs = base.export, base.nop, base.pairs local M = { "std.functional" } + +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + +local function iscallable (x) + if type (x) == "function" then return true end + return type ((getmetatable (x) or {}).__call) == "function" +end + + + +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- + + --- Partially apply a function. -- @function bind -- @func f function to apply partially @@ -53,22 +71,27 @@ end) --- A rudimentary case statement. --- Match `with` against keys in `branches` table, and return the result --- of running the function in the table value for the matching key, or --- the first non-key value function if no key matches. +-- Match *with* against keys in *branches* table, and return the result +-- table value for the matching key, or the first non-key value if no key +-- matches. Function or functable valued matches are called using *with* as +-- the sole argument, and the result of that call returned; otherwise the +-- matching value associated with the matching key is returned directly. -- @function case -- @param with expression to match -- @tparam table branches map possible matches to functions -- @return the return value from function with a matching key, or nil. -- @usage -- return case (type (object), { --- table = function () return something end, --- string = function () return something else end, +-- table = "table", +-- string = function () return "string" end, -- function (s) error ("unhandled type: " .. s) end, -- }) export (M, "case (any?, #table)", function (with, branches) - local f = branches[with] or branches[1] - if f then return f (with) end + local match = branches[with] or branches[1] + if iscallable (match) then + return match (with) + end + return match end) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index e802171..37d4413 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -95,7 +95,17 @@ specify std.functional: expect (f ("none", branches)).to_be "none" - it returns nil for unmatched keys with no default: expect (f ("none", { yes = yes, no = no })).to_be (nil) - - it evaluates `with` exactly once: + - it returns non-function matches: + expect (f ("t", {t = true})).to_be (true) + - it evaluates returned functions: + expect (f ("fn", {fn = function () return true end})). + to_be (true) + - it passes 'with' to function matches: + expect (f ("with", {function (s) return s end})).to_be "with" + - it evaluates returned functables: + functable = setmetatable ({}, {__call = function (t, with) return with end}) + expect (f ("functable", {functable})).to_be "functable" + - it evaluates 'with` exactly once: s = "prince" function acc () s = s .. "s"; return s end expect (f (acc (), { From 4683c5c691f7fbc219561880a50210a689fc31e3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 11 Aug 2014 18:19:10 +0100 Subject: [PATCH 342/703] functional: new `cond` function. * specs/functional_spec.yaml (cond): Specify behaviour of a new cond function. * lib/std/functional.lua (cond): Satisfy specified behaviours. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/functional.lua | 35 +++++++++++++++++++++++++++++++++++ specs/functional_spec.yaml | 36 +++++++++++++++++++++++++++++++++--- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 613b412..c4ad20a 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,9 @@ Stdlib NEWS - User visible changes - `functional.collect`, `functional.filter` and `functional.fold` now work with standard multi-return iterators, such as `std.ipairs`. + - New `functional.cond`, for evaluating multiple distinct expressions + to determine what following value to be the returned. + - `functional.map` now supports a mapping function that returns a key, value pair for the results table. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index d03a9ef..1566437 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -80,6 +80,7 @@ end) -- @param with expression to match -- @tparam table branches map possible matches to functions -- @return the return value from function with a matching key, or nil. +-- @see cond -- @usage -- return case (type (object), { -- table = "table", @@ -148,6 +149,40 @@ export (M, "compose (func*)", function (...) end) +--- A rudimentary condition-case statement. +-- If *expr* is "truthy" return *branch* if given, otherwise *expr* +-- itself. If the return value is a function or functable, then call it +-- with *expr* as the sole argument and return the result; otherwise +-- return it explicitly. If *expr* is "falsey", then recurse with the +-- first two arguments stripped. +-- @function cond +-- @param expr a Lua expression +-- @param branch a function, functable or value to use if *expr* is +-- "truthy" +-- @param ... additional arguments to retry if *expr* is "falsey" +-- @see case +-- @usage +-- -- recursively calculate the nth triangular number +-- function triangle (n) +-- return cond ( +-- n <= 0, 0, +-- n == 1, 1, +-- function () return n + triangle (n - 1) end) +-- end +M.cond = function (expr, branch, ...) + if branch == nil and select ("#", ...) == 0 then + expr, branch = true, expr + end + if expr then + if iscallable (branch) then + return branch (expr) + end + return branch + end + return M.cond (...) +end + + --- Curry a function. -- @function curry -- @func f function to curry diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 37d4413..0a23f16 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -4,9 +4,9 @@ before: this_module = "std.functional" global_table = "_G" - exported_apis = { 1, "bind", "case", "collect", "compose", "curry", "eval", - "filter", "fold", "id", "map", "memoize", "nop", "op", - "reduce" } + exported_apis = { 1, "bind", "case", "collect", "compose", "cond", "curry", + "eval", "filter", "fold", "id", "map", "memoize", "nop", + "op", "reduce" } M = require (this_module) @@ -153,6 +153,36 @@ specify std.functional: to_be (math.cos (math.sin (1))) +- describe cond: + - before: + yes = function () return true end + no = function () return false end + default = function (s) return s end + branches = { yes = yes, no = no, default } + + fname = "cond" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it returns nil for no arguments: + expect (f ()).to_be (nil) + - it evaluates a single function argument: + expect (f (function () return true end)).to_be (true) + - it evaluates a single functable argument: + functable = setmetatable ({}, {__call = function () return true end}) + expect (f (functable)).to_be (true) + - it returns a non-callable single argument directly: + expect (f "foo").to_be "foo" + - it evaluates a branch function if expr is truthy: + expect (f ("truthy", function (s) return s end)).to_be "truthy" + - it returns nil if the last expr is falsey: + expect (f (nil, function (s) return "falsey" end)).to_be (nil) + expect (f (false, true, false, true)).to_be (nil) + - it recurses with remaining arguments if first argument is falsey: + expect (f (nil, true, 42, M.id)).to_be (42) + expect (f (nil, true, false, false, 42, M.id)).to_be (42) + + - describe curry: - before: fname = "curry" From 92865f4a65be520408bf7fa5ca90a755231b5958 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 11 Aug 2014 18:48:30 +0100 Subject: [PATCH 343/703] refactor: move lambda back to std.functional. * lib/std.lua.in (lambda): Move from here... * lib/std/base.lua (lamba): ...and here... * lib/std/functional.lua (lambda): ...to here. * specs/functional_spec.yaml, specs/std_spec.yaml: Adjust accordingly. * lib/std/base.lua (tostring): Unroll lambda calls. * lib/std/operator.lua ("#"): Move implementation from here... * lib/std/base.lua (len): ...to here. Adjust all callers. (_len): Remove. (operator): Remove unused require statement * lib/std/list.lua (transpose): Unroll lambda call. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 24 ++++++------ lib/std.lua.in | 27 +------------ lib/std/base.lua | 70 +++++++++------------------------- lib/std/functional.lua | 78 +++++++++++++++++++++++++++++++++++--- lib/std/list.lua | 2 +- lib/std/operator.lua | 9 ++--- specs/functional_spec.yaml | 42 +++++++++++++++++++- specs/std_spec.yaml | 44 +-------------------- 8 files changed, 149 insertions(+), 147 deletions(-) diff --git a/NEWS b/NEWS index c4ad20a..34512f6 100644 --- a/NEWS +++ b/NEWS @@ -38,6 +38,18 @@ Stdlib NEWS - User visible changes - New `functional.cond`, for evaluating multiple distinct expressions to determine what following value to be the returned. + - New `functional.lambda` function for compiling lambda strings: + + table.sort (t, lambda "|a,b| a 0 then return n, t[n] end - end, t, _len (t) + 1 + end, t, len (t) + 1 end -- Be careful not to compact holes from `t` when reversing. local function ireverse (t) - local r, len = {}, _len (t) - for i = 1, len do r[len - i + 1] = t[i] end + local r, tlen = {}, len (t) + for i = 1, tlen do r[tlen - i + 1] = t[i] end return r end @@ -223,47 +225,6 @@ local function eval (s) end -local function lambda (l) - local s - - -- Support operator table lookup. - if operator[l] then - return operator[l] - end - - -- Support "|args|expression" format. - local args, body = string.match (l, "^|([^|]*)|%s*(.+)$") - if args and body then - s = "return function (" .. args .. ") return " .. body .. " end" - end - - -- Support "=expression" format. - if not s then - body = l:match "^=%s*(.+)$" - if body then - s = [[ - return function (...) - local _1,_2,_3,_4,_5,_6,_7,_8,_9 = unpack {...} - return ]] .. body .. [[ - end - ]] - end - end - - local ok, fn - if s then - ok, fn = pcall (loadstring (s)) - end - - -- Diagnose invalid input. - if not ok then - return nil, "invalid lambda string '" .. l .. "'" - end - - return fn -end - - local function require_version (module, min, too_big, pattern) local m = require (module) if min then @@ -279,9 +240,12 @@ end local _tostring = _G.tostring local function tostring (x) - return render (x, lambda '="{"', lambda '="}"', _tostring, - lambda '=_4.."=".._5', - lambda '=_2 and _4 and "," or ""') + return render (x, + function () return "{" end, + function () return "}" end, + _tostring, + function (_, _, _, is, vs) return is .."=".. vs end, + function (_, i, _, j) return i and j and "," or "" end) end @@ -922,7 +886,6 @@ return { ielems = ielems, ipairs = ipairs, ireverse = ireverse, - lambda = lambda, memoize = memoize, pairs = pairs, ripairs = ripairs, @@ -956,6 +919,7 @@ return { DEPRECATIONMSG = M.DEPRECATIONMSG, export = export, getcompat = getcompat, + len = len, setcompat = setcompat, toomanyarg_fmt = toomanyarg_fmt, } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 1566437..1b4b9ec 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,7 +8,8 @@ ]] -local base = require "std.base" +local base = require "std.base" +local operator = require "std.operator" local export, nop, pairs = base.export, base.nop, base.pairs @@ -39,7 +40,7 @@ end -- @tparam table t {p1=a1, ..., pn=an} table of parameters to bind to given arguments -- @return function with *pi* already bound -- @usage --- > cube = bind (std.lambda "^", {[2] = 3}) +-- > cube = bind (lambda "^", {[2] = 3}) -- > =cube (2) -- 8 local bind; bind = export (M, "bind (func, any?*)", function (f, ...) @@ -213,7 +214,7 @@ end) -- @treturn table elements e for which `p (e)` is not falsey. -- @see collect -- @usage --- > filter (std.lambda "|e|e%2==0", std.elems, {1, 2, 3, 4}) +-- > filter (lambda "|e|e%2==0", std.elems, {1, 2, 3, 4}) -- {2, 4} export (M, "filter (func, func, any*)", function (p, i, ...) local r = {} -- new results table @@ -244,6 +245,71 @@ function M.id (...) end +--- Compile a lambda string into a Lua function. +-- +-- A valid lambda string takes one of the following forms: +-- +-- 1. `operator`: where *op* is a key in @{std.operator}, equivalent to that operation +-- 1. `"=expression"`: equivalent to `function (...) return (expression) end` +-- 1. `"|args|expression"`: equivalent to `function (args) return (expression) end` +-- +-- The second form (starting with `=`) automatically assigns the first +-- nine arguments to parameters `_1` through `_9` for use within the +-- expression body. +-- +-- The results are memoized, so recompiling an previously compiled +-- lambda string is extremely fast. +-- @function lambda +-- @string s a lambda string +-- @treturn table compiled lambda string, can be called like a function +-- @usage +-- -- The following are all equivalent: +-- lambda "<" +-- lambda "= _1 < _2" +-- lambda "|a,b| a 2 ^ 3 ^ 4 ==> 4096 --- reduce (std.lambda "^", 2, std.ipairs, {3, 4}) +-- reduce (lambda "^", 2, std.ipairs, {3, 4}) local reduce = export (M, "reduce (func, any, func, any*)", function (f, d, i, ...) local fn, state, k = i (...) local t = {fn (state, k)} @@ -321,7 +387,7 @@ end) -- For backwards compatibility. -M.op = require "std.operator" +M.op = operator @@ -364,5 +430,5 @@ return M -- @treturn boolean "truthy" if the predicate condition succeeds, -- "falsey" otherwise -- @usage --- local predicate = std.lambda '|k,v|type(v)=="string"' +-- local predicate = lambda '|k,v|type(v)=="string"' -- local strvalues = filter (predicate, std.pairs, {name="Roberto", id=12345}) diff --git a/lib/std/list.lua b/lib/std/list.lua index 8c83074..06271fa 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -417,7 +417,7 @@ local function transpose (ls) end end - local rs, len, dims = List {}, #ls, map (base.lambda "#", ls) + local rs, len, dims = List {}, base.len (ls), map (base.len, ls) if #dims > 0 then for i = 1, math.max (unpack (dims)) do rs[i] = List {} diff --git a/lib/std/operator.lua b/lib/std/operator.lua index e5e4a7c..9e3419a 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -5,6 +5,9 @@ ]] +local base = require "std.base" + + --- Functional forms of Lua operators. -- -- Defined here so that other modules can write to it. @@ -39,11 +42,7 @@ return { ["{}"] = function (...) return {...} end, ['""'] = function (x) return tostring (x) end, ["~"] = function (s, p) return string.find (s, p) end, - ["#"] = function (t) - -- Lua < 5.2 doesn't call `__len` automatically! - local m = (getmetatable (t) or {}).__len - return m and m (t) or #t - end, + ["#"] = base.len, ["+"] = function (a, b) return a + b end, ["-"] = function (a, b) return a - b end, ["*"] = function (a, b) return a * b end, diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 0a23f16..6117853 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -5,8 +5,8 @@ before: global_table = "_G" exported_apis = { 1, "bind", "case", "collect", "compose", "cond", "curry", - "eval", "filter", "fold", "id", "map", "memoize", "nop", - "op", "reduce" } + "eval", "filter", "fold", "id", "lambda", "map", + "memoize", "nop", "op", "reduce" } M = require (this_module) @@ -308,6 +308,44 @@ specify std.functional: expect ({f (1, "two", false)}).to_equal {1, "two", false} +- describe lambda: + - before: + fname = "lambda" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "string")) + - it diagnoses wrong arguments types: + expect (f (false)).to_error (msg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (f ("foo", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + - it diagnoses bad lambda string: + expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" + - it diagnoses an uncompilable expression: + expect (select (2, f "||+")).to_be "invalid lambda string '||+'" + expect (select (2, f "=")).to_be "invalid lambda string '='" + + - context with argument format: + - it returns a function: + expect (prototype (f "|x| 1+x")).to_be "function" + - it compiles to a working Lua function: + fn = f "||42" + expect (fn ()).to_be (42) + - it propagates argument values: + fn = f "|...| {...}" + expect (fn (1,2,3)).to_equal {1,2,3} + - context with expression format: + - it returns a function: + expect (prototype (f "=1")).to_be "function" + - it compiles to a working Lua function: + fn = f "=42" + expect (fn ()).to_be (42) + - it sets auto-argument values: + fn = f "=_1*_1" + expect (fn (42)).to_be (1764) + + - describe map: - before: elements = {"a", "b", "c", "d", "e"} diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index ba01669..bc86c02 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -3,8 +3,8 @@ before: | global_table = "_G" exported_apis = { 1, "assert", "barrel", "elems", "eval", "getmetamethod", - "ielems", "ipairs", "ireverse", "lambda", "monkey_patch", - "pairs", "require", "ripairs", "tostring", "version" } + "ielems", "ipairs", "ireverse", "monkey_patch", "pairs", + "require", "ripairs", "tostring", "version" } -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -114,7 +114,6 @@ specify std: expect (t.eval).to_be (M.eval) expect (t.ielems).to_be (M.ielems) expect (t.ipairs).to_be (M.ipairs) - expect (t.lambda).to_be (M.lambda) expect (t.pairs).to_be (M.pairs) expect (t.require).to_be (M.require) - it installs std.math monkey patches: @@ -148,7 +147,6 @@ specify std: io = t.io, ipairs = M.ipairs, ireverse = M.ireverse, - lambda = M.lambda, leaves = M.tree.leaves, map = M.functional.map, math = t.math, @@ -358,44 +356,6 @@ specify std: expect (f {}).to_equal {} -- describe lambda: - - before: - fname = "lambda" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - - it diagnoses wrong arguments types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("foo", false)).to_error (toomanyarg (this_module, fname, 1, 2)) - - it diagnoses bad lambda string: - expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" - - it diagnoses an uncompilable expression: - expect (select (2, f "||+")).to_be "invalid lambda string '||+'" - expect (select (2, f "=")).to_be "invalid lambda string '='" - - - context with argument format: - - it returns a function: - expect (prototype (f "|x| 1+x")).to_be "function" - - it compiles to a working Lua function: - fn = f "||42" - expect (fn ()).to_be (42) - - it propagates argument values: - fn = f "|...| {...}" - expect (fn (1,2,3)).to_equal {1,2,3} - - context with expression format: - - it returns a function: - expect (prototype (f "=1")).to_be "function" - - it compiles to a working Lua function: - fn = f "=42" - expect (fn ()).to_be (42) - - it sets auto-argument values: - fn = f "=_1*_1" - expect (fn (42)).to_be (1764) - - - describe monkey_patch: - before: io_mt = {} From 1b935bdda851f8f1a65555b35863ade2e586c99d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 13 Aug 2014 13:41:49 +0100 Subject: [PATCH 344/703] travis: add slack notifications. * .travis.yml (notifications): Add slack. Signed-off-by: Gary V. Vaughan --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4925c77..7755175 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,3 +67,6 @@ script: LUA_CPATH=`pwd`'/ext/?.so;'"${LUA_CPATH-;}" LUA_INIT= LUA_INIT_5_2= make check V=1 + +notifications: + slack: aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih From fafdb824769dfcbdbca78495e0f3d5d145361014 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 12 Aug 2014 18:11:46 +0100 Subject: [PATCH 345/703] refactor: move list.foldl and list.foldr to std.functional. Move the documented location for foldl and foldr from list.lua to functional.lua, modernizing specs as we go. Keep the old access points, with a deprecation warning. * lib/std/list.lua (foldl, foldr): Move from here... * lib/std/base.lua (foldl, foldr): ...to here. * lib/std/functional.lua (reduce): Move from here... * lib/std/base.lua (reduce): ...to here, where foldl and foldr can use it. * specs/list_spec.yaml (foldl, foldr): Copy from here... * specs/functional_spec.yaml (foldl, foldr): ...to here. * specs/base_spec.yaml (before): Don't depend on the location of implementation of nop. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 12 +++ lib/std/base.lua | 51 +++++++++++-- lib/std/functional.lua | 52 ++++++++----- lib/std/list.lua | 73 +++++++----------- specs/base_spec.yaml | 2 +- specs/functional_spec.yaml | 70 ++++++++++++++++-- specs/list_spec.yaml | 147 +++++++++++++++++++++++++++++-------- 7 files changed, 295 insertions(+), 112 deletions(-) diff --git a/NEWS b/NEWS index 34512f6..4240ff5 100644 --- a/NEWS +++ b/NEWS @@ -38,6 +38,10 @@ Stdlib NEWS - User visible changes - New `functional.cond`, for evaluating multiple distinct expressions to determine what following value to be the returned. + - 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.lambda` function for compiling lambda strings: table.sort (t, lambda "|a,b| a cube = bind (lambda "^", {[2] = 3}) -- > =cube (2) -- 8 -local bind; bind = export (M, "bind (func, any?*)", function (f, ...) +local bind +bind = export (M, "bind (func, any?*)", function (f, ...) local fix = {...} if type (fix[1]) == "table" and fix[2] == nil then fix = fix[1] @@ -308,8 +310,6 @@ export (M, "lambda (string)", base.memoize (function (l) end, M.id)) - - --- Map a function over an iterator. -- @function map -- @func f function @@ -368,22 +368,40 @@ M.nop = nop -- @func i iterator -- @param ... iterator arguments -- @return result --- @see std.list.foldl --- @see std.list.foldr +-- @see foldl +-- @see foldr -- @usage -- --> 2 ^ 3 ^ 4 ==> 4096 -- reduce (lambda "^", 2, std.ipairs, {3, 4}) -local reduce = export (M, "reduce (func, any, func, any*)", function (f, d, i, ...) - local fn, state, k = i (...) - local t = {fn (state, k)} +export (M, "reduce (func, any, func, any*)", base.reduce) - local r = d - while t[1] ~= nil do - r = f (r, t[#t]) - t = {fn (state, t[1])} - end - return r -end) + +--- Fold a binary function left associatively. +-- If parameter *d* is omitted, the first element of *t* is used. +-- @function foldl +-- @func fn binary function +-- @param[opt] d initial left-most argument +-- @tparam table t a table +-- @return result +-- @see foldr +-- @see reduce +-- @usage +-- foldl (lambda "/", {10000, 100, 10}) == (10000 / 100) / 10 +export (M, "foldl (function, [any], table)", base.foldl) + + +--- Fold a binary function right associatively. +-- If parameter *d* is omitted, the last element of *t* is used. +-- @function foldr +-- @func fn binary function +-- @param[opt] d initial right-most argument +-- @tparam table t a table +-- @return result +-- @see foldl +-- @see reduce +-- @usage +-- foldr (lambda "/", {10000, 100, 10}) == 10000 / (100 / 10) +export (M, "foldr (function, [any], table)", base.foldr) -- For backwards compatibility. @@ -404,7 +422,7 @@ M.eval = DEPRECATED ("41", "'std.functional.eval'", M.fold = DEPRECATED ("41", "'std.functional.fold'", - "use 'std.functional.reduce' instead", reduce) + "use 'std.functional.reduce' instead", base.reduce) return M diff --git a/lib/std/list.lua b/lib/std/list.lua index 06271fa..48880d6 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -37,6 +37,8 @@ local object = require "std.object" local ipairs, pairs = base.ipairs, base.pairs local argcheck, argerror, argscheck, ielems, prototype, ireverse = base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype, base.ireverse +local foldl, foldr = base.foldl, base.foldr + local Object = object {} @@ -204,31 +206,6 @@ local function flatten (l) end ---- Fold a binary function through a list left associatively. --- @func fn binary function --- @param e element to place in left-most position --- @tparam List l a list --- @return result --- @see std.list:foldl -local function foldl (fn, e, l) - argscheck ("std.list.foldl", {"function", "any?", "List"}, {fn, e, l}) - return func.reduce (fn, e, ielems, l) -end - - ---- Fold a binary function through a list right associatively. --- @func fn binary function --- @param e element to place in right-most position --- @tparam List l a list --- @return result --- @see std.list:foldr -local function foldr (fn, e, l) - argscheck ("std.list.foldr", {"function", "any?", "List"}, {fn, e, l}) - return List (func.reduce (function (x, y) return fn (y, x) end, - e, ielems, ireverse (l))) -end - - --- Map a function over a list. -- @func fn map function -- @tparam List l a list @@ -464,8 +441,6 @@ local _functions = { enpair = enpair, filter = filter, flatten = flatten, - foldl = foldl, - foldr = foldr, map = map, map_with = map_with, project = project, @@ -497,6 +472,14 @@ _functions.relems = DEPRECATED ("41", "'std.list.relems'", "compose 'std.ielems' and 'std.ireverse' instead", relems) +_functions.foldl = DEPRECATED ("41", "'std.list.foldl'", + "use 'std.functional.foldl' instead", foldl) + + +_functions.foldr = DEPRECATED ("41", "'std.list.foldr'", + "use 'std.functional.foldr' instead", foldr) + + local function index_key (f, l) local r = {} for i, v in ipairs (l) do @@ -621,24 +604,6 @@ List = Object { -- @treturn List flattened list flatten = flatten, - ------ - -- Fold a binary function through a list left associatively. - -- @function foldl - -- @func fn binary function - -- @param e element to place in left-most position - -- @return result - -- @see std.list.foldl - foldl = function (self, fn, e) return foldl (fn, e, self) end, - - ------ - -- Fold a binary function through a list right associatively. - -- @function foldr - -- @func f binary function - -- @param e element to place in right-most position - -- @return result - -- @see std.list.foldr - foldr = function (self, fn, e) return foldr (fn, e, self) end, - ------ -- Map a function over a list. -- @function map @@ -694,13 +659,25 @@ List = Object { transpose = DEPRECATED ("38", "'std.list:transpose'", transpose), zip_with = DEPRECATED ("38", "'std.list:zip_with'", zip_with), - elems = DEPRECATED ("41", "'std.list:elems'", base.ielems), + elems = DEPRECATED ("41", "'std.list:elems'", base.ielems), + foldl = DEPRECATED ("41", "'std.list:foldl'", + "use 'std.functional.foldl' instead", + function (self, fn, e) + if e ~= nil then return foldl (fn, e, self) end + return foldl (fn, self) + end), + foldr = DEPRECATED ("41", "'std.list:foldr'", + "use 'std.functional.foldr' instead", + function (self, fn, e) + if e ~= nil then return foldr (fn, e, self) end + return foldr (fn, self) + end), index_key = DEPRECATED ("41", "'std.list:index_key'", function (self, f) return index_key (f, self) end), index_value = DEPRECATED ("41", "'std.list:index_value'", function (self, f) return index_value (f, self) end), - relems = DEPRECATED ("41", "'std.list:relems'", relems), - reverse = DEPRECATED ("41", "'std.list:reverse'", reverse), + relems = DEPRECATED ("41", "'std.list:relems'", relems), + reverse = DEPRECATED ("41", "'std.list:reverse'", reverse), }, diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index cc50d76..2dbe134 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -3,7 +3,7 @@ before: M = require (this_module) - nop = M.nop + nop = function () end specify std.base: - describe DEPRECATED: diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 6117853..61dfbdf 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -5,8 +5,8 @@ before: global_table = "_G" exported_apis = { 1, "bind", "case", "collect", "compose", "cond", "curry", - "eval", "filter", "fold", "id", "lambda", "map", - "memoize", "nop", "op", "reduce" } + "eval", "filter", "fold", "foldl", "foldr", "id", + "lambda", "map", "memoize", "nop", "op", "reduce" } M = require (this_module) @@ -119,7 +119,7 @@ specify std.functional: - describe collect: - before: fname = "collect" - msg = M.bind (badarg, {this_module, fname}) + msg = bind (badarg, {this_module, fname}) f = M[fname] - it diagnoses missing arguments: @@ -137,7 +137,7 @@ specify std.functional: - describe compose: - before: fname = "compose" - msg = M.bind (badarg, {this_module, fname}) + msg = bind (badarg, {this_module, fname}) f = M[fname] - it diagnoses missing arguments: @@ -186,7 +186,7 @@ specify std.functional: - describe curry: - before: fname = "curry" - msg = M.bind (badarg, {this_module, fname}) + msg = bind (badarg, {this_module, fname}) f = M[fname] - it diagnoses missing arguments: @@ -240,7 +240,7 @@ specify std.functional: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) fname = "filter" - msg = M.bind (badarg, {this_module, fname}) + msg = bind (badarg, {this_module, fname}) f = M[fname] - it diagnoses missing arguments: @@ -298,6 +298,60 @@ specify std.functional: expect (f (M.op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) +- describe foldl: + - before: + fname = "foldl" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (f, nil)).to_error (msg (2, "any value or table")) + expect (f (f, 42)).to_error (msg (3, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, 42, false)).to_error (msg (3, "table", "boolean")) + - it diagnoses too many arguments: + expect (f (f, 42, {}, false)). + to_error (toomanyarg (this_module, fname, 3, 4)) + + - it works with an empty table: + expect (f (M.op["+"], 10000, {})).to_be (10000) + - it folds a binary function through a table: + expect (f (M.op["+"], 10000, {1, 10, 100})).to_be (10111) + - it folds from left to right: + expect (f (M.op["^"], 2, {3, 4})).to_be ((2 ^ 3) ^ 4) + - it supports eliding init argument: + expect (f (M.op["^"], {2, 3, 4})).to_be ((2 ^ 3) ^ 4) + + +- describe foldr: + - before: + fname = "foldr" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (f, nil)).to_error (msg (2, "any value or table")) + expect (f (f, 42)).to_error (msg (3, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (f, 42, false)).to_error (msg (3, "table", "boolean")) + - it diagnoses too many arguments: + expect (f (f, 42, {}, false)). + to_error (toomanyarg (this_module, fname, 3, 4)) + + - it works with an empty table: + expect (f (M.op["+"], 1, {})).to_be (1) + - it folds a binary function through a table: + expect (f (M.op["+"], {10000, 100, 10, 1})).to_be (10111) + - it folds from right to left: + expect (f (M.op["/"], 10, {10000, 100})).to_be (10000 / (100 / 10)) + - it supports eliding init argument: + expect (f (M.op["/"], {10000, 100, 10})).to_be (10000 / (100 / 10)) + + - describe id: - before: f = M.id @@ -351,7 +405,7 @@ specify std.functional: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) fname = "map" - msg = M.bind (badarg, {this_module, fname}) + msg = bind (badarg, {this_module, fname}) f = M[fname] - it diagnoses missing arguments: @@ -443,7 +497,7 @@ specify std.functional: - describe reduce: - before: fname = "reduce" - msg = M.bind (badarg, {this_module, fname}) + msg = bind (badarg, {this_module, fname}) f = M[fname] - it diagnoses missing arguments: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 7bce7c8..ef1e457 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -441,52 +441,135 @@ specify std.list: - describe foldl: - before: - op = require "std.functional".op - l = List {1, 10, 100} - f = M.foldl + op = require "std.operator" - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.foldl' (function expected, got no value)" - expect (f (f, nil)). - to_error "bad argument #3 to 'std.list.foldl' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.foldl' (function expected, got boolean)" - expect (f (f, nil, false)). - to_error "bad argument #3 to 'std.list.foldl' (List expected, got boolean)" + fname = "foldl" + + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {op["+"], 1, {10}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list.foldl' was deprecated" + end + _, err = capture (f, {op["+"], 1, {10}}) + expect (err).to_be (nil) + + - context with a table: + - it works with an empty table: + expect (f (op["+"], 10000, {})).to_be (10000) + - it folds a binary function through a table: + expect (f (op["+"], 10000, {1, 10, 100})).to_be (10111) + - it folds from left to right: + expect (f (op["^"], 2, {3, 4})).to_be ((2 ^ 3) ^ 4) + + - context with a List: + - it works with an empty List: + expect (f (op["+"], 10000, List {})).to_be (10000) + - it folds a binary function through a List: + expect (f (op["+"], 10000, List {1, 10, 100})). + to_be (10111) + - it folds from left to right: + expect (f (op["^"], 2, List {3, 4})).to_be ((2 ^ 3) ^ 4) + + - context as a List object method: + - before: + l = List {3, 4} + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {l, op["+"], 1}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list:foldl' was deprecated" + end + _, err = capture (f, {l, op["+"], 1}) + expect (err).to_be (nil) - - context when called as a list object method: - it works with an empty list: l = List {} - expect (l:foldl (op["+"], 10000)).to_be (10000) + expect (f (l, op["+"], 2)).to_be (2) - it folds a binary function through a list: - expect (l:foldl (op["+"], 10000)).to_be (10111) + expect (f (l, op["+"], 2)).to_be (9) + - it folds from left to right: + expect (f (l, op["^"], 2)).to_be ((2 ^ 3) ^ 4) - describe foldr: - before: - op = require "std.functional".op - l = List {1, 10, 100} - f = M.foldr + op = require "std.operator" - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.foldr' (function expected, got no value)" - expect (f (f, nil)). - to_error "bad argument #3 to 'std.list.foldr' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.foldr' (function expected, got boolean)" - expect (f (f, nil, false)). - to_error "bad argument #3 to 'std.list.foldr' (List expected, got boolean)" + fname = "foldr" + + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {op["+"], 1, {10}}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list.foldr' was deprecated" + end + _, err = capture (f, {op["+"], 1, {10}}) + expect (err).to_be (nil) + + - context with a table: + - it works with an empty table: + expect (f (op["+"], 10000, {})).to_be (10000) + - it folds a binary function through a table: + expect (f (op["+"], 10000, {1, 10, 100})).to_be (10111) + - it folds from right to left: + expect (f (op["/"], 10, {10000, 100})).to_be (10000 / (100 / 10)) + + - context with a List: + - it works with an empty List: + expect (f (op["+"], 10000, List {})).to_be (10000) + - it folds a binary function through a List: + expect (f (op["+"], 10000, List {1, 10, 100})). + to_be (10111) + - it folds from right to left: + expect (f (op["/"], 10, List {10000, 100})). + to_be (10000 / (100 / 10)) + + - context as a List object method: + - before: + l = List {10000, 100} + f = l[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {l, op["+"], 1}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list:foldr' was deprecated" + end + _, err = capture (f, {l, op["+"], 1}) + expect (err).to_be (nil) - - context when called as a list object method: - it works with an empty list: l = List {} - expect (l:foldl (op["/"], 1)).to_be (1) + expect (f (l, op["+"], 10)).to_be (10) - it folds a binary function through a list: - expect (l:foldl (op["/"], 10000)).to_be (10) + expect (f (l, op["+"], 10)).to_be (10110) + - it folds from right to left: + expect (f (l, op["/"], 10)).to_be (10000 / (100 / 10)) - describe index_key: From 5d3d9d7ae5a65ff64d7f91c1883cc410960cc8e7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 12 Aug 2014 19:06:53 +0100 Subject: [PATCH 346/703] refactor: split out base functions for `std.functional`. * lib/std/base.lua (foldl, foldr, memoize, nop, reduce): Move from here... * lib/std/base/functional.lua (foldl, foldr, memoize, nop) (reduce): New file. ...to here. * lib/std/functional.lua, lib/std/list.lua: Adjust imports accordingly. * local.mk (luastdbasedir, dist_luastdbase_DATA): Install new file correctly. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 94 +++++++++---------------------------- lib/std/base/functional.lua | 85 +++++++++++++++++++++++++++++++++ lib/std/functional.lua | 72 ++++++++++++++-------------- lib/std/list.lua | 2 +- local.mk | 6 +++ 5 files changed, 150 insertions(+), 109 deletions(-) create mode 100644 lib/std/base/functional.lua diff --git a/lib/std/base.lua b/lib/std/base.lua index 1573252..216db90 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -250,69 +250,6 @@ end ---[[ ============================= ]]-- ---[[ Documented in functional.lua. ]]-- ---[[ ============================= ]]-- - - -local function memoize (fn, normalize) - if normalize == nil then - -- Call require here, to avoid pulling in all of 'std.string' - -- even when memoize is never called. - normalize = function (...) return tostring {...} end - end - - return setmetatable ({}, { - __call = function (self, ...) - local k = normalize (...) - local t = self[k] - if t == nil then - t = {fn (...)} - self[k] = t - end - return unpack (t) - end - }) -end - - -local function nop () end - - -local function reduce (f, d, i, ...) - local fn, state, k = i (...) - local t = {fn (state, k)} - - local r = d - while t[1] ~= nil do - r = f (r, t[#t]) - t = {fn (state, t[1])} - end - return r -end - - -local function foldl (fn, d, t) - if t == nil then - local tail = {} - for i = 2, len (d) do tail[#tail + 1] = d[i] end - d, t = d[1], tail - end - return reduce (fn, d, ipairs, t) -end - - -local function foldr (fn, d, t) - if t == nil then - local u, last = {}, len (d) - for i = 1, last - 1 do u[#u + 1] = d[i] end - d, t = d[last], u - end - return reduce (function (x, y) return fn (y, x) end, d, ipairs, ireverse (t)) -end - - - --[[ ========================= ]]-- --[[ Documented in object.lua. ]]-- --[[ ========================= ]]-- @@ -912,7 +849,7 @@ end) --- Metamethods -- @section Metamethods -return { +return setmetatable ({ -- std.lua -- assert = assert, @@ -927,13 +864,6 @@ return { require = require_version, tostring = tostring, - -- functional.lua -- - foldl = foldl, - foldr = foldr, - memoize = memoize, - nop = nop, - reduce = reduce, - -- object.lua -- prototype = prototype, @@ -961,4 +891,24 @@ return { len = len, setcompat = setcompat, toomanyarg_fmt = toomanyarg_fmt, -} + +}, { + + --- Lazy loading of shared base 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 base = require "base" + -- local memoize = base.functional.memoize + __index = function (self, name) + local ok, t = pcall (require, "std.base." .. name) + if ok then + rawset (self, name, t) + return t + end + end, +}) diff --git a/lib/std/base/functional.lua b/lib/std/base/functional.lua new file mode 100644 index 0000000..0ce2fe6 --- /dev/null +++ b/lib/std/base/functional.lua @@ -0,0 +1,85 @@ +--[[-- + Base implementations of functions exported by `std.functional`. + + The only reason to keep these here is to support deprecated access points + to the shared implementations, where they are only loaded when actually + needed, rather than cluttering `std.base`. + + These will be merged back into `std.functional` when the deprecated access + points are no longer supported. + + @module std.base.functional +]] + + +local base = require "std.base" +local operator = require "std.operator" + + +local ipairs, ireverse, len = base.ipairs, base.ireverse, base.len + + +local function memoize (fn, normalize) + if normalize == nil then + -- Call require here, to avoid pulling in all of 'std.string' + -- even when memoize is never called. + normalize = function (...) return require "std.base".tostring {...} end + end + + return setmetatable ({}, { + __call = function (self, ...) + local k = normalize (...) + local t = self[k] + if t == nil then + t = {fn (...)} + self[k] = t + end + return unpack (t) + end + }) +end + + +local function nop () end + + +local function reduce (f, d, i, ...) + local fn, state, k = i (...) + local t = {fn (state, k)} + + local r = d + while t[1] ~= nil do + r = f (r, t[#t]) + t = {fn (state, t[1])} + end + return r +end + + +local function foldl (fn, d, t) + if t == nil then + local tail = {} + for i = 2, len (d) do tail[#tail + 1] = d[i] end + d, t = d[1], tail + end + return reduce (fn, d, ipairs, t) +end + + +local function foldr (fn, d, t) + if t == nil then + local u, last = {}, len (d) + for i = 1, last - 1 do u[#u + 1] = d[i] end + d, t = d[last], u + end + return reduce (function (x, y) return fn (y, x) end, d, ipairs, ireverse (t)) +end + + +return { + foldl = foldl, + foldr = foldr, + memoize = memoize, + nop = nop, + reduce = reduce, +} diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 77b4c34..94ca623 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -11,8 +11,8 @@ local base = require "std.base" local operator = require "std.operator" -local export, ireverse, len, nop, pairs = - base.export, base.ireverse, base.len, base.nop, base.pairs +local export, ireverse, len, pairs = + base.export, base.ireverse, base.len, base.pairs local M = { "std.functional" } @@ -238,6 +238,34 @@ export (M, "filter (func, func, any*)", function (p, i, ...) end) +--- Fold a binary function left associatively. +-- If parameter *d* is omitted, the first element of *t* is used. +-- @function foldl +-- @func fn binary function +-- @param[opt] d initial left-most argument +-- @tparam table t a table +-- @return result +-- @see foldr +-- @see reduce +-- @usage +-- foldl (lambda "/", {10000, 100, 10}) == (10000 / 100) / 10 +export (M, "foldl (function, [any], table)", base.functional.foldl) + + +--- Fold a binary function right associatively. +-- If parameter *d* is omitted, the last element of *t* is used. +-- @function foldr +-- @func fn binary function +-- @param[opt] d initial right-most argument +-- @tparam table t a table +-- @return result +-- @see foldl +-- @see reduce +-- @usage +-- foldr (lambda "/", {10000, 100, 10}) == 10000 / (100 / 10) +export (M, "foldr (function, [any], table)", base.functional.foldr) + + --- Identity function. -- @function id -- @param ... @@ -269,7 +297,7 @@ end -- lambda "<" -- lambda "= _1 < _2" -- lambda "|a,b| a 2 ^ 3 ^ 4 ==> 4096 -- reduce (lambda "^", 2, std.ipairs, {3, 4}) -export (M, "reduce (func, any, func, any*)", base.reduce) - - ---- Fold a binary function left associatively. --- If parameter *d* is omitted, the first element of *t* is used. --- @function foldl --- @func fn binary function --- @param[opt] d initial left-most argument --- @tparam table t a table --- @return result --- @see foldr --- @see reduce --- @usage --- foldl (lambda "/", {10000, 100, 10}) == (10000 / 100) / 10 -export (M, "foldl (function, [any], table)", base.foldl) - - ---- Fold a binary function right associatively. --- If parameter *d* is omitted, the last element of *t* is used. --- @function foldr --- @func fn binary function --- @param[opt] d initial right-most argument --- @tparam table t a table --- @return result --- @see foldl --- @see reduce --- @usage --- foldr (lambda "/", {10000, 100, 10}) == 10000 / (100 / 10) -export (M, "foldr (function, [any], table)", base.foldr) +export (M, "reduce (func, any, func, any*)", base.functional.reduce) -- For backwards compatibility. @@ -422,7 +422,7 @@ M.eval = DEPRECATED ("41", "'std.functional.eval'", M.fold = DEPRECATED ("41", "'std.functional.fold'", - "use 'std.functional.reduce' instead", base.reduce) + "use 'std.functional.reduce' instead", base.functional.reduce) return M @@ -439,7 +439,7 @@ return M -- @treturn string normalized arguments -- @usage -- local normalize = function (name, value, props) return name end --- local intern = std.memoize (mksymbol, normalize) +-- local intern = std.functional.memoize (mksymbol, normalize) --- Signature of a @{filter} predicate callback function. diff --git a/lib/std/list.lua b/lib/std/list.lua index 48880d6..7a990d5 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -37,7 +37,7 @@ local object = require "std.object" local ipairs, pairs = base.ipairs, base.pairs local argcheck, argerror, argscheck, ielems, prototype, ireverse = base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype, base.ireverse -local foldl, foldr = base.foldl, base.foldr +local foldl, foldr = base.functional.foldl, base.functional.foldr local Object = object {} diff --git a/local.mk b/local.mk index 8186560..3126c0a 100644 --- a/local.mk +++ b/local.mk @@ -82,6 +82,12 @@ dist_luastd_DATA = \ lib/std/vector.lua \ $(NOTHING_ELSE) +luastdbasedir = $(luastddir)/base + +dist_luastdbase_DATA = \ + lib/std/base/functional.lua \ + $(NOTHING_ELSE) + # For bugwards compatibility with LuaRocks 2.1, while ensuring that # `require "std.debug_init"` continues to work, we have to install # the former `$(luadir)/std/debug_init.lua` to `debug_init/init.lua`. From 7b0e39cd39c004df058a62801aed6530d1a8b6ae Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 13 Aug 2014 14:25:53 +0100 Subject: [PATCH 347/703] string: remove unused local. * lib/std/string.lua (render): Remove unused local. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index 62a4abf..67126ae 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -25,8 +25,6 @@ local _tostring = base.tostring local M = { "std.string" } -local render -- forward declaration - --[[ ================= ]]-- From 8d946aac40ad172ae846abe041919af304329dc4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 13 Aug 2014 15:00:33 +0100 Subject: [PATCH 348/703] specs: specify `std.list` apis. * specs/list_spec.yaml (std.list): Add specs for exported apis, and global table hygiene. Signed-off-by: Gary V. Vaughan --- specs/list_spec.yaml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index ef1e457..ee26953 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1,5 +1,12 @@ before: - this_module = "std.list" + this_module = "std.list" + global_table = "_G" + + exported_apis = { "append", "compare", "concat", "cons", "depair", + "elems", "enpair", "filter", "flatten", "foldl", + "foldr", "index_key", "index_value", "map", + "map_with", "project", "relems", "rep", "reverse", + "shape", "sub", "tail", "transpose", "zip_with" } M = require (this_module) @@ -13,6 +20,15 @@ specify std.list: - it does not touch the global table: expect (show_apis {added_to="_G", by="std.list"}). 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 via the std module: + - it does not touch the global table: + expect (show_apis {added_to=global_table, by="std"}). + to_equal {} - describe construction: - context from List clone method: From 85efd0fbc33fdabf349903a9fea40e25d876ec89 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 13 Aug 2014 16:48:44 +0100 Subject: [PATCH 349/703] list: base.export module functions for improved argchecks. * lib/std/list.lua (_functions): Rename from this... (M): ...to this. Add a module name entry for export. * specs/list_spec.yaml (exported_apis): Adjust accordingly. (append, compare, concat, filter, flatten, map, project, rep) (sub, tail, transpose, zip_with): Modernize badarg error specifications. Specify "too many arguments" error behaviours. * lib/std/list.lua (cons): Store in M; adjust getcompat/setcompat id. (map_with, project, transpose, zip_with): Use export for improved argchecks. (append, compare, concat, depair, enpair, filter, flatten, map) (rep, shape, sub, tail): Likewise. Remove manual argcheck calls. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 176 +++++++++------------------- specs/list_spec.yaml | 267 +++++++++++++++++++++++++------------------ 2 files changed, 211 insertions(+), 232 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 7a990d5..8f071ec 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -33,16 +33,15 @@ local base = require "std.base" local func = require "std.functional" local object = require "std.object" +local Object = object {} +local List -- forward declaration local ipairs, pairs = base.ipairs, base.pairs -local argcheck, argerror, argscheck, ielems, prototype, ireverse = - base.argcheck, base.argerror, base.argscheck, base.ielems, base.prototype, base.ireverse +local argerror, argscheck, export, ielems, prototype, ireverse = + base.argerror, base.argscheck, base.export, base.ielems, base.prototype, base.ireverse local foldl, foldr = base.functional.foldl, base.functional.foldr - -local Object = object {} - -local List -- forward declaration +local M = { "std.list" } ------ @@ -53,13 +52,11 @@ local List -- forward declaration -- @tparam List l a list -- @param x item -- @treturn List new list containing `{l[1], ..., l[#l], x}` -local function append (l, x) - argscheck ("std.list.append", {"List", "any"}, {l, x}) - +local append = export (M, "append (List, any)", function (l, x) local r = l {} r[#r + 1] = x return r -end +end) --- Compare two lists element-by-element, from left-to-right. @@ -71,9 +68,7 @@ end -- @tparam table m another list -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 -- if `l` is greater than `m` -local function compare (l, m) - argscheck ("std.list.compare", {"List", "List|table"}, {l, m}) - +local compare = export (M, "compare (List, List|table)", function (l, m) for i = 1, math.min (#l, #m) do local li, mi = tonumber (l[i]), tonumber (m[i]) if li == nil or mi == nil then @@ -91,7 +86,7 @@ local function compare (l, m) return 1 end return 0 -end +end) --- Concatenate arguments into a list. @@ -99,15 +94,7 @@ end -- @param ... tuple of lists -- @treturn List new list containing -- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` -local function concat (l, ...) - if _ARGCHECK then - argcheck ("std.list.concat", 1, "List", l) - argcheck ("std.list.concat", 2, "List|table", select (1, ...)) - for i, v in ipairs {...} do - argcheck ("std.list.concat", i + 1, "List|table", v) - end - end - +local concat = export (M, "concat (List, List|table*)", function (l, ...) local r = List {} for e in ielems {l, ...} do for v in ielems (e) do @@ -115,7 +102,7 @@ local function concat (l, ...) end end return r -end +end) --- Prepend an item to a list. @@ -123,12 +110,12 @@ end -- @tparam List l a list -- @param x item -- @treturn List new list containing `{x, unpack (l)}` -local function cons (x, l) +M.cons = function (x, l) if prototype (x) == "List" and prototype (l) ~= "List" then - if not base.getcompat (cons) then + if not base.getcompat (M.cons) then io.stderr:write (base.DEPRECATIONMSG ("41", - "'std.list.cons' with list argument first", 2)) - base.setcompat (cons) + "'std.list.cons' with list argument first", 2)) + base.setcompat (M.cons) end x, l = l, x end @@ -143,19 +130,17 @@ end -- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` -- @see enpair -local function depair (ls) +local depair = export (M, "depair (List|table)", function (ls) if _ARGCHECK then local fname = "std.list.depair" - argcheck (fname, 1, "List|table", ls) - for i, v in ipairs (ls) do local actual = prototype (v) if actual ~= "List" and actual ~= "table" then argerror (fname, 1, "List or table of pairs expected, got " .. - actual .. " at index " .. i, 2) + actual .. " at index " .. i, 2) elseif #v ~= 2 then argerror (fname, 1, "List or table of pairs expected, got " .. - #v .. "-tuple at index " .. i, 2) + #v .. "-tuple at index " .. i, 2) end end end @@ -165,7 +150,7 @@ local function depair (ls) t[v[1]] = v[2] end return t -end +end) --- Turn a table into a list of pairs. @@ -173,15 +158,13 @@ end -- @tparam table t a table `{i1=v1, ..., in=vn}` -- @treturn List a new list containing `{{i1, v1}, ..., {in, vn}}` -- @see depair -local function enpair (t) - argcheck ("std.list.enpair", 1, "table", t) - +export (M, "enpair (table)", function (t) local ls = List {} for i, v in pairs (t) do ls[#ls + 1] = List {i, v} end return ls -end +end) --- Filter a list according to a predicate. @@ -190,20 +173,17 @@ end -- @treturn List new list containing elements `e` of `l` for which -- `p (e)` is true -- @see std.list:filter -local function filter (p, l) - argscheck ("std.list.filter", {"function", "List"}, {p, l}) +local filter = export (M, "filter (function, List)", function (p, l) return List (func.filter (p, ielems, l)) -end +end) --- Flatten a list. -- @tparam List l a list -- @treturn List flattened list -local function flatten (l) - argcheck ("std.list.flatten", 1, "List", l) - +local flatten = export (M, "flatten (List)", function (l) return List (func.collect (base.leaves, ipairs, l)) -end +end) --- Map a function over a list. @@ -211,32 +191,28 @@ end -- @tparam List l a list -- @treturn List new list containing `{fn (l[1]), ..., fn (l[#l])}` -- @see std.list:map -local function map (fn, l) - argscheck ("std.list.map", {"function", "List|table"}, {fn, l}) +local map = export (M, "map (function, List|table)", function (fn, l) return List (func.map (fn, ielems, l)) -end +end) --- Map a function over a list of lists. -- @func fn map function -- @tparam List ls a list of lists -- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` -local function map_with (fn, ls) +local map_with = export (M, "map_with (function, List)", function (fn, ls) if _ARGCHECK then - local fname = "std.list.map_with" - argscheck (fname, {"function", "List"}, {fn, ls}) - for i, v in ipairs (ls) do local actual = prototype (v) if actual ~= "List" then - argerror (fname, 2, "List of Lists expected, got " .. + argerror ("std.list.map_with", 2, "List of Lists expected, got " .. actual .. " at index " .. i, 2) end end end return List (func.map (func.compose (unpack, fn), ielems, ls)) -end +end) --- Project a list of fields from a list of tables. @@ -244,38 +220,32 @@ end -- @tparam List l a list of tables -- @treturn List list of `f` fields -- @see std.list:project -local function project (f, l) +local project = export (M, "project (any, List)", function (f, l) if _ARGCHECK then - local fname = "std.list.project" - argcheck (fname, 2, "List", l) - for i, v in ipairs (l) do local actual = prototype (v) if actual ~= "table" then - argerror (fname, 2, "List of tables expected, got " .. + argerror ("std.list.project", 2, "List of tables expected, got " .. actual .. " at index " .. i, 2) end end end - return map (function (t) return t[f] end, l) -end +end) --- Repeat a list. -- @tparam List l a list -- @int n number of times to repeat -- @treturn List `n` copies of `l` appended together -local function rep (l, n) - argscheck ("std.list.rep", {"List", "int"}, {l, n}) - +local rep = export (M, "rep (List, int)", function (l, n) local r = List {} for i = 1, n do r = concat (r, l) end return r -end +end) --- Shape a list according to a list of dimensions. @@ -298,9 +268,7 @@ end -- @tparam List l a list -- @return reshaped list -- @see std.list:shape -local function shape (s, l) - argscheck ("std.list.shape", {"table", "List"}, {s, l}) - +local shape = export (M, "shape (table, List)", function (s, l) l = flatten (l) -- Check the shape and calculate the size of the zero, if any local size = 1 @@ -333,7 +301,7 @@ local function shape (s, l) end end return (fill (1, 1)) -end +end) --- Return a sub-range of a list. @@ -343,9 +311,7 @@ end -- @int from start of range (default: 1) -- @int to end of range (default: `#l`) -- @treturn List new list containing `{l[from], ..., l[to]}` -local function sub (l, from, to) - argscheck ("std.list.sub", {"List", "int?", "int?"}, {l, from, to}) - +local sub = export (M, "sub (List, int?, int?)", function (l, from, to) local r = List {} local len = #l from = from or 1 @@ -360,17 +326,15 @@ local function sub (l, from, to) r[#r + 1] = l[i] end return r -end +end) --- Return a list with its first element removed. -- @tparam List l a list -- @treturn List new list containing `{l[2], ..., l[#l]}` -local function tail (l) - argcheck ("std.list.tail", 1, "List", l) - +local tail = export (M, "tail (List)", function (l) return sub (l, 2) -end +end) --- Transpose a list of lists. @@ -380,15 +344,12 @@ end -- `{{ls<1,1>, ..., ls<1,c>}, ..., {ls<r,1>, ..., ls<r,c>}}` -- @treturn List new list containing -- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` -local function transpose (ls) +local transpose = export (M, "transpose (table|List)", function (ls) if _ARGCHECK then - local fname = "std.list.transpose" - argcheck (fname, 1, "table|List", ls) - for i, v in ipairs (ls) do local actual = prototype (v) if actual ~= "List" then - argerror (fname, 1, "List or table of Lists expected, got " .. + argerror ("std.list.transpose", 1, "List or table of Lists expected, got " .. actual .. " at index " .. i, 2) end end @@ -404,7 +365,7 @@ local function transpose (ls) end end return rs -end +end) --- Zip a list of lists together with a function. @@ -413,44 +374,19 @@ end -- @treturn List a new list containing -- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` -- where `N = max {map (function (l) return #l end, ls)}` -local function zip_with (ls, fn) +local zip_with = export (M, "zip_with (List, function)", function (ls, fn) if _ARGCHECK then - local fname = "std.list.zip_with" - argscheck (fname, {"List", "function"}, {ls, fn}) - for i, v in ipairs (ls) do local actual = prototype (v) if actual ~= "List" then - argerror (fname, 1, + argerror ("std.list.zip_with", 1, "List of Lists expected, got " .. actual .. " at index " .. i, 2) end end end return map_with (fn, transpose (ls)) -end - - ---- @export -local _functions = { - append = append, - compare = compare, - concat = concat, - cons = cons, - depair = depair, - enpair = enpair, - filter = filter, - flatten = flatten, - map = map, - map_with = map_with, - project = project, - rep = rep, - shape = shape, - sub = sub, - tail = tail, - transpose = transpose, - zip_with = zip_with, -} +end) @@ -462,21 +398,21 @@ local _functions = { local DEPRECATED = base.DEPRECATED -_functions.elems = DEPRECATED ("41", "'std.list.elems'", +M.elems = DEPRECATED ("41", "'std.list.elems'", "use 'std.ielems' instead", base.ielems) local function relems (l) return base.ielems (base.ireverse (l)) end -_functions.relems = DEPRECATED ("41", "'std.list.relems'", +M.relems = DEPRECATED ("41", "'std.list.relems'", "compose 'std.ielems' and 'std.ireverse' instead", relems) -_functions.foldl = DEPRECATED ("41", "'std.list.foldl'", +M.foldl = DEPRECATED ("41", "'std.list.foldl'", "use 'std.functional.foldl' instead", foldl) -_functions.foldr = DEPRECATED ("41", "'std.list.foldr'", +M.foldr = DEPRECATED ("41", "'std.list.foldr'", "use 'std.functional.foldr' instead", foldr) @@ -491,7 +427,7 @@ local function index_key (f, l) return r end -_functions.index_key = DEPRECATED ("41", "'std.list.index_key'", +M.index_key = DEPRECATED ("41", "'std.list.index_key'", "compose 'std.list.filter' and 'std.table.invert' instead", index_key) @@ -506,13 +442,13 @@ local function index_value (f, l) return r end -_functions.index_value = DEPRECATED ("41", "'std.list.index_value'", +M.index_value = DEPRECATED ("41", "'std.list.index_value'", "compose 'std.list.filter' and 'std.table.invert' instead", index_value) local function reverse (l) return List (ireverse (l)) end -_functions.reverse = DEPRECATED ("41", "'std.list.reverse'", +M.reverse = DEPRECATED ("41", "'std.list.reverse'", "use 'std.ireverse' instead", reverse) @@ -587,7 +523,7 @@ List = Object { -- @function cons -- @param x item -- @treturn List new list containing `{x, unpack (self)}` - cons = function (self, x) return cons (x, self) end, + cons = function (self, x) return M.cons (x, self) end, ------ -- Filter a list according to a predicate. @@ -681,7 +617,7 @@ List = Object { }, - _functions = _functions, + _functions = M, } diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index ee26953..2ca61c0 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -2,7 +2,7 @@ before: this_module = "std.list" global_table = "_G" - exported_apis = { "append", "compare", "concat", "cons", "depair", + exported_apis = { 1, "append", "compare", "concat", "cons", "depair", "elems", "enpair", "filter", "flatten", "foldl", "foldr", "index_key", "index_value", "map", "map_with", "project", "relems", "rep", "reverse", @@ -75,15 +75,19 @@ specify std.list: - describe append: - before: - f = M.append - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.append' (List expected, got no value)" - expect (f (l)). - to_error "bad argument #2 to 'std.list.append' (any value expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.append' (List expected, got boolean)" + fname = "append" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "List")) + expect (f (l)).to_error (msg (2, "any value")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 42, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - context when called as a list object method: - it returns a list object: l = l:append ("quux") @@ -109,17 +113,21 @@ specify std.list: - describe compare: - before: a, b = List {"foo", "bar"}, List {"foo", "baz"} - f = M.compare - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.compare' (List expected, got no value)" - expect (f (l)). - to_error "bad argument #2 to 'std.list.compare' (List or table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.compare' (List expected, got boolean)" - expect (f (a, false)). - to_error "bad argument #2 to 'std.list.compare' (List or table expected, got boolean)" + + fname = "compare" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "List")) + expect (f (l)).to_error (msg (2, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "List", "boolean")) + expect (f (a, false)).to_error (msg (2, "List or table", "boolean")) + - it diagnoses too many arguments: + expect (f (a, b, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + - context when called as a list object method: - it returns -1 when the first list is less than the second: | expect (a:compare {"foo", "baz"}).to_be (-1) @@ -181,19 +189,19 @@ specify std.list: - describe concat: - before: l = List {"foo", "bar"} - f = M.concat - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.concat' (List expected, got no value)" - expect (f (l)). - to_error "bad argument #2 to 'std.list.concat' (List or table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.concat' (List expected, got boolean)" - expect (f (l, false)). - to_error "bad argument #2 to 'std.list.concat' (List or table expected, got boolean)" + + fname = "concat" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "List")) + expect (f (l)).to_error (msg (2, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "List", "boolean")) + expect (f (l, false)).to_error (msg (2, "List or table", "boolean")) expect (f (l, l, false)). - to_error "bad argument #3 to 'std.list.concat' (List or table expected, got boolean)" + to_error (msg (3, "List or table", "boolean")) - context when called as a list object method: - it returns a list object: @@ -207,7 +215,7 @@ specify std.list: to_equal (List {"foo", "bar", "baz", "quux"}) expect (l:concat (List {"baz"}, List {"quux"})). to_equal (List {"foo", "bar", "baz", "quux"}) - - context whne called as a list metamethod: + - context when called as a list metamethod: - it returns a list object: l = l .. List {"baz"} expect (prototype (l)).to_be "List" @@ -391,8 +399,11 @@ specify std.list: - it diagnoses missing arguments: expect (f ()).to_error (msg (1, "table")) - - it diagnoses wrong argument types: | + - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) - it returns a list object: expect (prototype (f (t))).to_be "List" @@ -407,18 +418,20 @@ specify std.list: - before: l = List {"foo", "bar", "baz", "quux"} p = function (e) return (e:match "a" ~= nil) end - f = M.filter + + fname = "filter" + msg = bind (badarg, {this_module, fname}) + f = M[fname] - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.filter' (function expected, got no value)" - expect (f (f)). - to_error "bad argument #2 to 'std.list.filter' (List expected, got no value)" + expect (f ()).to_error (msg (1, "function")) + expect (f (p)).to_error (msg (2, "List")) - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.filter' (function expected, got boolean)" - expect (f (f, false)). - to_error "bad argument #2 to 'std.list.filter' (List expected, got boolean)" + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (p, false)).to_error (msg (2, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (p, l, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) - context when called as a list object method: - it returns a list object: @@ -434,14 +447,18 @@ specify std.list: - describe flatten: - before: l = List {List {List {"one"}}, "two", List {List {"three"}, "four"}} - f = M.flatten - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.flatten' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.flatten' (List expected, got boolean)" + fname = "flatten" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) - context when called as a list object method: - it returns a list object: @@ -737,18 +754,20 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5} sq = function (n) return n * n end - f = M.map - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.map' (function expected, got no value)" - expect (f (f)). - to_error "bad argument #2 to 'std.list.map' (List or table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.map' (function expected, got boolean)" - expect (f (f, false)). - to_error "bad argument #2 to 'std.list.map' (List or table expected, got boolean)" + fname = "map" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (sq)).to_error (msg (2, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (sq, false)).to_error (msg (2, "List or table", "boolean")) + - it diagnoses too many arguments: + expect (f (sq, l, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) - context when called as a list object method: - it returns a list object: @@ -789,6 +808,9 @@ specify std.list: to_error (msg (2, "List of Lists", "table at index 1")) expect (f (fn, List {List {}, false})). to_error (msg (2, "List of Lists", "boolean at index 2")) + - it diagnoses too many arguments: + expect (f (fn, l, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) - it returns a list object: m = f (fn, l) @@ -841,18 +863,23 @@ specify std.list: {first = 1, second = 2, third = 3}, {first = "1st", second = "2nd", third = "3rd"}, } - f = M.project - - it diagnoses missing arguments: | - expect (f (f)). - to_error "bad argument #2 to 'std.list.project' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (f, false)). - to_error "bad argument #2 to 'std.list.project' (List expected, got boolean)" + fname = "project" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "any value")) + expect (f (f)).to_error (msg (2, "List")) + - it diagnoses wrong argument types: + expect (f (f, false)).to_error (msg (2, "List", "boolean")) expect (f (f, List {false})). - to_error "bad argument #2 to 'std.list.project' (List of tables expected, got boolean at index 1)" + to_error (msg (2, "List of tables", "boolean at index 1")) expect (f (f, List {{}, false})). - to_error "bad argument #2 to 'std.list.project' (List of tables expected, got boolean at index 2)" + to_error (msg (2, "List of tables", "boolean at index 2")) + - it diagnoses too many arguments: + expect (f (f, l, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) - context when called as a list object method: - it returns a list object: @@ -925,18 +952,20 @@ specify std.list: - describe rep: - before: l = List {"foo", "bar"} - f = M.rep - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.rep' (List expected, got no value)" - expect (f (l)). - to_error "bad argument #2 to 'std.list.rep' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.rep' (List expected, got boolean)" - expect (f (l, false)). - to_error "bad argument #2 to 'std.list.rep' (int expected, got boolean)" + fname = "rep" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "List")) + expect (f (l)).to_error (msg (2, "int")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "List", "boolean")) + expect (f (l, false)).to_error (msg (2, "int", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 2, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) - context when called as a list object method: - it returns a list object: @@ -1012,18 +1041,20 @@ specify std.list: - describe shape: - before: l = List {1, 2, 3, 4, 5, 6} - f = M.shape - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.shape' (table expected, got no value)" - expect (f ({})). - to_error "bad argument #2 to 'std.list.shape' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.shape' (table expected, got boolean)" - expect (f ({}, false)). - to_error "bad argument #2 to 'std.list.shape' (List expected, got boolean)" + fname = "shape" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + expect (f ({})).to_error (msg (2, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f ({}, false)).to_error (msg (2, "List", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, l, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) - context when called as a list object method: - it returns a list object: | @@ -1051,18 +1082,20 @@ specify std.list: - describe sub: - before: l = List {1, 2, 3, 4, 5, 6, 7} - f = M.sub - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.sub' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.sub' (List expected, got boolean)" - expect (f (l, false)). - to_error "bad argument #2 to 'std.list.sub' (int or nil expected, got boolean)" - expect (f (l, 1, false)). - to_error "bad argument #3 to 'std.list.sub' (int or nil expected, got boolean)" + fname = "sub" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "List", "boolean")) + expect (f (l, false)).to_error (msg (2, "int or nil", "boolean")) + expect (f (l, 1, false)).to_error (msg (3, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 1, 2, false)). + to_error (toomanyarg (this_module, fname, 3, 4)) - context when called as a list object method: - it returns a list object: | @@ -1086,14 +1119,18 @@ specify std.list: - describe tail: - before: l = List {1, 2, 3, 4, 5, 6, 7} - f = M.tail - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.list.tail' (List expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.list.tail' (List expected, got boolean)" + fname = "tail" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) - context when called as a list object method: - it returns a list object: | @@ -1120,8 +1157,11 @@ specify std.list: - it diagnoses missing arguments: expect (f ()).to_error (msg (1, "table or List")) - - it diagnoses wrong argument types: | + - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "table or List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) - it transposes rows and columns: expect (f (l)).to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) @@ -1178,6 +1218,9 @@ specify std.list: expect (f (List {List {}, false}, f)). to_error (msg (1, "List of Lists", "boolean at index 2")) expect (f (l, false)).to_error (msg (2, "function", "boolean")) + - it diagnoses too many arguments: + expect (f (l, fn, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) - it returns a list object: expect (prototype (f (l, fn))).to_be "List" From af4c6fc07338fecf45d4c08e940d381446f63e91 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 13 Aug 2014 20:58:46 +0100 Subject: [PATCH 350/703] list: update LDocs. * lib/std/list.lua: Add @function and @static keywords. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/std/list.lua b/lib/std/list.lua index 8f071ec..708ca44 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -49,6 +49,8 @@ local M = { "std.list" } -- @table List --- Append an item to a list. +-- @static +-- @function append -- @tparam List l a list -- @param x item -- @treturn List new list containing `{l[1], ..., l[#l], x}` @@ -90,6 +92,8 @@ end) --- Concatenate arguments into a list. +-- @static +-- @function concat -- @tparam List l a list -- @param ... tuple of lists -- @treturn List new list containing @@ -106,6 +110,7 @@ end) --- Prepend an item to a list. +-- @static -- @function cons -- @tparam List l a list -- @param x item @@ -127,6 +132,8 @@ end --- Turn a list of pairs into a table. -- @todo Find a better name. +-- @static +-- @function depair -- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` -- @see enpair @@ -155,6 +162,8 @@ end) --- Turn a table into a list of pairs. -- @todo Find a better name. +-- @static +-- @function enpair -- @tparam table t a table `{i1=v1, ..., in=vn}` -- @treturn List a new list containing `{{i1, v1}, ..., {in, vn}}` -- @see depair @@ -168,6 +177,8 @@ end) --- Filter a list according to a predicate. +-- @static +-- @function filter -- @func p predicate function, of one argument returning a boolean -- @tparam List l a list -- @treturn List new list containing elements `e` of `l` for which @@ -179,6 +190,8 @@ end) --- Flatten a list. +-- @static +-- @function flatten -- @tparam List l a list -- @treturn List flattened list local flatten = export (M, "flatten (List)", function (l) @@ -187,6 +200,8 @@ end) --- Map a function over a list. +-- @static +-- @function map -- @func fn map function -- @tparam List l a list -- @treturn List new list containing `{fn (l[1]), ..., fn (l[#l])}` @@ -197,6 +212,8 @@ end) --- Map a function over a list of lists. +-- @static +-- @function map_with -- @func fn map function -- @tparam List ls a list of lists -- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` @@ -216,6 +233,8 @@ end) --- Project a list of fields from a list of tables. +-- @static +-- @function project -- @param f field to project -- @tparam List l a list of tables -- @treturn List list of `f` fields @@ -236,6 +255,8 @@ end) --- Repeat a list. +-- @static +-- @function rep -- @tparam List l a list -- @int n number of times to repeat -- @treturn List `n` copies of `l` appended together @@ -264,6 +285,8 @@ end) -- -- @todo Use ileaves instead of flatten (needs a while instead of a -- for in fill function) +-- @static +-- @function shape -- @tparam table s `{d1, ..., dn}` -- @tparam List l a list -- @return reshaped list @@ -307,6 +330,8 @@ end) --- Return a sub-range of a list. -- (The equivalent of `string.sub` on strings; negative list indices -- count from the end of the list.) +-- @static +-- @function sub -- @tparam List l a list -- @int from start of range (default: 1) -- @int to end of range (default: `#l`) @@ -330,6 +355,8 @@ end) --- Return a list with its first element removed. +-- @static +-- @function tail -- @tparam List l a list -- @treturn List new list containing `{l[2], ..., l[#l]}` local tail = export (M, "tail (List)", function (l) @@ -340,6 +367,8 @@ end) --- Transpose a list of lists. -- This function in Lua is equivalent to zip and unzip in more strongly -- typed languages. +-- @static +-- @function transpose -- @tparam table ls -- `{{ls<1,1>, ..., ls<1,c>}, ..., {ls<r,1>, ..., ls<r,c>}}` -- @treturn List new list containing @@ -369,6 +398,8 @@ end) --- Zip a list of lists together with a function. +-- @static +-- @function zip_with -- @tparam table ls list of lists -- @tparam function fn function -- @treturn List a new list containing From 0bfbc1ee14de55d13a083b47525e020ca84000c4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 13 Aug 2014 20:38:26 +0100 Subject: [PATCH 351/703] debug: support "container of homogenous_thing" in argcheck. * specs/debug_spec.yaml (argcheck): Specify behaviours when checking for combinations of "List of table" variations. * lib/std/base.lua (formaterror): Add optional index parameter, and diagnose errors that use it with new "type at index N" format; otherwise, simplify "List of table" strings in expectedtypes to just "List" when the error is in the outer type matching. (checktype): Abstracted out of `argcheck`. (argcheck): Simplify accordingly. Detect and diagnose element mismatches with "List of table" expected types. (export): Likewise. (typeof): Factored out entirely. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 161 +++++++++++++-------- specs/debug_spec.yaml | 327 ++++++++++++++++++++++++++++-------------- 2 files changed, 326 insertions(+), 162 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 216db90..cbf79e6 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -458,7 +458,7 @@ end -- @tparam table expectedtypes a table of matchable types -- @string actual the actual argument to match with -- @treturn string formatted *extramsg* for this mismatch for @{argerror} -local function formaterror (expectedtypes, actual) +local function formaterror (expectedtypes, actual, index) local actualtype = prototype (actual) -- Tidy up actual type for display. @@ -475,6 +475,10 @@ local function formaterror (expectedtypes, actual) end end + if index then + actualtype = actualtype .. " at index " .. tostring (index) + end + -- Tidy up expected types for display. local t = {} for i, v in ipairs (expectedtypes) do @@ -482,6 +486,8 @@ local function formaterror (expectedtypes, actual) t[i] = "function" elseif v == "any" then t[i] = "any value" + elseif not index then + t[i] = v:match "(%S+) of %S+" or v else t[i] = v end @@ -489,78 +495,103 @@ local function formaterror (expectedtypes, actual) local expectedstr = concat (t): gsub ("#table", "non-empty table"): - gsub ("#list", "non-empty list") + gsub ("#list", "non-empty list"): + gsub ("(%S+ of %S+)", "%1s"): + gsub ("(%S+ of %S+)ss", "%1s") return expectedstr .. " expected, got " .. actualtype end -if _ARGCHECK then +--- Compare *check* against type of *actual* +-- @string check extended type name expected +-- @param actual object being typechecked +-- @treturn boolean `true` if *actual* is of type *check*, otherwise +-- `false` +local function checktype (check, actual) + local actualtype = prototype (actual) + if check == "#table" then + if actualtype == "table" and next (actual) then + return true + end - local typeof = type -- free up `type` for use as a variable + elseif check == "any" then + if actual ~= nil then + return true + end - -- Doc-commented in debug.lua - function argcheck (name, i, expected, actual, level) - level = level or 2 - expected = normalize (split (expected, "|")) + elseif check == "file" then + if io.type (actual) == "file" then + return true + end - -- Check actual has one of the types from expected - local ok, actualtype = false, prototype (actual) - for i, check in ipairs (expected) do - if check == "#table" then - if actualtype == "table" and next (actual) then - ok = true - end + elseif check == "function" or check == "func" then + if actualtype == "function" or + (getmetatable (actual) or {}).__call ~= nil + then + return true + end - elseif check == "any" then - if actual ~= nil then - ok = true - end + elseif check == "int" then + if actualtype == "number" and actual == math.floor (actual) then + return true + end - elseif check == "file" then - if io.type (actual) == "file" then - ok = true - end + elseif check == "list" or check == "#list" then + if actualtype == "table" or actualtype == "List" then + local len, count = #actual, 0 + local i = next (actual) + repeat + if i ~= nil then count = count + 1 end + i = next (actual, i) + until i == nil or count > len + if count == len and (check == "list" or count > 0) then + return true + end + end - elseif check == "function" or check == "func" then - if actualtype == "function" or - (getmetatable (actual) or {}).__call ~= nil - then - ok = true - end + elseif check == "object" then + if actualtype ~= "table" and type (actual) == "table" then + return true + end - elseif check == "int" then - if actualtype == "number" and actual == math.floor (actual) then - ok = true - end + elseif type (check) == "string" and check:sub (1, 1) == ":" then + if check == actual then + return true + end - elseif check == "list" or check == "#list" then - if actualtype == "table" or actualtype == "List" then - local len, count = #actual, 0 - local i = next (actual) - repeat - if i ~= nil then count = count + 1 end - i = next (actual, i) - until i == nil or count > len - if count == len and (check == "list" or count > 0) then - ok = true - end - end + elseif check == actualtype then + return true + end - elseif check == "object" then - if actualtype ~= "table" and typeof (actual) == "table" then - ok = true - end + return false +end - elseif typeof (check) == "string" and check:sub (1, 1) == ":" then - if check == actual then - ok = true - end - elseif check == actualtype then - ok = true - end +if _ARGCHECK then + + -- Doc-commented in debug.lua + function argcheck (name, i, expected, actual, level) + level = level or 2 + expected = normalize (split (expected, "|")) + -- Check actual has one of the types from expected + local ok = false + for _, expect in ipairs (expected) do + local check, contents = expect:match "^(%S+) of (%S-)s?$" + check = check or expect + + -- Does the type of actual check out? + ok = checktype (check, actual) + + -- For "table of things", check all elements are a thing too. + if ok and contents and type (actual) == "table" then + for k, v in pairs (actual) do + if not checktype (contents, v) then + argerror (name, i, formaterror (expected, v, k), level + 1) + end + end + end if ok then break end end @@ -572,8 +603,8 @@ if _ARGCHECK then -- Doc-commented in debug.lua function argscheck (name, expected, actual) - if typeof (expected) ~= "table" then expected = {expected} end - if typeof (actual) ~= "table" then actual = {actual} end + if type (expected) ~= "table" then expected = {expected} end + if type (actual) ~= "table" then actual = {actual} end for i, v in ipairs (expected) do argcheck (name, i, expected[i], actual[i], 3) @@ -751,6 +782,20 @@ local function export (M, decl, fn, ...) expected = merge (unpack (tables)) end local i = bestmismatch + + -- For "table of things", check all elements are a thing too. + if types[i] then + local check, contents = types[i]:match "^(%S+) of (%S-)s?$" + if contents and type (args[i]) == "table" then + for k, v in pairs (args[i]) do + if not checktype (contents, v) then + argerror (name, i, formaterror (expected, v, k), 2) + end + end + end + end + + -- Otherwise the argument type itself was mismatched. argerror (name, i, formaterror (expected, args[i]), 2) end diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index c987c9d..b8e7906 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -142,110 +142,229 @@ specify std.debug: - it is not disabled by leaving _DEBUG.argcheck unset: expect (luaproc (mkstack (nil, "{}"))). to_contain_error "bad argument" - - it diagnoses missing primitive types: - expect (fn ("boolean", nil)).to_error "boolean expected, got no value" - expect (fn ("file", nil)).to_error "file expected, got no value" - expect (fn ("number", nil)).to_error "number expected, got no value" - expect (fn ("string", nil)).to_error "string expected, got no value" - expect (fn ("table", nil)).to_error "table expected, got no value" - - it diagnoses mismatched primitive types: - expect (fn ("boolean", {0})).to_error "boolean expected, got table" - expect (fn ("file", {0})).to_error "file expected, got table" - expect (fn ("number", {0})).to_error "number expected, got table" - expect (fn ("string", {0})).to_error "string expected, got table" - expect (fn ("table", false)).to_error "table expected, got boolean" - expect (fn ("table", require "std.object")). - to_error "table expected, got Object" - - it matches primitive Lua types: - expect (fn ("boolean", true)).not_to_error () - expect (fn ("file", io.stderr)).not_to_error () - expect (fn ("number", 1)).not_to_error () - expect (fn ("string", "s")).not_to_error () - expect (fn ("table", {})).not_to_error () - - it diagnoses missing int types: - expect (fn ("int", nil)).to_error "int expected, got no value" - - it diagnoses mismatched int types: - expect (fn ("int", false)).to_error "int expected, got boolean" - expect (fn ("int", 1.234)).to_error "int expected, got number" - expect (fn ("int", 1234e-3)).to_error "int expected, got number" - - it matches int types: - expect (fn ("int", 1)).not_to_error () - expect (fn ("int", 1.0)).not_to_error () - expect (fn ("int", 0x1234)).not_to_error () - expect (fn ("int", 1.234e3)).not_to_error () - - it diagnoses missing constant string types: - expect (fn (":foo", nil)).to_error ":foo expected, got no value" - - it diagnoses mismatched constant string types: - expect (fn (":foo", false)).to_error ":foo expected, got boolean" - expect (fn (":foo", ":bar")).to_error ":foo expected, got :bar" - expect (fn (":foo", "foo")).to_error ":foo expected, got string" - - it matches constant string types: - expect (fn (":foo", ":foo")).not_to_error () - - it diagnoses missing callable types: - expect (fn ("function", nil)).to_error "function expected, got no value" - - it diagnoses mismatched callable types: - expect (fn ("function", {0})).to_error "function expected, got table" - - it matches callable types: - expect (fn ("function", function () end)).not_to_error () - expect (fn ("function", setmetatable ({}, {__call = function () end}))). - not_to_error () - - it diagnoses missing non-empty table types: - expect (fn ("#table", nil)). - to_error "non-empty table expected, got no value" - - it diagnoses mismatched non-empty table types: - expect (fn ("#table", false)). - to_error "non-empty table expected, got boolean" - expect (fn ("#table", {})). - to_error "non-empty table expected, got empty table" - - it matches non-empty table types: - expect (fn ("#table", {0})).not_to_error () - - it diagnonses missing list types: - expect (fn ("list", nil)). - to_error "list expected, got no value" - - it diagnoses mismatched list types: - expect (fn ("list", false)). - to_error "list expected, got boolean" - expect (fn ("list", {foo=1})). - to_error "list expected, got table" - expect (fn ("list", Object)). - to_error "list expected, got Object" - - it matches list types: - expect (fn ("list", {})).not_to_error () - expect (fn ("list", {1})).not_to_error () - - it diagnonses missing non-empty list types: - expect (fn ("#list", nil)). - to_error "non-empty list expected, got no value" - - it diagnoses mismatched non-empty list types: - expect (fn ("#list", false)). - to_error "non-empty list expected, got boolean" - expect (fn ("#list", {})). - to_error "non-empty list expected, got empty list" - expect (fn ("#list", {foo=1})). - to_error "non-empty list expected, got table" - expect (fn ("#list", Object)). - to_error "non-empty list expected, got empty Object" - expect (fn ("#list", List {})). - to_error "non-empty list expected, got empty List" - - it matches non-empty list types: - expect (fn ("#list", {1})).not_to_error () - - it diagnoses missing object types: - expect (fn ("object", nil)).to_error "object expected, got no value" - expect (fn ("Object", nil)).to_error "Object expected, got no value" - expect (fn ("Foo", nil)).to_error "Foo expected, got no value" - expect (fn ("any", nil)).to_error "any value expected, got no value" - - it diagnoses mismatched object types: - expect (fn ("object", {0})).to_error "object expected, got table" - expect (fn ("Object", {0})).to_error "Object expected, got table" - expect (fn ("object", {_type="Object"})).to_error "object expected, got table" - expect (fn ("Object", {_type="Object"})).to_error "Object expected, got table" - expect (fn ("Object", Foo)).to_error "Object expected, got Foo" - expect (fn ("Foo", {0})).to_error "Foo expected, got table" - expect (fn ("Foo", Object)).to_error "Foo expected, got Object" - - it matches object types: - expect (fn ("object", Object)).not_to_error () - expect (fn ("object", Object {})).not_to_error () - expect (fn ("object", Foo)).not_to_error () - expect (fn ("object", Foo {})).not_to_error () + + - context with primitives: + - it diagnoses missing types: + expect (fn ("boolean", nil)).to_error "boolean expected, got no value" + expect (fn ("file", nil)).to_error "file expected, got no value" + expect (fn ("number", nil)).to_error "number expected, got no value" + expect (fn ("string", nil)).to_error "string expected, got no value" + expect (fn ("table", nil)).to_error "table expected, got no value" + - it diagnoses mismatched types: + expect (fn ("boolean", {0})).to_error "boolean expected, got table" + expect (fn ("file", {0})).to_error "file expected, got table" + expect (fn ("number", {0})).to_error "number expected, got table" + expect (fn ("string", {0})).to_error "string expected, got table" + expect (fn ("table", false)).to_error "table expected, got boolean" + expect (fn ("table", require "std.object")). + to_error "table expected, got Object" + - it matches types: + expect (fn ("boolean", true)).not_to_error () + expect (fn ("file", io.stderr)).not_to_error () + expect (fn ("number", 1)).not_to_error () + expect (fn ("string", "s")).not_to_error () + expect (fn ("table", {})).not_to_error () + - context with int: + - it diagnoses missing types: + expect (fn ("int", nil)).to_error "int expected, got no value" + - it diagnoses mismatched types: + expect (fn ("int", false)).to_error "int expected, got boolean" + expect (fn ("int", 1.234)).to_error "int expected, got number" + expect (fn ("int", 1234e-3)).to_error "int expected, got number" + - it matches types: + expect (fn ("int", 1)).not_to_error () + expect (fn ("int", 1.0)).not_to_error () + expect (fn ("int", 0x1234)).not_to_error () + expect (fn ("int", 1.234e3)).not_to_error () + - context with constant string: + - it diagnoses missing types: + expect (fn (":foo", nil)).to_error ":foo expected, got no value" + - it diagnoses mismatched types: + expect (fn (":foo", false)).to_error ":foo expected, got boolean" + expect (fn (":foo", ":bar")).to_error ":foo expected, got :bar" + expect (fn (":foo", "foo")).to_error ":foo expected, got string" + - it matches types: + expect (fn (":foo", ":foo")).not_to_error () + - context with callable types: + - it diagnoses missing types: + expect (fn ("function", nil)).to_error "function expected, got no value" + - it diagnoses mismatched types: + expect (fn ("function", {0})).to_error "function expected, got table" + - it matches types: + expect (fn ("function", function () end)).not_to_error () + expect (fn ("function", setmetatable ({}, {__call = function () end}))). + not_to_error () + - context with table of homogenous elements: + - it diagnoses missing types: + expect (fn ("table of boolean", nil)). + to_error "table expected, got no value" + expect (fn ("table of booleans", nil)). + to_error "table expected, got no value" + - it diagnoses mismatched types: + expect (fn ("table of file", io.stderr)). + to_error "table expected, got file" + expect (fn ("table of files", io.stderr)). + to_error "table expected, got file" + - it diagnoses mismatched element types: + expect (fn ("table of number", {false})). + to_error "table of numbers expected, got boolean at index 1" + expect (fn ("table of numbers", {1, 2, "3"})). + to_error "table of numbers expected, got string at index 3" + expect (fn ("table of numbers", {a=1, b=2, c="3"})). + to_error "table of numbers expected, got string at index c" + - it matches types: + expect (fn ("table of string", {})).not_to_error () + expect (fn ("table of string", {"foo"})).not_to_error () + expect (fn ("table of string", {"f", "o", "o"})).not_to_error () + expect (fn ("table of string", {b="b", a="a", r="r"})).not_to_error () + - context with non-empty table types: + - it diagnoses missing types: + expect (fn ("#table", nil)). + to_error "non-empty table expected, got no value" + - it diagnoses mismatched types: + expect (fn ("#table", false)). + to_error "non-empty table expected, got boolean" + expect (fn ("#table", {})). + to_error "non-empty table expected, got empty table" + - it matches types: + expect (fn ("#table", {0})).not_to_error () + - context with non-empty table of homogenous elements: + - it diagnoses missing types: + expect (fn ("#table of boolean", nil)). + to_error "non-empty table expected, got no value" + expect (fn ("#table of booleans", nil)). + to_error "non-empty table expected, got no value" + - it diagnoses mismatched types: + expect (fn ("#table of file", {})). + to_error "non-empty table expected, got empty table" + expect (fn ("#table of file", io.stderr)). + to_error "non-empty table expected, got file" + - it diagnoses mismatched element types: + expect (fn ("#table of number", {false})). + to_error "non-empty table of numbers expected, got boolean at index 1" + expect (fn ("#table of numbers", {1, 2, "3"})). + to_error "non-empty table of numbers expected, got string at index 3" + expect (fn ("#table of numbers", {a=1, b=2, c="3"})). + to_error "non-empty table of numbers expected, got string at index c" + - it matches types: + expect (fn ("#table of string", {"foo"})).not_to_error () + expect (fn ("#table of string", {"f", "o", "o"})).not_to_error () + expect (fn ("#table of string", {b="b", a="a", r="r"})).not_to_error () + - context with list: + - it diagnonses missing types: + expect (fn ("list", nil)). + to_error "list expected, got no value" + - it diagnoses mismatched types: + expect (fn ("list", false)). + to_error "list expected, got boolean" + expect (fn ("list", {foo=1})). + to_error "list expected, got table" + expect (fn ("list", Object)). + to_error "list expected, got Object" + - it matches types: + expect (fn ("list", {})).not_to_error () + expect (fn ("list", {1})).not_to_error () + - context with list of homogenous elements: + - it diagnoses missing types: + expect (fn ("list of boolean", nil)). + to_error "list expected, got no value" + expect (fn ("list of booleans", nil)). + to_error "list expected, got no value" + - it diagnoses mismatched types: + expect (fn ("list of file", io.stderr)). + to_error "list expected, got file" + expect (fn ("list of files", io.stderr)). + to_error "list expected, got file" + expect (fn ("list of files", {file=io.stderr})). + to_error "list expected, got table" + - it diagnoses mismatched element types: + expect (fn ("list of number", {false})). + to_error "list of numbers expected, got boolean at index 1" + expect (fn ("list of numbers", {1, 2, "3"})). + to_error "list of numbers expected, got string at index 3" + - it matches types: + expect (fn ("list of string", {})).not_to_error () + expect (fn ("list of string", {"foo"})).not_to_error () + expect (fn ("list of string", {"f", "o", "o"})).not_to_error () + - context with non-empty list: + - it diagnonses missing types: + expect (fn ("#list", nil)). + to_error "non-empty list expected, got no value" + - it diagnoses mismatched types: + expect (fn ("#list", false)). + to_error "non-empty list expected, got boolean" + expect (fn ("#list", {})). + to_error "non-empty list expected, got empty list" + expect (fn ("#list", {foo=1})). + to_error "non-empty list expected, got table" + expect (fn ("#list", Object)). + to_error "non-empty list expected, got empty Object" + expect (fn ("#list", List {})). + to_error "non-empty list expected, got empty List" + - it matches types: + expect (fn ("#list", {1})).not_to_error () + - context with non-empty list of homogenous elements: + - it diagnoses missing types: + expect (fn ("#list of boolean", nil)). + to_error "non-empty list expected, got no value" + expect (fn ("#list of booleans", nil)). + to_error "non-empty list expected, got no value" + - it diagnoses mismatched types: + expect (fn ("#list of file", {})). + to_error "non-empty list expected, got empty table" + expect (fn ("#list of file", io.stderr)). + to_error "non-empty list expected, got file" + expect (fn ("#list of files", {file=io.stderr})). + to_error "non-empty list expected, got table" + - it diagnoses mismatched element types: + expect (fn ("#list of number", {false})). + to_error "non-empty list of numbers expected, got boolean at index 1" + expect (fn ("#list of numbers", {1, 2, "3"})). + to_error "non-empty list of numbers expected, got string at index 3" + - it matches types: + expect (fn ("#list of string", {"foo"})).not_to_error () + expect (fn ("#list of string", {"f", "o", "o"})).not_to_error () + - context with container: + - it diagnoses missing types: + expect (fn ("List of boolean", nil)). + to_error "List expected, got no value" + expect (fn ("List of booleans", nil)). + to_error "List expected, got no value" + - it diagnoses mismatched types: + expect (fn ("List of file", io.stderr)). + to_error "List expected, got file" + expect (fn ("List of files", io.stderr)). + to_error "List expected, got file" + expect (fn ("List of files", {file=io.stderr})). + to_error "List expected, got table" + - it diagnoses mismatched element types: + expect (fn ("List of number", List {false})). + to_error "List of numbers expected, got boolean at index 1" + expect (fn ("List of numbers", List {1, 2, "3"})). + to_error "List of numbers expected, got string at index 3" + - it matches types: + expect (fn ("list of string", List {})).not_to_error () + expect (fn ("list of string", List {"foo"})).not_to_error () + expect (fn ("list of string", List {"f", "o", "o"})).not_to_error () + - context with object: + - it diagnoses missing types: + expect (fn ("object", nil)).to_error "object expected, got no value" + expect (fn ("Object", nil)).to_error "Object expected, got no value" + expect (fn ("Foo", nil)).to_error "Foo expected, got no value" + expect (fn ("any", nil)).to_error "any value expected, got no value" + - it diagnoses mismatched types: + expect (fn ("object", {0})).to_error "object expected, got table" + expect (fn ("Object", {0})).to_error "Object expected, got table" + expect (fn ("object", {_type="Object"})).to_error "object expected, got table" + expect (fn ("Object", {_type="Object"})).to_error "Object expected, got table" + expect (fn ("Object", Foo)).to_error "Object expected, got Foo" + expect (fn ("Foo", {0})).to_error "Foo expected, got table" + expect (fn ("Foo", Object)).to_error "Foo expected, got Object" + - it matches types: + expect (fn ("object", Object)).not_to_error () + expect (fn ("object", Object {})).not_to_error () + expect (fn ("object", Foo)).not_to_error () + expect (fn ("object", Foo {})).not_to_error () - it matches anything: expect (fn ("any", true)).not_to_error () expect (fn ("any", {})).not_to_error () From ad553a6ec0f94df4fa70e8fe843d42f666b6cbb4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 13 Aug 2014 21:01:04 +0100 Subject: [PATCH 352/703] list: factor out manual argument checking. * lib/std/list.lua (depair, map_with, project, transpose) (zip_with): Use new "container of thing" support in export type- lists to replace manual checks. (_ARGCHECK): Remove. No longer used. * specs/list_spec.yaml (depair, map_with, project, transpose) (zip_with): Adjust error expectations accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 70 +++++--------------------------------------- specs/list_spec.yaml | 22 +++++++------- 2 files changed, 17 insertions(+), 75 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 708ca44..57c578b 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -27,14 +27,12 @@ ]] -local _ARGCHECK = require "std.debug_init"._ARGCHECK - local base = require "std.base" local func = require "std.functional" local object = require "std.object" -local Object = object {} -local List -- forward declaration +local Object = object {} +local List -- forward declaration local ipairs, pairs = base.ipairs, base.pairs local argerror, argscheck, export, ielems, prototype, ireverse = @@ -137,21 +135,7 @@ end -- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` -- @see enpair -local depair = export (M, "depair (List|table)", function (ls) - if _ARGCHECK then - local fname = "std.list.depair" - for i, v in ipairs (ls) do - local actual = prototype (v) - if actual ~= "List" and actual ~= "table" then - argerror (fname, 1, "List or table of pairs expected, got " .. - actual .. " at index " .. i, 2) - elseif #v ~= 2 then - argerror (fname, 1, "List or table of pairs expected, got " .. - #v .. "-tuple at index " .. i, 2) - end - end - end - +local depair = export (M, "depair (List of Lists)", function (ls) local t = {} for v in ielems (ls) do t[v[1]] = v[2] @@ -217,17 +201,7 @@ end) -- @func fn map function -- @tparam List ls a list of lists -- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` -local map_with = export (M, "map_with (function, List)", function (fn, ls) - if _ARGCHECK then - for i, v in ipairs (ls) do - local actual = prototype (v) - if actual ~= "List" then - argerror ("std.list.map_with", 2, "List of Lists expected, got " .. - actual .. " at index " .. i, 2) - end - end - end - +local map_with = export (M, "map_with (function, List of Lists)", function (fn, ls) return List (func.map (func.compose (unpack, fn), ielems, ls)) end) @@ -239,17 +213,7 @@ end) -- @tparam List l a list of tables -- @treturn List list of `f` fields -- @see std.list:project -local project = export (M, "project (any, List)", function (f, l) - if _ARGCHECK then - for i, v in ipairs (l) do - local actual = prototype (v) - if actual ~= "table" then - argerror ("std.list.project", 2, "List of tables expected, got " .. - actual .. " at index " .. i, 2) - end - end - end - +local project = export (M, "project (any, List of tables)", function (f, l) return map (function (t) return t[f] end, l) end) @@ -373,17 +337,7 @@ end) -- `{{ls<1,1>, ..., ls<1,c>}, ..., {ls<r,1>, ..., ls<r,c>}}` -- @treturn List new list containing -- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` -local transpose = export (M, "transpose (table|List)", function (ls) - if _ARGCHECK then - for i, v in ipairs (ls) do - local actual = prototype (v) - if actual ~= "List" then - argerror ("std.list.transpose", 1, "List or table of Lists expected, got " .. - actual .. " at index " .. i, 2) - end - end - end - +local transpose = export (M, "transpose (List of Lists)", function (ls) local rs, len, dims = List {}, base.len (ls), map (base.len, ls) if #dims > 0 then for i = 1, math.max (unpack (dims)) do @@ -405,17 +359,7 @@ end) -- @treturn List a new list containing -- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` -- where `N = max {map (function (l) return #l end, ls)}` -local zip_with = export (M, "zip_with (List, function)", function (ls, fn) - if _ARGCHECK then - for i, v in ipairs (ls) do - local actual = prototype (v) - if actual ~= "List" then - argerror ("std.list.zip_with", 1, - "List of Lists expected, got " .. actual .. " at index " .. i, 2) - end - end - end - +local zip_with = export (M, "zip_with (List of Lists, function)", function (ls, fn) return map_with (fn, transpose (ls)) end) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 2ca61c0..0df35c0 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -294,17 +294,15 @@ specify std.list: f = M[fname] - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List or table")) + expect (f ()).to_error (msg (1, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List or table", "boolean")) + expect (f (false)).to_error (msg (1, "List", "boolean")) expect (f (List {0})). - to_error (msg (1, "List or table of pairs", "number at index 1")) + to_error (msg (1, "List of Lists", "number at index 1")) expect (f (List {{}})). - to_error (msg (1, "List or table of pairs", "0-tuple at index 1")) - expect (f (List {{"a"}})). - to_error (msg (1, "List or table of pairs", "1-tuple at index 1")) - expect (f (List {{"a", "b"}, ""})). - to_error (msg (1, "List or table of pairs", "string at index 2")) + to_error (msg (1, "List of Lists", "empty table at index 1")) + expect (f (List { List {"a", "b"}, ""})). + to_error (msg (1, "List of Lists", "string at index 2")) - it returns a primitive table: expect (prototype (f (l))).to_be "table" @@ -804,7 +802,7 @@ specify std.list: - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "function", "boolean")) expect (f (fn, false)).to_error (msg (2, "List", "boolean")) - expect (f (fn, List {{}})). + expect (f (fn, List {{1}})). to_error (msg (2, "List of Lists", "table at index 1")) expect (f (fn, List {List {}, false})). to_error (msg (2, "List of Lists", "boolean at index 2")) @@ -1156,9 +1154,9 @@ specify std.list: f = M[fname] - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table or List")) + expect (f ()).to_error (msg (1, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table or List", "boolean")) + expect (f (false)).to_error (msg (1, "List", "boolean")) - it diagnoses too many arguments: expect (f (l, false)). to_error (toomanyarg (this_module, fname, 1, 2)) @@ -1214,7 +1212,7 @@ specify std.list: - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "List", "boolean")) expect (f (List {{}}, f)). - to_error (msg (1, "List of Lists", "table at index 1")) + to_error (msg (1, "List of Lists", "empty table at index 1")) expect (f (List {List {}, false}, f)). to_error (msg (1, "List of Lists", "boolean at index 2")) expect (f (l, false)).to_error (msg (2, "function", "boolean")) From 5f35e566114147ea5fade4c0bed1f742378c4e57 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 13 Aug 2014 23:21:02 +0100 Subject: [PATCH 353/703] functional: replace list.map_with using new functional.map_with. * specs/functional_spec.yaml (map_with): Specify behaviour of improved map_with implementation that handles tables. * lib/std/functional.lua (map_with): Improved implementation of `list.map_with`. * lib/std/list.lua (map_with): Deprecate. * specs/list_spec.yaml (map_with): Adjust accordingly. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 +++++++ lib/std/functional.lua | 23 ++++++++++++++++++-- lib/std/list.lua | 26 +++++++++++------------ specs/functional_spec.yaml | 43 +++++++++++++++++++++++++++++++++++++- specs/list_spec.yaml | 24 ++++++++++----------- 5 files changed, 94 insertions(+), 30 deletions(-) diff --git a/NEWS b/NEWS index 4240ff5..a465506 100644 --- a/NEWS +++ b/NEWS @@ -57,6 +57,11 @@ Stdlib NEWS - User visible changes - `functional.map` now supports a mapping function that returns a key, value pair for the results table. + - 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. @@ -139,6 +144,9 @@ Stdlib NEWS - User visible changes use. After that, in some future release, they will be removed entirely. + - `list.map_with` has been deprecated, in favour of the more powerful + new `functional.map_with` which handles tables as well as lists. + - `list.relems` has been deprecated, in favour of the more idiomatic `functional.compose (std.ireverse, std.ielems)`. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 94ca623..fe03b4d 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -135,7 +135,7 @@ end) -- b -- c -- a -export (M, "compose (func*)", function (...) +local compose = export (M, "compose (func*)", function (...) local arg = {...} local fns, n = arg, #arg for i = 1, n do @@ -345,10 +345,11 @@ end, M.id)) -- @param ... iterator arguments -- @treturn table results -- @see filter +-- @see map_with -- @usage -- > map (function (e) return e % 2 end, std.elems, {1, 2, 3, 4}) -- {1, 0, 1, 0} -export (M, "map (func, func, any*)", function (f, i, ...) +local map = export (M, "map (func, func, any*)", function (f, i, ...) local fn, state, k = i (...) local t = {fn (state, k)} @@ -366,6 +367,24 @@ export (M, "map (func, func, any*)", function (f, i, ...) end) +--- Map a function over a table of tables. +-- @function map_with +-- @func fn map function +-- @tparam table ts a table of *fn* argument tables +-- @treturn table new table of *fn* results +-- @see map +-- @usage +-- --> {3, 2} +-- map_with (lambda '|...|select ("#", ...)', {{1, 2, 3}, {4, 5}}) +export (M, "map_with (function, table of tables)", function (fn, ts) + local r = {} + for k, v in pairs (ts) do + r[k] = fn (unpack (v)) + end + return r +end) + + --- Memoize a function, by wrapping it in a functable. -- -- To ensure that memoize always returns the same results for the same diff --git a/lib/std/list.lua b/lib/std/list.lua index 57c578b..19d43c9 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -195,17 +195,6 @@ local map = export (M, "map (function, List|table)", function (fn, l) end) ---- Map a function over a list of lists. --- @static --- @function map_with --- @func fn map function --- @tparam List ls a list of lists --- @treturn List new list `{fn (unpack (ls[1]))), ..., fn (unpack (ls[#ls]))}` -local map_with = export (M, "map_with (function, List of Lists)", function (fn, ls) - return List (func.map (func.compose (unpack, fn), ielems, ls)) -end) - - --- Project a list of fields from a list of tables. -- @static -- @function project @@ -359,6 +348,11 @@ end) -- @treturn List a new list containing -- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` -- where `N = max {map (function (l) return #l end, ls)}` + +local function map_with (fn, ls) + return List (func.map (func.compose (unpack, fn), ielems, ls)) +end + local zip_with = export (M, "zip_with (List of Lists, function)", function (ls, fn) return map_with (fn, transpose (ls)) end) @@ -421,6 +415,10 @@ M.index_value = DEPRECATED ("41", "'std.list.index_value'", "compose 'std.list.filter' and 'std.table.invert' instead", index_value) +M.map_with = DEPRECATED ("41", "'std.list.map_with'", + "use 'std.functional.map_with' instead", map_with) + + local function reverse (l) return List (ireverse (l)) end M.reverse = DEPRECATED ("41", "'std.list.reverse'", @@ -566,7 +564,7 @@ List = Object { ------ depair = DEPRECATED ("38", "'std.list:depair'", depair), map_with = DEPRECATED ("38", "'std.list:map_with'", - function (self, f) return map_with (f, self) end), + function (self, fn) return map_with (fn, self) end), transpose = DEPRECATED ("38", "'std.list:transpose'", transpose), zip_with = DEPRECATED ("38", "'std.list:zip_with'", zip_with), @@ -584,9 +582,9 @@ List = Object { return foldr (fn, self) end), index_key = DEPRECATED ("41", "'std.list:index_key'", - function (self, f) return index_key (f, self) end), + function (self, fn) return index_key (fn, self) end), index_value = DEPRECATED ("41", "'std.list:index_value'", - function (self, f) return index_value (f, self) end), + function (self, fn) return index_value (fn, self) end), relems = DEPRECATED ("41", "'std.list:relems'", relems), reverse = DEPRECATED ("41", "'std.list:reverse'", reverse), }, diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 61dfbdf..a1211d5 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -6,7 +6,8 @@ before: exported_apis = { 1, "bind", "case", "collect", "compose", "cond", "curry", "eval", "filter", "fold", "foldl", "foldr", "id", - "lambda", "map", "memoize", "nop", "op", "reduce" } + "lambda", "map", "map_with", "memoize", "nop", "op", + "reduce" } M = require (this_module) @@ -440,6 +441,46 @@ specify std.functional: to_equal (inverse) +- describe map_with: + - before: + t = {{1, 2, 3}, {4, 5}} + fn = function (...) return select ("#", ...) end + + fname = "map_with" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (fn)).to_error (msg (2, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (fn, false)).to_error (msg (2, "table", "boolean")) + expect (f (fn, {1})). + to_error (msg (2, "table of tables", "number at index 1")) + expect (f (fn, {{}, false})). + to_error (msg (2, "table of tables", "boolean at index 2")) + - it diagnoses too many arguments: + expect (f (fn, t, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + + - it returns a list object: + u = f (fn, t) + expect (type (u)).to_be "table" + - it creates a new table: + old = t + u = f (fn, t) + expect (t).to_equal (old) + expect (u).not_to_equal (old) + expect (t).to_equal {{1, 2, 3}, {4, 5}} + - it maps a function over a table of lists: + expect (f (fn, t)).to_equal {3, 2} + - it maps a function over a table of tables: + expect (f (fn, {a={1,2,3}, b={4,5}})).to_equal {a=3, b=2} + - it works for an empty table: + expect (f (fn, {})).to_equal ({}) + + - describe memoize: - before: fname = "memoize" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 0df35c0..eb34640 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -796,19 +796,17 @@ specify std.list: msg = bind (badarg, {this_module, fname}) f = M[fname] - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (fn)).to_error (msg (2, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (fn, false)).to_error (msg (2, "List", "boolean")) - expect (f (fn, List {{1}})). - to_error (msg (2, "List of Lists", "table at index 1")) - expect (f (fn, List {List {}, false})). - to_error (msg (2, "List of Lists", "boolean at index 2")) - - it diagnoses too many arguments: - expect (f (fn, l, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {fn, l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list.map_with' was deprecated" + end + _, err = capture (f, {fn, l}) + expect (err).to_be (nil) - it returns a list object: m = f (fn, l) From 04a93bebab1e9783523840fa81fd62ce1d6727ab Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 14 Aug 2014 13:34:42 +0100 Subject: [PATCH 354/703] functional: improve LDocs for consistency and clarity. * lib/std/functional.lua: Be consistent with parameter names in all functions. Be consistent with usage example formats. Be consistent with lambda string quoting in examples. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 187 ++++++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 95 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index fe03b4d..e06acdd 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -37,18 +37,16 @@ end --- Partially apply a function. -- @function bind --- @func f function to apply partially --- @tparam table t {p1=a1, ..., pn=an} table of parameters to bind to given arguments --- @return function with *pi* already bound +-- @func fn function to apply partially +-- @tparam table argt {p1=a1, ..., pn=an} table of arguments to bind +-- @return function with *argt* arguments already bound -- @usage --- > cube = bind (lambda "^", {[2] = 3}) --- > =cube (2) --- 8 +-- cube = bind (lambda "^", {[2] = 3}) local bind -bind = export (M, "bind (func, any?*)", function (f, ...) - local fix = {...} - if type (fix[1]) == "table" and fix[2] == nil then - fix = fix[1] +bind = export (M, "bind (func, any?*)", function (fn, ...) + local argt = {...} + if type (argt[1]) == "table" and argt[2] == nil then + argt = argt[1] else if not base.getcompat (bind) then io.stderr:write (base.DEPRECATIONMSG ("39", @@ -60,7 +58,7 @@ bind = export (M, "bind (func, any?*)", function (f, ...) return function (...) local arg = {} - for i, v in pairs (fix) do + for i, v in pairs (argt) do arg[i] = v end local i = 1 @@ -68,21 +66,22 @@ bind = export (M, "bind (func, any?*)", function (f, ...) while arg[i] ~= nil do i = i + 1 end arg[i] = v end - return f (unpack (arg)) + return fn (unpack (arg)) end end) --- A rudimentary case statement. --- Match *with* against keys in *branches* table, and return the result --- table value for the matching key, or the first non-key value if no key --- matches. Function or functable valued matches are called using *with* as --- the sole argument, and the result of that call returned; otherwise the --- matching value associated with the matching key is returned directly. +-- Match *with* against keys in *branches* table. -- @function case -- @param with expression to match -- @tparam table branches map possible matches to functions --- @return the return value from function with a matching key, or nil. +-- @return the value associated with a matching key, or the first non-key +-- value if no key matches. Function or functable valued matches are +-- called using *with* as the sole argument, and the result of that call +-- returned; otherwise the matching value associated with the matching +-- key is returned directly; or else `nil` if there is no match and no +-- default. -- @see cond -- @usage -- return case (type (object), { @@ -101,17 +100,17 @@ end) --- Collect the results of an iterator. -- @function collect --- @func i iterator --- @param ... iterator arguments --- @return results of running the iterator on *arguments* +-- @func ifn iterator function +-- @param ... iterator function arguments +-- @return results of running *ifn* on *arguments* -- @see filter -- @see map -- @usage --- > =collect (compose (std.ireverse, std.ielems), {"a", "b", "c"}) --- {"c", "b", "a"} -export (M, "collect (func, any*)", function (i, ...) +-- --> {"c", "b", "a"} +-- collect (compose (std.ireverse, std.ielems), {"a", "b", "c"}) +export (M, "collect (func, any*)", function (ifn, ...) local r = {} - for k, v in i (...) do + for k, v in ifn (...) do if v == nil then k, v = #r + 1, k end r[k] = v end @@ -122,7 +121,7 @@ end) --- Compose functions. -- @function compose -- @func ... functions to compose --- @treturn function composition of fn (... (f1) ...): note that this is the +-- @treturn function composition of fnN .. fn1: note that this is the -- reverse of what you might expect, but means that code like: -- -- functional.compose (function (x) return f (x) end, @@ -130,11 +129,8 @@ end) -- -- can be read from top to bottom. -- @usage --- > vpairs = compose (table.invert, pairs) --- > for v in vpairs {"a", "b", "c"} do print (v) end --- b --- c --- a +-- vpairs = compose (table.invert, ipairs) +-- for v, i in vpairs {"a", "b", "c"} do process (v, i) end local compose = export (M, "compose (func*)", function (...) local arg = {...} local fns, n = arg, #arg @@ -188,21 +184,19 @@ end --- Curry a function. -- @function curry --- @func f function to curry +-- @func fn function to curry -- @int n number of arguments --- @treturn function curried version of *f* +-- @treturn function curried version of *fn* -- @usage --- > add = curry (function (x, y) return x + y end, 2) --- > incr, decr = add (1), add (-1) --- > =incr (99), decr (99) --- 100 98 +-- add = curry (function (x, y) return x + y end, 2) +-- incr, decr = add (1), add (-1) local curry -curry = export (M, "curry (func, int)", function (f, n) +curry = export (M, "curry (func, int)", function (fn, n) if n <= 1 then - return f + return fn else return function (x) - return curry (bind (f, x), n - 1) + return curry (bind (fn, x), n - 1) end end end) @@ -210,39 +204,41 @@ end) --- Filter an iterator with a predicate. -- @function filter --- @tparam predicate p predicate function --- @func i iterator +-- @tparam predicate pfn predicate function +-- @func ifn iterator function -- @param ... iterator arguments --- @treturn table elements e for which `p (e)` is not falsey. +-- @treturn table elements e for which `pfn (e)` is not "falsey". -- @see collect +-- @see map -- @usage --- > filter (lambda "|e|e%2==0", std.elems, {1, 2, 3, 4}) --- {2, 4} -export (M, "filter (func, func, any*)", function (p, i, ...) +-- --> {2, 4} +-- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) +export (M, "filter (func, func, any*)", function (pfn, ifn, ...) local r = {} -- new results table - local fn, state, k = i (...) - local t = {fn (state, k)} -- table of iteration 1 + local nextfn, state, k = ifn (...) + local t = {nextfn (state, k)} -- table of iteration 1 while t[1] ~= nil do -- until iterator returns nil k = t[1] - if p (unpack (t)) then -- pass all iterator results to p + if pfn (unpack (t)) then -- pass all iterator results to p if t[2] ~= nil then r[k] = t[2] -- k,v = t[1],t[2] else r[#r + 1] = k -- k,v = #r + 1,t[1] end end - t = {fn (state, k)} -- maintain loop invariant + t = {nextfn (state, k)} -- maintain loop invariant end return r end) --- Fold a binary function left associatively. --- If parameter *d* is omitted, the first element of *t* is used. +-- If parameter *d* is omitted, the first element of *t* is used, +-- and *t* treated as if it had been passed without that element. -- @function foldl -- @func fn binary function --- @param[opt] d initial left-most argument +-- @param[opt=t[1]] d initial left-most argument -- @tparam table t a table -- @return result -- @see foldr @@ -253,10 +249,11 @@ export (M, "foldl (function, [any], table)", base.functional.foldl) --- Fold a binary function right associatively. --- If parameter *d* is omitted, the last element of *t* is used. +-- If parameter *d* is omitted, the last element of *t* is used, +-- and *t* treated as if it had been passed without that element. -- @function foldr -- @func fn binary function --- @param[opt] d initial right-most argument +-- @param[opt=t[1]] d initial right-most argument -- @tparam table t a table -- @return result -- @see foldl @@ -268,8 +265,8 @@ export (M, "foldr (function, [any], table)", base.functional.foldr) --- Identity function. -- @function id --- @param ... --- @return the arguments passed to the function +-- @param ... arguments +-- @return *arguments* function M.id (...) return ... end @@ -279,9 +276,9 @@ end -- -- A valid lambda string takes one of the following forms: -- --- 1. `operator`: where *op* is a key in @{std.operator}, equivalent to that operation --- 1. `"=expression"`: equivalent to `function (...) return (expression) end` --- 1. `"|args|expression"`: equivalent to `function (args) return (expression) end` +-- 1. `'operator'`: where *op* is a key in @{std.operator}, equivalent to that operation +-- 1. `'=expression'`: equivalent to `function (...) return (expression) end` +-- 1. `'|args|expression'`: equivalent to `function (args) return (expression) end` -- -- The second form (starting with `=`) automatically assigns the first -- nine arguments to parameters `_1` through `_9` for use within the @@ -294,28 +291,28 @@ end -- @treturn table compiled lambda string, can be called like a function -- @usage -- -- The following are all equivalent: --- lambda "<" --- lambda "= _1 < _2" --- lambda "|a,b| a map (function (e) return e % 2 end, std.elems, {1, 2, 3, 4}) --- {1, 0, 1, 0} -local map = export (M, "map (func, func, any*)", function (f, i, ...) - local fn, state, k = i (...) - local t = {fn (state, k)} +-- --> {1, 0, 1, 0} +-- map (lambda '=_1,_2%2', pairs, {1, 2, 3, 4}) +local map = export (M, "map (func, func, any*)", function (fn, ifn, ...) + local nextfn, state, k = ifn (...) + local t = {nextfn (state, k)} local r = {} while t[1] ~= nil do k = t[1] - local d, v = f (unpack (t)) + local d, v = fn (unpack (t)) if v == nil then d, v = #r + 1, d end if v ~= nil then r[d] = v end - t = {fn (state, k)} + t = {nextfn (state, k)} end return r end) @@ -370,15 +367,15 @@ end) --- Map a function over a table of tables. -- @function map_with -- @func fn map function --- @tparam table ts a table of *fn* argument tables +-- @tparam table tt a table of tabulated *fn* arguments -- @treturn table new table of *fn* results -- @see map -- @usage --- --> {3, 2} --- map_with (lambda '|...|select ("#", ...)', {{1, 2, 3}, {4, 5}}) -export (M, "map_with (function, table of tables)", function (fn, ts) +-- --> {123, 45} +-- map_with (lambda '|...|table.concat {...}', {{1, 2, 3}, {4, 5}}) +export (M, "map_with (function, table of tables)", function (fn, tt) local r = {} - for k, v in pairs (ts) do + for k, v in pairs (tt) do r[k] = fn (unpack (v)) end return r @@ -388,12 +385,12 @@ end) --- Memoize a function, by wrapping it in a functable. -- -- To ensure that memoize always returns the same results for the same --- arguments, it passes arguments to `normalize` (std.string.tostring --- by default). You can specify a more sophisticated function if memoize --- should handle complicated argument equivalencies. +-- arguments, it passes arguments to *fn*. You can specify a more +-- sophisticated function if memoize should handle complicated argument +-- equivalencies. -- @function memoize -- @func fn pure function: a function with no side effects --- @tparam[opt] normalize normalize function to normalize arguments +-- @tparam[opt=std.tostring] normalize normfn function to normalize arguments -- @treturn functable memoized function -- @usage -- local fast = memoize (function (...) --[[ slow code ]] end) @@ -410,16 +407,16 @@ M.nop = base.functional.nop --- Fold a binary function into an iterator. -- @function reduce --- @func f function +-- @func fn reduce function -- @param d initial first argument --- @func i iterator +-- @func ifn iterator function -- @param ... iterator arguments -- @return result -- @see foldl -- @see foldr -- @usage -- --> 2 ^ 3 ^ 4 ==> 4096 --- reduce (lambda "^", 2, std.ipairs, {3, 4}) +-- reduce (lambda '^', 2, std.ipairs, {3, 4}) export (M, "reduce (func, any, func, any*)", base.functional.reduce) From a0fae0e2c5792104602dc6e91f086f2aea56d44c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 14 Aug 2014 16:22:02 +0100 Subject: [PATCH 355/703] functional: new callable module function. * specs/functional_spec.yaml (callable): Specify correct behaviour for new callable function. * lib/std/functional.lua (iscallable): Move from here... * lib/std/base/functional.lua (callable): ...to here. Adjust all callers. * lib/std/functional.lua (M.callable): Reexport as a public api. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/base/functional.lua | 17 ++++++++++++----- lib/std/functional.lua | 31 ++++++++++++------------------- specs/functional_spec.yaml | 25 +++++++++++++++++++++---- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/NEWS b/NEWS index a465506..186187d 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,9 @@ Stdlib NEWS - User visible changes optional; when omitted these functions automatically start with the left- or right-most element of the table argument resp. + - New `functional.callable` function for detecting objects or + primitives that can be called as if they were a function. + - New `functional.lambda` function for compiling lambda strings: table.sort (t, lambda "|a,b| a Date: Thu, 14 Aug 2014 17:23:15 +0100 Subject: [PATCH 356/703] functional: support default iterators where possible. * specs/functional_spec.yaml (collect, filter, map): Specify behaviours when iterator argument is omitted. * lib/std/functional.lua (collect): Default iterator to ipairs. (filter, map): Default iterator to pairs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 13 +++++---- lib/std/base/functional.lua | 10 +++---- lib/std/functional.lua | 55 +++++++++++++++++++++++-------------- specs/functional_spec.yaml | 26 +++++++++++------- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/NEWS b/NEWS index 186187d..15d4f06 100644 --- a/NEWS +++ b/NEWS @@ -32,12 +32,18 @@ Stdlib NEWS - User visible changes their return value propagated back to the case caller. Function values behave the same as in previous releases. - - `functional.collect`, `functional.filter` and `functional.fold` now - work with standard multi-return iterators, such as `std.ipairs`. + - `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. @@ -57,9 +63,6 @@ Stdlib NEWS - User visible changes table.sort (t, lambda "<") - - `functional.map` now supports a mapping function that returns a key, - value pair for the results table. - - 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 diff --git a/lib/std/base/functional.lua b/lib/std/base/functional.lua index d43a042..01bfd46 100644 --- a/lib/std/base/functional.lua +++ b/lib/std/base/functional.lua @@ -49,14 +49,14 @@ end local function nop () end -local function reduce (f, d, i, ...) - local fn, state, k = i (...) - local t = {fn (state, k)} +local function reduce (fn, d, ifn, ...) + local nextfn, state, k = ifn (...) + local t = {nextfn (state, k)} local r = d while t[1] ~= nil do - r = f (r, t[#t]) - t = {fn (state, t[1])} + r = fn (r, t[#t]) + t = {nextfn (state, t[1])} end return r end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 46625b4..e580cf3 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -11,8 +11,8 @@ local base = require "std.base" local operator = require "std.operator" -local export, ireverse, len, pairs = - base.export, base.ireverse, base.len, base.pairs +local export, ipairs, ireverse, len, pairs = + base.export, base.ipairs, base.ireverse, base.len, base.pairs local callable = base.functional.callable local M = { "std.functional" } @@ -22,7 +22,7 @@ local M = { "std.functional" } --- Partially apply a function. -- @function bind -- @func fn function to apply partially --- @tparam table argt {p1=a1, ..., pn=an} table of arguments to bind +-- @tparam table argt table of *fn* arguments to bind -- @return function with *argt* arguments already bound -- @usage -- cube = bind (lambda "^", {[2] = 3}) @@ -93,17 +93,22 @@ end) --- Collect the results of an iterator. -- @function collect --- @func ifn iterator function --- @param ... iterator function arguments --- @return results of running *ifn* on *arguments* +-- @func[opt=std.ipairs] ifn iterator function +-- @param ... *ifn* arguments +-- @treturn table of results from running *ifn* on *args* -- @see filter -- @see map -- @usage --- --> {"c", "b", "a"} --- collect (compose (std.ireverse, std.ielems), {"a", "b", "c"}) -export (M, "collect (func, any*)", function (ifn, ...) +-- --> {"a", "b", "c"} +-- collect {"a", "b", "c", x=1, y=2, z=5} +export (M, "collect ([func], any*)", function (ifn, ...) + local argt = {...} + if not callable (ifn) then + ifn, argt = ipairs, {ifn, ...} + end + local r = {} - for k, v in ifn (...) do + for k, v in ifn (unpack (argt)) do if v == nil then k, v = #r + 1, k end r[k] = v end @@ -198,7 +203,7 @@ end) --- Filter an iterator with a predicate. -- @function filter -- @tparam predicate pfn predicate function --- @func ifn iterator function +-- @func[opt=std.pairs] ifn iterator function -- @param ... iterator arguments -- @treturn table elements e for which `pfn (e)` is not "falsey". -- @see collect @@ -206,11 +211,16 @@ end) -- @usage -- --> {2, 4} -- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) -export (M, "filter (func, func, any*)", function (pfn, ifn, ...) - local r = {} -- new results table - local nextfn, state, k = ifn (...) +export (M, "filter (func, [func], any*)", function (pfn, ifn, ...) + local argt = {...} + if not callable (ifn) then + ifn, argt = pairs, {ifn, ...} + end + + local nextfn, state, k = ifn (unpack (argt)) local t = {nextfn (state, k)} -- table of iteration 1 + local r = {} -- new results table while t[1] ~= nil do -- until iterator returns nil k = t[1] if pfn (unpack (t)) then -- pass all iterator results to p @@ -331,7 +341,7 @@ end, M.id)) --- Map a function over an iterator. -- @function map -- @func fn map function --- @func ifn iterator function +-- @func[opt=std.pairs] ifn iterator function -- @param ... iterator arguments -- @treturn table results -- @see filter @@ -339,8 +349,13 @@ end, M.id)) -- @usage -- --> {1, 0, 1, 0} -- map (lambda '=_1,_2%2', pairs, {1, 2, 3, 4}) -local map = export (M, "map (func, func, any*)", function (fn, ifn, ...) - local nextfn, state, k = ifn (...) +local map = export (M, "map (func, [func], any*)", function (fn, ifn, ...) + local argt = {...} + if not callable (ifn) then + ifn, argt = pairs, {ifn, ...} + end + + local nextfn, state, k = ifn (unpack (argt)) local t = {nextfn (state, k)} local r = {} @@ -357,16 +372,16 @@ local map = export (M, "map (func, func, any*)", function (fn, ifn, ...) end) ---- Map a function over a table of tables. +--- Map a function over a table of argument lists. -- @function map_with -- @func fn map function --- @tparam table tt a table of tabulated *fn* arguments +-- @tparam table tt a table of *fn* argument lists -- @treturn table new table of *fn* results -- @see map -- @usage -- --> {123, 45} -- map_with (lambda '|...|table.concat {...}', {{1, 2, 3}, {4, 5}}) -export (M, "map_with (function, table of tables)", function (fn, tt) +export (M, "map_with (function, table of lists)", function (fn, tt) local r = {} for k, v in pairs (tt) do r[k] = fn (unpack (v)) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 92347d9..739708e 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -141,15 +141,15 @@ specify std.functional: f = M[fname] - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f ()).to_error (msg (1, "function or any value")) - it collects a list of single return value iterator results: expect (f (base.ielems, {"a", "b", "c"})).to_equal {"a", "b", "c"} - it collects a table of key:value iterator results: t = {"first", second="two", last=3} expect (f (pairs, t)).to_equal (t) + - it defaults to ipairs iteration: + expect (f {1, 2, [5]=5, a="b", c="d"}).to_equal {1, 2} - describe compose: @@ -263,10 +263,9 @@ specify std.functional: - it diagnoses missing arguments: expect (f ()).to_error (msg (1, "function")) - expect (f (f)).to_error (msg (2, "function")) + expect (f (f)).to_error (msg (2, "function or any value")) - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, false)).to_error (msg (2, "function", "boolean")) - it works with an empty table: expect (f (M.id, pairs, {})).to_equal {} @@ -286,6 +285,10 @@ specify std.functional: to_equal {"first", last="three"} expect (f (function (k, v) return k % 2 == 0 end, ipairs, elements)). to_equal {[2]="b", [4]="d"} + - it defaults to pairs iteration: + t = {"first", second=2, last="three"} + expect (f (function (k, v) return type (v) == "string" end, t)). + to_equal {"first", last="three"} - describe fold: @@ -428,10 +431,9 @@ specify std.functional: - it diagnoses missing arguments: expect (f ()).to_error (msg (1, "function")) - expect (f (f)).to_error (msg (2, "function")) + expect (f (f)).to_error (msg (2, "function or any value")) - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, false)).to_error (msg (2, "function", "boolean")) - it works with an empty table: expect (f (M.id, ipairs, {})).to_equal {} @@ -456,6 +458,10 @@ specify std.functional: - it supports key:value results from mapping function: expect (f (function (k, v) return v, k end, pairs, elements)). to_equal (inverse) + - it defaults to pairs iteration: + t = {"first", second=2, last="three"} + expect (f (function (k, v) return type (v) == "string" end, t)). + to_contain.a_permutation_of {true, false, true} - describe map_with: @@ -473,10 +479,10 @@ specify std.functional: - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "function", "boolean")) expect (f (fn, false)).to_error (msg (2, "table", "boolean")) - expect (f (fn, {1})). - to_error (msg (2, "table of tables", "number at index 1")) + expect (f (fn, {{a=1}})). + to_error (msg (2, "table of lists", "table at index 1")) expect (f (fn, {{}, false})). - to_error (msg (2, "table of tables", "boolean at index 2")) + to_error (msg (2, "table of lists", "boolean at index 2")) - it diagnoses too many arguments: expect (f (fn, t, false)). to_error (toomanyarg (this_module, fname, 2, 3)) From a875876b573e43311d6b8cccb41e4b480563f14f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 14 Aug 2014 17:51:45 +0100 Subject: [PATCH 357/703] list: deprecate list.map. * specs/list_spec.yaml (map): Specify output of deprecation warning on first call. * specs/debug_spec.yaml (debug, say): Use functional.map for mkwrap instead of deprecated list.map. * lib/std/list.lua (map): Deprecate. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 5 ++-- lib/std/list.lua | 22 +++++++---------- specs/debug_spec.yaml | 16 ++++++------- specs/list_spec.yaml | 55 ++++++++++++++++++++++++++++++------------- 4 files changed, 58 insertions(+), 40 deletions(-) diff --git a/NEWS b/NEWS index 15d4f06..5c04473 100644 --- a/NEWS +++ b/NEWS @@ -150,8 +150,9 @@ Stdlib NEWS - User visible changes use. After that, in some future release, they will be removed entirely. - - `list.map_with` has been deprecated, in favour of the more powerful - new `functional.map_with` which handles tables as well as lists. + - `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.relems` has been deprecated, in favour of the more idiomatic `functional.compose (std.ireverse, std.ielems)`. diff --git a/lib/std/list.lua b/lib/std/list.lua index 19d43c9..63cd2ad 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -183,18 +183,6 @@ local flatten = export (M, "flatten (List)", function (l) end) ---- Map a function over a list. --- @static --- @function map --- @func fn map function --- @tparam List l a list --- @treturn List new list containing `{fn (l[1]), ..., fn (l[#l])}` --- @see std.list:map -local map = export (M, "map (function, List|table)", function (fn, l) - return List (func.map (fn, ielems, l)) -end) - - --- Project a list of fields from a list of tables. -- @static -- @function project @@ -203,7 +191,7 @@ end) -- @treturn List list of `f` fields -- @see std.list:project local project = export (M, "project (any, List of tables)", function (f, l) - return map (function (t) return t[f] end, l) + return List (func.map (function (t) return t[f] end, ielems, l)) end) @@ -327,7 +315,7 @@ end) -- @treturn List new list containing -- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` local transpose = export (M, "transpose (List of Lists)", function (ls) - local rs, len, dims = List {}, base.len (ls), map (base.len, ls) + local rs, len, dims = List {}, base.len (ls), func.map (base.len, ielems, ls) if #dims > 0 then for i = 1, math.max (unpack (dims)) do rs[i] = List {} @@ -415,6 +403,12 @@ M.index_value = DEPRECATED ("41", "'std.list.index_value'", "compose 'std.list.filter' and 'std.table.invert' instead", index_value) +local function map (fn, l) return List (func.map (fn, ielems, l)) end + +M.map = DEPRECATED ("41", "'std.list.map'", + "use 'std.functional.map' instead", map) + + M.map_with = DEPRECATED ("41", "'std.list.map_with'", "use 'std.functional.map_with' instead", map_with) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index b8e7906..f63a52f 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -519,10 +519,10 @@ specify std.debug: - describe debug: - before: | - function mkwrap (x) + function mkwrap (k, v) local fmt = "%s" - if type (x) == "string" then fmt = "%q" end - return string.format (fmt, require "std".tostring (x)) + if type (v) == "string" then fmt = "%q" end + return k, string.format (fmt, require "std".tostring (v)) end function mkdebug (debugp, ...) @@ -531,7 +531,7 @@ specify std.debug: require "std.debug" (%s) ]], require "std".tostring (debugp), - table.concat (require "std.list".map (mkwrap, {...}), ", ")) + table.concat (require "std.functional".map (mkwrap, {...}), ", ")) end - it does nothing when _DEBUG is disabled: @@ -557,10 +557,10 @@ specify std.debug: - describe say: - before: | - function mkwrap (x) + function mkwrap (k, v) local fmt = "%s" - if type (x) == "string" then fmt = "%q" end - return string.format (fmt, require "std".tostring (x)) + if type (v) == "string" then fmt = "%q" end + return k, string.format (fmt, require "std".tostring (v)) end function mksay (debugp, ...) @@ -569,7 +569,7 @@ specify std.debug: require "std.debug".say (%s) ]], require "std".tostring (debugp), - table.concat (require "std.list".map (mkwrap, {...}), ", ")) + table.concat (require "std.functional".map (mkwrap, {...}), ", ")) end - context when _DEBUG is disabled: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index eb34640..9befb78 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -754,34 +754,57 @@ specify std.list: sq = function (n) return n * n end fname = "map" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (sq)).to_error (msg (2, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (sq, false)).to_error (msg (2, "List or table", "boolean")) - - it diagnoses too many arguments: - expect (f (sq, l, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {sq, l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list.map' was deprecated" + end + _, err = capture (f, {sq, l}) + expect (err).to_be (nil) + + - it returns a list object: + m = f (sq, l) + expect (prototype (m)).to_be "List" + - it works for an empty list: + l = List {} + expect (f (sq, l)).to_equal (List {}) + - it creates a new list: + o = l + m = f (sq, l) + expect (l).to_equal (o) + expect (m).not_to_equal (o) + expect (l).to_equal (List {1, 2, 3, 4, 5}) + - it maps a function over a list: + expect (f (sq, l)).to_equal (List {1, 4, 9, 16, 25}) + + - context as a list object method: + - before: + f = l[fname] - - context when called as a list object method: - it returns a list object: - m = l:map (sq) + m = f (l, sq) expect (prototype (m)).to_be "List" - it works for an empty list: l = List {} - expect (l:map (sq)).to_equal (List {}) + expect (f (l, sq)).to_equal (List {}) - it creates a new list: o = l - m = l:map (sq) + m = f (l, sq) expect (l).to_equal (o) expect (m).not_to_equal (o) expect (l).to_equal (List {1, 2, 3, 4, 5}) - it maps a function over a list: - expect (l:map (sq)).to_equal (List {1, 4, 9, 16, 25}) + expect (f (l, sq)).to_equal (List {1, 4, 9, 16, 25}) - describe map_with: From 9109b26315f9746da72201c0e09d4486b48f5ffc Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 15 Aug 2014 15:45:19 +0100 Subject: [PATCH 358/703] functional: new zip and zip_with replace list transpose and zip_with. * specs/functional_spec.yaml (zip, zip_with): Specify behaviour of new general zip and zip_with apis. * lib/std/functional.lua (zip, zip_with): New functions. * lib/std/list.lua (transpose, zip_with): Deprecate. * specs/list_spec.yaml (transpose, zip_with): Specify deprecation warning behaviours. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 26 ++++++++++-- lib/std/functional.lua | 74 ++++++++++++++++++++++++++------- lib/std/list.lua | 70 +++++++++++++------------------- specs/functional_spec.yaml | 83 +++++++++++++++++++++++++++++++++----- specs/list_spec.yaml | 42 ++++++++++--------- 5 files changed, 208 insertions(+), 87 deletions(-) diff --git a/NEWS b/NEWS index 5c04473..5ed3fb5 100644 --- a/NEWS +++ b/NEWS @@ -10,7 +10,7 @@ Stdlib NEWS - User visible changes - 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 paramater + - New `debug.argscheck` function for checking all function parameter types with a single function call in the common case. - New `_DEBUG.argcheck` field that disables `debug.argcheck` (and @@ -75,6 +75,18 @@ Stdlib NEWS - User visible changes - 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. @@ -160,6 +172,12 @@ Stdlib NEWS - User visible changes - `list.reverse` has been deprecated in favour of the more general and more accurately named `std.ireverse`. + - `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. @@ -234,9 +252,11 @@ Stdlib NEWS - User visible changes 'List'" error. - `list.transpose` works again, and handles empty lists without - raising an error. + 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. + - `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. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index e580cf3..a7135e0 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -11,9 +11,9 @@ local base = require "std.base" local operator = require "std.operator" -local export, ipairs, ireverse, len, pairs = - base.export, base.ipairs, base.ireverse, base.len, base.pairs local callable = base.functional.callable +local export, ielems, ipairs, ireverse, pairs = + base.export, base.ielems, base.ipairs, base.ireverse, base.pairs local M = { "std.functional" } @@ -346,27 +346,28 @@ end, M.id)) -- @treturn table results -- @see filter -- @see map_with +-- @see zip -- @usage --- --> {1, 0, 1, 0} --- map (lambda '=_1,_2%2', pairs, {1, 2, 3, 4}) -local map = export (M, "map (func, [func], any*)", function (fn, ifn, ...) +-- --> {1, 4, 9, 16} +-- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) +export (M, "map (func, [func], any*)", function (mapfn, ifn, ...) local argt = {...} if not callable (ifn) then ifn, argt = pairs, {ifn, ...} end local nextfn, state, k = ifn (unpack (argt)) - local t = {nextfn (state, k)} + local mapargs = {nextfn (state, k)} local r = {} - while t[1] ~= nil do - k = t[1] - local d, v = fn (unpack (t)) + while mapargs[1] ~= nil do + k = mapargs[1] + local d, v = mapfn (unpack (mapargs)) if v == nil then d, v = #r + 1, d end if v ~= nil then r[d] = v end - t = {nextfn (state, k)} + mapargs = {nextfn (state, k)} end return r end) @@ -378,13 +379,15 @@ end) -- @tparam table tt a table of *fn* argument lists -- @treturn table new table of *fn* results -- @see map +-- @see zip_with -- @usage --- --> {123, 45} --- map_with (lambda '|...|table.concat {...}', {{1, 2, 3}, {4, 5}}) -export (M, "map_with (function, table of lists)", function (fn, tt) +-- --> {"123", "45"}, {a="123", b="45"} +-- conc = bind (map_with, {lambda '|...|table.concat {...}'}) +-- conc {{1, 2, 3}, {4, 5}}, conc {a={1, 2, 3, x="y"}, b={4, 5, z=6}} +local map_with = export (M, "map_with (function, table of tables)", function (mapfn, tt) local r = {} for k, v in pairs (tt) do - r[k] = fn (unpack (v)) + r[k] = mapfn (unpack (v)) end return r end) @@ -428,6 +431,49 @@ M.nop = base.functional.nop export (M, "reduce (func, any, func, any*)", base.functional.reduce) +--- Zip a table of tables. +-- Make a new table, with lists of elements at the same index in the +-- original table. This function is effectively its own inverse. +-- @function zip +-- @tparam table tt a table of tables +-- @treturn table new table with lists of elements of the same key +-- from *tt* +-- @see map +-- @see zip_with +-- @usage +-- --> {{1, 3, 5}, {2, 4}}, {a={x=1, y=3, z=5}, b={x=2, y=4}} +-- zip {{1, 2}, {3, 4}, {5}}, zip {x={a=1, b=2}, y={a=3, b=4}, z={a=5}} +local zip = export (M, "zip (table of tables)", function (tt) + local r = {} + for outerk, inner in pairs (tt) do + for k, v in pairs (inner) do + r[k] = r[k] or {} + r[k][outerk] = v + end + end + return r +end) + + +--- Zip a list of tables together with a function. +-- @function zip_with +-- @tparam function fn function +-- @tparam table tt table of tables +-- @treturn table a new table of results from calls to *fn* with arguments +-- made from all elements the same key in the original tables; effectively +-- the "columns" in a simple list +-- of lists. +-- @see map_with +-- @see zip +-- @usage +-- --> {"135", "24"}, {a="1", b="25"} +-- conc = bind (zip_with, {lambda '|...|table.concat {...}'}) +-- conc {{1, 2}, {3, 4}, {5}}, conc {{a=1, b=2}, x={a=3, b=4}, {b=5}} +export (M, "zip_with (function, table of tables)", function (fn, tt) + return map_with (fn, zip (tt)) +end) + + -- For backwards compatibility. M.op = operator diff --git a/lib/std/list.lua b/lib/std/list.lua index 63cd2ad..cdcae1f 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -305,47 +305,6 @@ local tail = export (M, "tail (List)", function (l) end) ---- Transpose a list of lists. --- This function in Lua is equivalent to zip and unzip in more strongly --- typed languages. --- @static --- @function transpose --- @tparam table ls --- `{{ls<1,1>, ..., ls<1,c>}, ..., {ls<r,1>, ..., ls<r,c>}}` --- @treturn List new list containing --- `{{ls<1,1>, ..., ls<r,1>}, ..., {ls<1,c>, ..., ls<r,c>}}` -local transpose = export (M, "transpose (List of Lists)", function (ls) - local rs, len, dims = List {}, base.len (ls), func.map (base.len, ielems, ls) - if #dims > 0 then - for i = 1, math.max (unpack (dims)) do - rs[i] = List {} - for j = 1, len do - rs[i][j] = ls[j][i] - end - end - end - return rs -end) - - ---- Zip a list of lists together with a function. --- @static --- @function zip_with --- @tparam table ls list of lists --- @tparam function fn function --- @treturn List a new list containing --- `{f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])` --- where `N = max {map (function (l) return #l end, ls)}` - -local function map_with (fn, ls) - return List (func.map (func.compose (unpack, fn), ielems, ls)) -end - -local zip_with = export (M, "zip_with (List of Lists, function)", function (ls, fn) - return map_with (fn, transpose (ls)) -end) - - --[[ ============= ]]-- --[[ Deprecations. ]]-- @@ -409,6 +368,10 @@ M.map = DEPRECATED ("41", "'std.list.map'", "use 'std.functional.map' instead", map) +local function map_with (fn, ls) + return List (func.map (func.compose (unpack, fn), ielems, ls)) +end + M.map_with = DEPRECATED ("41", "'std.list.map_with'", "use 'std.functional.map_with' instead", map_with) @@ -419,6 +382,31 @@ M.reverse = DEPRECATED ("41", "'std.list.reverse'", "use 'std.ireverse' instead", reverse) +local function transpose (ls) + local rs, len, dims = List {}, base.len (ls), func.map (base.len, ielems, ls) + if #dims > 0 then + for i = 1, math.max (unpack (dims)) do + rs[i] = List {} + for j = 1, len do + rs[i][j] = ls[j][i] + end + end + end + return rs +end + +M.transpose = DEPRECATED ("41", "'std.list.transpose'", + "use 'std.functional.zip' instead", transpose) + + +local function zip_with (ls, fn) + return map_with (fn, transpose (ls)) +end + +M.zip_with = DEPRECATED ("41", "'std.list.zip_with'", + "use 'std.functional.zip_with' instead", zip_with) + + List = Object { -- Derived object type. diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 739708e..049a8f0 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -7,7 +7,7 @@ before: exported_apis = { 1, "bind", "callable", "case", "collect", "compose", "cond", "curry", "eval", "filter", "fold", "foldl", "foldr", "id", "lambda", "map", "map_with", "memoize", - "nop", "op", "reduce" } + "nop", "op", "reduce", "zip", "zip_with" } M = require (this_module) @@ -479,15 +479,15 @@ specify std.functional: - it diagnoses wrong argument types: expect (f (false)).to_error (msg (1, "function", "boolean")) expect (f (fn, false)).to_error (msg (2, "table", "boolean")) - expect (f (fn, {{a=1}})). - to_error (msg (2, "table of lists", "table at index 1")) expect (f (fn, {{}, false})). - to_error (msg (2, "table of lists", "boolean at index 2")) + to_error (msg (2, "table of tables", "boolean at index 2")) - it diagnoses too many arguments: expect (f (fn, t, false)). to_error (toomanyarg (this_module, fname, 2, 3)) - - it returns a list object: + - it works for an empty table: + expect (f (fn, {})).to_equal ({}) + - it returns a table: u = f (fn, t) expect (type (u)).to_be "table" - it creates a new table: @@ -496,12 +496,12 @@ specify std.functional: expect (t).to_equal (old) expect (u).not_to_equal (old) expect (t).to_equal {{1, 2, 3}, {4, 5}} - - it maps a function over a table of lists: + - it maps a function over a list of argument lists: expect (f (fn, t)).to_equal {3, 2} - - it maps a function over a table of tables: + - it discards hash-part arguments: + expect (f (fn, {{1,x=2,3}, {4,5,y="z"}})).to_equal {2, 2} + - it maps a function over a table of argument lists: expect (f (fn, {a={1,2,3}, b={4,5}})).to_equal {a=3, b=2} - - it works for an empty table: - expect (f (fn, {})).to_equal ({}) - describe memoize: @@ -584,3 +584,68 @@ specify std.functional: expect (f (M.op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) - it reduces elements from left to right: expect (f (M.op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) + + +- describe zip: + - before: + tt = {{1, 2}, {3, 4}, {5, 6}} + + fname = "zip" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f (tt, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + + - it works for an empty table: + expect (f {}).to_equal {} + - it is the inverse of itself: + expect (f (f (tt))).to_equal (tt) + - it transposes rows and columns: + expect (f (tt)).to_equal {{1, 3, 5}, {2, 4, 6}} + expect (f {x={a=1, b=2}, y={a=3, b=4}, z={b=5}}). + to_equal {a={x=1, y=3}, b={x=2,y=4,z=5}} + + +- describe zip_with: + - before: + tt = {{1, 2}, {3, 4}, {5}} + fn = function (...) return tonumber (table.concat {...}) end + + fname = "zip_with" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "function")) + expect (f (fn)).to_error (msg (2, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (fn, false)).to_error (msg (2, "table", "boolean")) + expect (f (fn, {{}, false})). + to_error (msg (2, "table of tables", "boolean at index 2")) + - it diagnoses too many arguments: + expect (f (fn, tt, false)). + to_error (toomanyarg (this_module, fname, 2, 3)) + + - it works for an empty table: + expect (f (fn, {})).to_equal {} + - it returns a table: + expect (type (f (fn, tt))).to_be "table" + - it returns the result in a new table: + expect (f (fn, tt)).not_to_be (tt) + - it does not perturb the argument list: + m = f (fn, tt) + expect (tt).to_equal {{1, 2}, {3, 4}, {5}} + - it combines column entries with a function: + expect (f (fn, tt)).to_equal {135, 24} + - it discards hash-part arguments: + expect (f (fn, {{1,2}, x={3,4}, {[2]=5}})).to_equal {1, 25} + - it combines matching key entries with a function: + expect (f (fn, {{a=1,b=2}, {a=3,b=4}, {b=5}})). + to_equal {a=13, b=245} diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 9befb78..1f9c732 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1174,13 +1174,17 @@ specify std.list: msg = bind (badarg, {this_module, fname}) f = M[fname] - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list.transpose' was deprecated" + end + _, err = capture (f, {l}) + expect (err).to_be (nil) - it transposes rows and columns: expect (f (l)).to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) @@ -1227,19 +1231,17 @@ specify std.list: msg = bind (badarg, {this_module, fname}) f = M[fname] - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) - expect (f (l)).to_error (msg (2, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) - expect (f (List {{}}, f)). - to_error (msg (1, "List of Lists", "empty table at index 1")) - expect (f (List {List {}, false}, f)). - to_error (msg (1, "List of Lists", "boolean at index 2")) - expect (f (l, false)).to_error (msg (2, "function", "boolean")) - - it diagnoses too many arguments: - expect (f (l, fn, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {l, fn}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list.zip_with' was deprecated" + end + _, err = capture (f, {l, fn}) + expect (err).to_be (nil) - it returns a list object: expect (prototype (f (l, fn))).to_be "List" From 129a7a86575e47b1bbc8940eb2638f5ba60ce7f2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 15 Aug 2014 18:47:22 +0100 Subject: [PATCH 359/703] refactor: use new functional apis in std.tree. * lib/std/tree.lua (reduce, operator): Use these... (fold, op): ...instead of these. Signed-off-by: Gary V. Vaughan --- lib/std/tree.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 52f85f8..8678142 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -14,11 +14,13 @@ local base = require "std.base" local container = require "std.container" local func = require "std.functional" +local operator = require "std.operator" local Container = container {} -local ipairs, pairs = base.ipairs, base.pairs -local ielems, base_leaves, prototype = base.ielems, base.leaves, base.prototype -local fold, op = func.fold, func.op + +local ielems, ipairs, base_leaves, pairs, prototype = + base.ielems, base.ipairs, base.leaves, base.pairs, base.prototype +local reduce = func.reduce local Tree -- forward declaration @@ -225,7 +227,7 @@ Tree = Container { -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? __index = function (self, i) if prototype (i) == "table" then - return fold (op["[]"], self, ielems, i) + return reduce (operator["[]"], self, ielems, i) else return rawget (self, i) end From a5fc95be9aa933b8b477896628c1f8ed0baa0651 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 15 Aug 2014 18:56:50 +0100 Subject: [PATCH 360/703] refactor: share leaves implementation from new std.base.tree. * lib/std/base.lua (leaves): Move from here... * lib/std/base/tree.lua (leaves): New file. ...to here. * local.mk (dist_luastdbase_DATA): Add lib/std/base/tree.lua. * lib/std/io.lua, lib/std/list.lua, lib/std/tree.lua: Adjust. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 23 ----------------------- lib/std/base/tree.lua | 28 ++++++++++++++++++++++++++++ lib/std/io.lua | 2 +- lib/std/list.lua | 2 +- lib/std/tree.lua | 2 +- local.mk | 1 + 6 files changed, 32 insertions(+), 26 deletions(-) create mode 100644 lib/std/base/tree.lua diff --git a/lib/std/base.lua b/lib/std/base.lua index cbf79e6..cd377b8 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -261,26 +261,6 @@ end ---[[ ======================= ]]-- ---[[ Documented in tree.lua. ]]-- ---[[ ======================= ]]-- - - -local function leaves (it, tr) - local function visit (n) - if type (n) == "table" then - for _, v in it (n) do - visit (v) - end - else - coroutine.yield (n) - end - end - return coroutine.wrap (visit), tr -end - - - --[[ ================== ]]-- --[[ Argument Checking. ]]-- --[[ ================== ]]-- @@ -919,9 +899,6 @@ return setmetatable ({ -- table.lua -- getmetamethod = getmetamethod, - -- tree.lua -- - leaves = leaves, - -- Argument Checking. -- argcheck = argcheck, argerror = argerror, diff --git a/lib/std/base/tree.lua b/lib/std/base/tree.lua new file mode 100644 index 0000000..62fad4e --- /dev/null +++ b/lib/std/base/tree.lua @@ -0,0 +1,28 @@ +--[[-- + Base implementations of functions exported by `std.tree`. + + These functions are required by implementations of exported functions + in other stdlib modules. We keep them here to avoid bloating std.base, + which is loaded by *every* stdlib module. + + @module std.base.tree +]] + + +local function leaves (it, tr) + local function visit (n) + if type (n) == "table" then + for _, v in it (n) do + visit (v) + end + else + coroutine.yield (n) + end + end + return coroutine.wrap (visit), tr +end + + +return { + leaves = leaves, +} diff --git a/lib/std/io.lua b/lib/std/io.lua index fdd3a7e..f0ef8de 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -19,7 +19,7 @@ local package = { local ipairs, pairs = base.ipairs, base.pairs local argerror, export, leaves, split = - base.argerror, base.export, base.leaves, base.split + base.argerror, base.export, base.tree.leaves, base.split local M = { "std.io" } diff --git a/lib/std/list.lua b/lib/std/list.lua index cdcae1f..796d29e 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -179,7 +179,7 @@ end) -- @tparam List l a list -- @treturn List flattened list local flatten = export (M, "flatten (List)", function (l) - return List (func.collect (base.leaves, ipairs, l)) + return List (func.collect (base.tree.leaves, ipairs, l)) end) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 8678142..7cb77a3 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -19,7 +19,7 @@ local operator = require "std.operator" local Container = container {} local ielems, ipairs, base_leaves, pairs, prototype = - base.ielems, base.ipairs, base.leaves, base.pairs, base.prototype + base.ielems, base.ipairs, base.tree.leaves, base.pairs, base.prototype local reduce = func.reduce local Tree -- forward declaration diff --git a/local.mk b/local.mk index 3126c0a..19e23cd 100644 --- a/local.mk +++ b/local.mk @@ -86,6 +86,7 @@ luastdbasedir = $(luastddir)/base dist_luastdbase_DATA = \ lib/std/base/functional.lua \ + lib/std/base/tree.lua \ $(NOTHING_ELSE) # For bugwards compatibility with LuaRocks 2.1, while ensuring that From 16a0667dcd301a3c0b8e60b359709305a56c5518 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 15 Aug 2014 19:29:22 +0100 Subject: [PATCH 361/703] refactor: merge most of std.base.functional back into functional. * lib/std/base/functional.lua: Remove comment about needing to wait until deprecated access points are gone before merging. (foldl, foldr, memoize, nop): Move from here... * lib/std/functional.lua (foldl, foldr, memoize, nop): ...to here. * lib/std/list.lua (foldl, foldr): Keep a file local copy of these functions to satisfy deprecated access points. Signed-off-by: Gary V. Vaughan --- lib/std/base/functional.lua | 64 ++--------------------------------- lib/std/functional.lua | 66 +++++++++++++++++++++++++++++++------ lib/std/list.lua | 20 ++++++++++- 3 files changed, 78 insertions(+), 72 deletions(-) diff --git a/lib/std/base/functional.lua b/lib/std/base/functional.lua index 01bfd46..bbdfb00 100644 --- a/lib/std/base/functional.lua +++ b/lib/std/base/functional.lua @@ -1,54 +1,20 @@ --[[-- Base implementations of functions exported by `std.functional`. - The only reason to keep these here is to support deprecated access points - to the shared implementations, where they are only loaded when actually - needed, rather than cluttering `std.base`. - - These will be merged back into `std.functional` when the deprecated access - points are no longer supported. + These functions are required by implementations of exported functions + in other stdlib modules. We keep them here to avoid bloating std.base, + which is loaded by *every* stdlib module. @module std.base.functional ]] -local base = require "std.base" -local operator = require "std.operator" - - -local ipairs, ireverse, len = base.ipairs, base.ireverse, base.len - - local function callable (x) if type (x) == "function" then return true end return type ((getmetatable (x) or {}).__call) == "function" end -local function memoize (fn, normalize) - if normalize == nil then - -- Call require here, to avoid pulling in all of 'std.string' - -- even when memoize is never called. - normalize = function (...) return require "std.base".tostring {...} end - end - - return setmetatable ({}, { - __call = function (self, ...) - local k = normalize (...) - local t = self[k] - if t == nil then - t = {fn (...)} - self[k] = t - end - return unpack (t) - end - }) -end - - -local function nop () end - - local function reduce (fn, d, ifn, ...) local nextfn, state, k = ifn (...) local t = {nextfn (state, k)} @@ -62,31 +28,7 @@ local function reduce (fn, d, ifn, ...) end -local function foldl (fn, d, t) - if t == nil then - local tail = {} - for i = 2, len (d) do tail[#tail + 1] = d[i] end - d, t = d[1], tail - end - return reduce (fn, d, ipairs, t) -end - - -local function foldr (fn, d, t) - if t == nil then - local u, last = {}, len (d) - for i = 1, last - 1 do u[#u + 1] = d[i] end - d, t = d[last], u - end - return reduce (function (x, y) return fn (y, x) end, d, ipairs, ireverse (t)) -end - - return { callable = callable, - foldl = foldl, - foldr = foldr, - memoize = memoize, - nop = nop, reduce = reduce, } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index a7135e0..37169ca 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -11,14 +11,46 @@ local base = require "std.base" local operator = require "std.operator" -local callable = base.functional.callable -local export, ielems, ipairs, ireverse, pairs = - base.export, base.ielems, base.ipairs, base.ireverse, base.pairs +local export, ielems, ipairs, ireverse, len, pairs = + base.export, base.ielems, base.ipairs, base.ireverse, base.len, base.pairs +local callable, reduce = base.functional.callable, base.functional.reduce local M = { "std.functional" } +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + +local function memoize (fn, normalize) + if normalize == nil then + -- Call require here, to avoid pulling in all of 'std.string' + -- even when memoize is never called. + normalize = function (...) return require "std.base".tostring {...} end + end + + return setmetatable ({}, { + __call = function (self, ...) + local k = normalize (...) + local t = self[k] + if t == nil then + t = {fn (...)} + self[k] = t + end + return unpack (t) + end + }) +end + + + +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- + + --- Partially apply a function. -- @function bind -- @func fn function to apply partially @@ -248,7 +280,14 @@ end) -- @see reduce -- @usage -- foldl (lambda "/", {10000, 100, 10}) == (10000 / 100) / 10 -export (M, "foldl (function, [any], table)", base.functional.foldl) +export (M, "foldl (function, [any], table)", function (fn, d, t) + if t == nil then + local tail = {} + for i = 2, len (d) do tail[#tail + 1] = d[i] end + d, t = d[1], tail + end + return reduce (fn, d, ipairs, t) +end) --- Fold a binary function right associatively. @@ -263,7 +302,14 @@ export (M, "foldl (function, [any], table)", base.functional.foldl) -- @see reduce -- @usage -- foldr (lambda "/", {10000, 100, 10}) == 10000 / (100 / 10) -export (M, "foldr (function, [any], table)", base.functional.foldr) +export (M, "foldr (function, [any], table)", function (fn, d, t) + if t == nil then + local u, last = {}, len (d) + for i = 1, last - 1 do u[#u + 1] = d[i] end + d, t = d[last], u + end + return reduce (function (x, y) return fn (y, x) end, d, ipairs, ireverse (t)) +end) --- Identity function. @@ -297,7 +343,7 @@ end -- lambda '<' -- lambda '= _1 < _2' -- lambda '|a,b| a 2 ^ 3 ^ 4 ==> 4096 -- reduce (lambda '^', 2, std.ipairs, {3, 4}) -export (M, "reduce (func, any, func, any*)", base.functional.reduce) +export (M, "reduce (func, any, func, any*)", reduce) --- Zip a table of tables. @@ -492,7 +538,7 @@ M.eval = DEPRECATED ("41", "'std.functional.eval'", M.fold = DEPRECATED ("41", "'std.functional.fold'", - "use 'std.functional.reduce' instead", base.functional.reduce) + "use 'std.functional.reduce' instead", reduce) return M diff --git a/lib/std/list.lua b/lib/std/list.lua index 796d29e..ea0c91f 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -37,7 +37,6 @@ local List -- forward declaration local ipairs, pairs = base.ipairs, base.pairs local argerror, argscheck, export, ielems, prototype, ireverse = base.argerror, base.argscheck, base.export, base.ielems, base.prototype, base.ireverse -local foldl, foldr = base.functional.foldl, base.functional.foldr local M = { "std.list" } @@ -324,10 +323,29 @@ M.relems = DEPRECATED ("41", "'std.list.relems'", "compose 'std.ielems' and 'std.ireverse' instead", relems) +local function foldl (fn, d, t) + if t == nil then + local tail = {} + for i = 2, len (d) do tail[#tail + 1] = d[i] end + d, t = d[1], tail + end + return base.functional.reduce (fn, d, ipairs, t) +end + M.foldl = DEPRECATED ("41", "'std.list.foldl'", "use 'std.functional.foldl' instead", foldl) +local function foldr (fn, d, t) + if t == nil then + local u, last = {}, len (d) + for i = 1, last - 1 do u[#u + 1] = d[i] end + d, t = d[last], u + end + return base.functional.reduce ( + function (x, y) return fn (y, x) end, d, ipairs, ireverse (t)) +end + M.foldr = DEPRECATED ("41", "'std.list.foldr'", "use 'std.functional.foldr' instead", foldr) From cc4ac26ee0a10d3696433d5775f847c4e56bcb3c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 15 Aug 2014 23:31:08 +0100 Subject: [PATCH 362/703] refactor: move list.flatten to functional.flatten. * lib/std/list.lua (flatten): Deprecate. * lib/std/functional.lua (flatten): Export from here. * specs/list_spec.yaml, specs/functional_spec.yaml: Adjust. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/functional.lua | 12 +++++++++- lib/std/list.lua | 19 ++++++++-------- specs/functional_spec.yaml | 29 +++++++++++++++++++++--- specs/list_spec.yaml | 46 ++++++++++++++++++++++++++------------ 5 files changed, 81 insertions(+), 28 deletions(-) diff --git a/NEWS b/NEWS index 5ed3fb5..bba098c 100644 --- a/NEWS +++ b/NEWS @@ -149,6 +149,9 @@ Stdlib NEWS - User visible changes - `functional.fold` has been renamed to `functional.reduce`, the old name now gives a deprecation warning. + - `list.flatten` has been moved to `functional.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 diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 37169ca..fb41f6a 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -14,6 +14,7 @@ local operator = require "std.operator" local export, ielems, ipairs, ireverse, len, pairs = base.export, base.ielems, base.ipairs, base.ireverse, base.len, base.pairs local callable, reduce = base.functional.callable, base.functional.reduce +local leaves = base.tree.leaves local M = { "std.functional" } @@ -133,7 +134,7 @@ end) -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} -export (M, "collect ([func], any*)", function (ifn, ...) +local collect = export (M, "collect ([func], any*)", function (ifn, ...) local argt = {...} if not callable (ifn) then ifn, argt = ipairs, {ifn, ...} @@ -268,6 +269,15 @@ export (M, "filter (func, [func], any*)", function (pfn, ifn, ...) end) +--- Flatten a nested table into a list. +-- @function flatten +-- @tparam table t a table +-- @treturn table a list of all non-table elements of *t* +export (M, "flatten (table)", function (t) + return collect (leaves, ipairs, t) +end) + + --- Fold a binary function left associatively. -- If parameter *d* is omitted, the first element of *t* is used, -- and *t* treated as if it had been passed without that element. diff --git a/lib/std/list.lua b/lib/std/list.lua index ea0c91f..a951923 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -172,16 +172,6 @@ local filter = export (M, "filter (function, List)", function (p, l) end) ---- Flatten a list. --- @static --- @function flatten --- @tparam List l a list --- @treturn List flattened list -local flatten = export (M, "flatten (List)", function (l) - return List (func.collect (base.tree.leaves, ipairs, l)) -end) - - --- Project a list of fields from a list of tables. -- @static -- @function project @@ -231,6 +221,11 @@ end) -- @tparam List l a list -- @return reshaped list -- @see std.list:shape + +local function flatten (l) + return List (func.collect (base.tree.leaves, ipairs, l)) +end + local shape = export (M, "shape (table, List)", function (s, l) l = flatten (l) -- Check the shape and calculate the size of the zero, if any @@ -323,6 +318,10 @@ M.relems = DEPRECATED ("41", "'std.list.relems'", "compose 'std.ielems' and 'std.ireverse' instead", relems) +M.flatten = DEPRECATED ("41", "'std.list.flatten'", + "use 'std.functional.flatten' instead", flatten) + + local function foldl (fn, d, t) if t == nil then local tail = {} diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 049a8f0..3509f09 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -5,9 +5,9 @@ before: global_table = "_G" exported_apis = { 1, "bind", "callable", "case", "collect", "compose", - "cond", "curry", "eval", "filter", "fold", "foldl", - "foldr", "id", "lambda", "map", "map_with", "memoize", - "nop", "op", "reduce", "zip", "zip_with" } + "cond", "curry", "eval", "filter", "flatten", "fold", + "foldl", "foldr", "id", "lambda", "map", "map_with", + "memoize", "nop", "op", "reduce", "zip", "zip_with" } M = require (this_module) @@ -291,6 +291,29 @@ specify std.functional: to_equal {"first", last="three"} +- describe flatten: + - before: + t = {{{"one"}}, "two", {{"three"}, "four"}} + + fname = "flatten" + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it diagnoses missing arguments: + expect (f ()).to_error (msg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (msg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f (t, false)). + to_error (toomanyarg (this_module, fname, 1, 2)) + + - it returns a table: + expect (type (f (t))).to_be "table" + - it works for an empty table: + expect (f {}).to_equal {} + - it flattens a nested table: + expect (f (t)).to_equal {"one", "two", "three", "four"} + - describe fold: - before: fname = "fold" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 1f9c732..2acda40 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -447,26 +447,44 @@ specify std.list: l = List {List {List {"one"}}, "two", List {List {"three"}, "four"}} fname = "flatten" - msg = bind (badarg, {this_module, fname}) - f = M[fname] - - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + - context as a module function: + - before: + msg = bind (badarg, {this_module, fname}) + f = M[fname] + + - it writes a deprecation warning to standard error on first call: | + -- Unwrap functable + f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call + + _, err = capture (f, {l}) + if err ~= nil then + -- skip this test when using Specl < 12 capture stub + expect (err).to_contain "'std.list.flatten' was deprecated" + end + _, err = capture (f, {l}) + expect (err).to_be (nil) - - context when called as a list object method: - it returns a list object: - m = List.flatten (l) - expect (prototype (m)).to_be "List" + expect (prototype (f (l))).to_be "List" - it works for an empty list: l = List {} - expect (l:flatten ()).to_equal (List {}) + expect (f (l)).to_equal (List {}) - it flattens a list: - expect (l:flatten ()). + expect (f (l)). + to_equal (List {"one", "two", "three", "four"}) + + - context as a List object method: + - before: + f = l[fname] + + - it returns a list object: + expect (prototype (f (l))).to_be "List" + - it works for an empty list: + l = List {} + expect (f (l)).to_equal (List {}) + - it flattens a list: + expect (f (l)). to_equal (List {"one", "two", "three", "four"}) From cfa2f9066a92197fb05f7a6ffca2f7a7d348b74d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 16 Aug 2014 17:53:58 +0100 Subject: [PATCH 363/703] base: ensure export errors report callsite in stack trace. * specs/base_spec.yaml (export): Finish and simplify mkstack(). Specify callsite line numbers in export errors. * lib/std/base.lua (formaterror): Allow expectedtypes to be a string. (export): Use it to generate error messages. (export): Set levels correctly for correct callsite reporting. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 42 ++++++++++++++++++++++-------------------- specs/base_spec.yaml | 40 ++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index cd377b8..3a61649 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -460,25 +460,27 @@ local function formaterror (expectedtypes, actual, index) end -- Tidy up expected types for display. - local t = {} - for i, v in ipairs (expectedtypes) do - if v == "func" then - t[i] = "function" - elseif v == "any" then - t[i] = "any value" - elseif not index then - t[i] = v:match "(%S+) of %S+" or v - else - t[i] = v + local expectedstr = expectedtypes + if type (expectedtypes) == "table" then + local t = {} + for i, v in ipairs (expectedtypes) do + if v == "func" then + t[i] = "function" + elseif v == "any" then + t[i] = "any value" + elseif not index then + t[i] = v:match "(%S+) of %S+" or v + else + t[i] = v + end end + expectedstr = concat (t): + gsub ("#table", "non-empty table"): + gsub ("#list", "non-empty list"): + gsub ("(%S+ of %S+)", "%1s"): + gsub ("(%S+ of %S+)ss", "%1s") end - local expectedstr = concat (t): - gsub ("#table", "non-empty table"): - gsub ("#list", "non-empty list"): - gsub ("(%S+ of %S+)", "%1s"): - gsub ("(%S+ of %S+)ss", "%1s") - return expectedstr .. " expected, got " .. actualtype end @@ -701,13 +703,13 @@ local function export (M, decl, fn, ...) if arglen (args) > 3 then error (string.format (toomanyarg_fmt, fname, 3, arglen (args)), 2) elseif type (M[1]) ~= "string" then - argerror (fname, 1, "module name at index 1 expected, got no value") + argerror (fname, 1, formaterror ("module name at index 1", M[1]), 2) elseif name == nil then - argerror (fname, 2, "function name expected") + argerror (fname, 2, formaterror ("function name", name), 2) elseif types == nil then - argerror (fname, 2, "argument type specifications expected") + argerror (fname, 2, formaterror ("argument type specification", types), 2) elseif #types < 1 then - argerror (fname, 2, "at least 1 argument type expected, got 0") + argerror (fname, 2, "at least 1 argument type expected, got " .. #types, 2) end local name = M[1] .. (M[2] and ":" or ".") .. name diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 2dbe134..e0a6946 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -97,19 +97,15 @@ specify std.base: - describe export: - - before: - function mkstack (level, types) + - before: | + function mkstack (name, spec) return string.format ([[ - _DEBUG = %s -- line 1 - local export = require "std.base".export -- line 2 - export ("ohnoes", 1, "table", t, %s) -- line 4 - end -- line 5 - function caller () -- line 6 - local r = ohnoes "not a table" -- line 7 - return "not a tail call" -- line 8 - end -- line 9 - caller () -- line 10 - ]], tostring (debugp), tostring (level)) + local export = require "std.base".export -- line 1 + local function caller () -- line 2 + export ({%s}, "%s", function () end) -- line 3 + end -- line 4 + caller () -- line 5 + ]], tostring (name), tostring (spec)) end mkmagic = function () return "MAGIC" end @@ -129,16 +125,16 @@ specify std.base: - it diagnoses too many arguments: expect (f ({}, "", function () end, false)). to_error (toomanyarg (this_module, fname, 3, 4)) - - it diagnoses missing module name element: - expect (f ({}, "", function () end)). - to_error (msg (1, "module name at index 1")) - - it diagnoses malformed declaration string: | - M = { "base_spec.yaml" } - expect (f (M, "", function () end)).to_error "function name expected" - expect (f (M, "woo", function () end)). - to_error "argument type specifications expected" - expect (f (M, "woo ()", function () end)). - to_error (msg (2, "at least 1 argument type", "0")) + - it diagnoses missing module name element at callsite: | + expect (luaproc (mkstack ("", ""))). + to_contain_error (":3: " .. msg (1, "module name at index 1")) + - it diagnoses malformed declaration string at callsite: | + expect (luaproc (mkstack ('"mkstack"', ""))). + to_contain_error (":3: " .. msg (2, "function name")) + expect (luaproc (mkstack ('"mkstack"', "woo"))). + to_contain_error (":3: " .. msg (2, "argument type specification")) + expect (luaproc (mkstack ('"mkstack"', "woo ()"))). + to_contain_error (":3: " .. msg (2, "at least 1 argument type", "0")) - it returns the supplied function: M = { "base_spec.yaml" } From 373bd56e5483e5f3699e53cbfe63fae180027bb6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 16 Aug 2014 18:15:05 +0100 Subject: [PATCH 364/703] base: support zero argument exports. * specs/base_spec.yaml (export): Remove specification for zero argument export error. * lib/std/base.lua (export): Remove zero argument error. * specs/base_spec.yaml (export): Specify argument checking of zero argument exports. * lib/std/base.lua (export): Support zero arguments. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 4 +--- specs/base_spec.yaml | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 3a61649..c29be2a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -708,8 +708,6 @@ local function export (M, decl, fn, ...) argerror (fname, 2, formaterror ("function name", name), 2) elseif types == nil then argerror (fname, 2, formaterror ("argument type specification", types), 2) - elseif #types < 1 then - argerror (fname, 2, "at least 1 argument type expected, got " .. #types, 2) end local name = M[1] .. (M[2] and ":" or ".") .. name @@ -717,7 +715,7 @@ local function export (M, decl, fn, ...) -- If the final element of types ends with "*", then set max to a -- sentinel value to denote type-checking of *all* remaining -- unchecked arguments against that type-spec is required. - local max, fin = #types, types[#types]:match "^(.+)%*$" + local max, fin = #types, (types[#types] or ""):match "^(.+)%*$" if fin then max = math.huge types[#types] = fin diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index e0a6946..7a9e4f1 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -133,8 +133,6 @@ specify std.base: to_contain_error (":3: " .. msg (2, "function name")) expect (luaproc (mkstack ('"mkstack"', "woo"))). to_contain_error (":3: " .. msg (2, "argument type specification")) - expect (luaproc (mkstack ('"mkstack"', "woo ()"))). - to_contain_error (":3: " .. msg (2, "at least 1 argument type", "0")) - it returns the supplied function: M = { "base_spec.yaml" } @@ -153,6 +151,19 @@ specify std.base: os.exit (M.export == export and 0 or 1) ]] expect (luaproc (script)).to_succeed () + - context when checking zero argument function: + - before: + fake_module = "base_spec.yaml" + M = { fake_module } + fname = "chk_function" + msg = function (...) return badarg (fake_module, fname, ...) end + f (M, "chk_function ()", mkmagic) + chk = M[fname] + - it diagnoses too many arguments: + expect (chk (false)). + to_error (toomanyarg (fake_module, fname, 0, 1)) + - it accepts correct argument types: + expect (chk ()).to_be "MAGIC" - context when checking single argument function: - before: fake_module = "base_spec.yaml" From 95a8ff62ee5d6d5f63099e8fee5f12b2d3b07c16 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 17 Aug 2014 02:32:37 +0100 Subject: [PATCH 365/703] refactor: simplify argument error specifications. * specs/spec_helper.lua.in (toomanyarg): Remove. (badarg): Now file local, and treats one or two numeric args as a `too many arguments` error request. (init): Prebind badarg module and function names. * lib/std/base.lua: Add M[1] for error message matching. * specs/container_spec.yaml (construction): Unroll non-generated bad argument error messages. * specs/base_spec.yaml, specs/debug_spec.yaml, specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/math_spec.yaml, specs/package_spec.yaml, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Simplify argument error specifications. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 1 + specs/base_spec.yaml | 157 ++++++++++------------- specs/container_spec.yaml | 8 +- specs/debug_spec.yaml | 61 ++++----- specs/functional_spec.yaml | 213 +++++++++++++------------------ specs/io_spec.yaml | 113 +++++++---------- specs/list_spec.yaml | 170 ++++++++++--------------- specs/math_spec.yaml | 33 +++-- specs/package_spec.yaml | 71 +++++------ specs/spec_helper.lua.in | 36 +++--- specs/std_spec.yaml | 130 ++++++++----------- specs/string_spec.yaml | 249 +++++++++++++++---------------------- specs/table_spec.yaml | 242 +++++++++++++++++++---------------- 13 files changed, 642 insertions(+), 842 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index c29be2a..6ffc91f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -875,6 +875,7 @@ end) -- @section Metamethods return setmetatable ({ + "std.base", -- std.lua -- assert = assert, diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 7a9e4f1..6df9619 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -17,28 +17,25 @@ specify std.base: ) end - fname = "DEPRECATED" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "DEPRECATED") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - expect (f "version").to_error (msg (2, "string")) - expect (f ("version", "name")).to_error (msg (3, "string or function")) - expect (f ("version", "name", "extramsg")).to_error (msg (4, "function")) + expect (f ()).to_error (badarg (1, "string")) + expect (f "version").to_error (badarg (2, "string")) + expect (f ("version", "name")).to_error (badarg (3, "string or function")) + expect (f ("version", "name", "extramsg")).to_error (badarg (4, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("version", false)).to_error (msg (2, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("version", false)).to_error (badarg (2, "string", "boolean")) expect (f ("version", "name", false)). - to_error (msg (3, "string or function", "boolean")) + to_error (badarg (3, "string or function", "boolean")) expect (f ("version", "name", "extramsg", false)). - to_error (msg (4, "function", "boolean")) + to_error (badarg (4, "function", "boolean")) - it diagnoses too many arguments: | expect (f ("version", "name", "extramsg", nop, false)). - to_error (toomanyarg (this_module, fname, 4, 5)) + to_error (badarg (5)) pending "issue #76" - expect (f ("version", "name", nop, false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f ("version", "name", nop, false)).to_error (badarg (4)) - it returns a function: expect (type (f ("0", "deprecated", nop))).to_be "function" @@ -110,29 +107,26 @@ specify std.base: mkmagic = function () return "MAGIC" end - fname = "export" - msg = function (...) return badarg (this_module, fname, ...) end - f = M[fname] + f, badarg = init (M, "export") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) - expect (f ({})).to_error (msg (2, "string")) - expect (f ({}, "")).to_error (msg (3, "function")) + expect (f ()).to_error (badarg (1, "table")) + expect (f ({})).to_error (badarg (2, "string")) + expect (f ({}, "")).to_error (badarg (3, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) - expect (f ({}, false)).to_error (msg (2, "string", "boolean")) - expect (f ({}, "", false)).to_error (msg (3, "function", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_error (badarg (2, "string", "boolean")) + expect (f ({}, "", false)).to_error (badarg (3, "function", "boolean")) - it diagnoses too many arguments: - expect (f ({}, "", function () end, false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f ({}, "", function () end, false)).to_error (badarg (4)) - it diagnoses missing module name element at callsite: | expect (luaproc (mkstack ("", ""))). - to_contain_error (":3: " .. msg (1, "module name at index 1")) + to_contain_error (":3: " .. badarg (1, "module name at index 1")) - it diagnoses malformed declaration string at callsite: | expect (luaproc (mkstack ('"mkstack"', ""))). - to_contain_error (":3: " .. msg (2, "function name")) + to_contain_error (":3: " .. badarg (2, "function name")) expect (luaproc (mkstack ('"mkstack"', "woo"))). - to_contain_error (":3: " .. msg (2, "argument type specification")) + to_contain_error (":3: " .. badarg (2, "argument type specification")) - it returns the supplied function: M = { "base_spec.yaml" } @@ -151,115 +145,100 @@ specify std.base: os.exit (M.export == export and 0 or 1) ]] expect (luaproc (script)).to_succeed () + - context when checking zero argument function: - before: - fake_module = "base_spec.yaml" - M = { fake_module } - fname = "chk_function" - msg = function (...) return badarg (fake_module, fname, ...) end + M = { "base_spec.yaml" } + _, badarg = init (M, "chk_function") f (M, "chk_function ()", mkmagic) - chk = M[fname] + chk = M.chk_function - it diagnoses too many arguments: - expect (chk (false)). - to_error (toomanyarg (fake_module, fname, 0, 1)) + expect (chk (false)).to_error (badarg (1)) - it accepts correct argument types: expect (chk ()).to_be "MAGIC" + - context when checking single argument function: - before: - fake_module = "base_spec.yaml" - M = { fake_module } - fname = "chk_function" - msg = function (...) return badarg (fake_module, fname, ...) end + M = { "base_spec.yaml" } + _, badarg = init (M, "chk_function") f (M, "chk_function (#table)", mkmagic) - chk = M[fname] + chk = M.chk_function - it diagnoses missing arguments: - expect (chk ()).to_error (msg (1, "non-empty table")) + expect (chk ()).to_error (badarg (1, "non-empty table")) - it diagnoses wrong argument types: - expect (chk {}).to_error (msg (1, "non-empty table", "empty table")) + expect (chk {}).to_error (badarg (1, "non-empty table", "empty table")) - it diagnoses too many arguments: - expect (chk ({1}, 2, nop, "", false)). - to_error (toomanyarg (fake_module, fname, 1, 5)) + expect (chk ({1}, 2, nop, "", false)).to_error (badarg (1, 5)) - it accepts correct argument types: expect (chk ({1})).to_be "MAGIC" + - context when checking multi-argument function: - before: - fake_module = "base_spec.yaml" - M = { fake_module } - fname = "chk_function" - msg = function (...) return badarg (fake_module, fname, ...) end + M = { "base_spec.yaml" } + _, badarg = init (M, "chk_function") f (M, "chk_function (table, function)", mkmagic) - chk = M[fname] + chk = M.chk_function - it diagnoses missing arguments: - expect (chk ()).to_error (msg (1, "table")) - expect (chk ({})).to_error (msg (2, "function")) + expect (chk ()).to_error (badarg (1, "table")) + expect (chk ({})).to_error (badarg (2, "function")) - it diagnoses wrong argument types: - expect (chk (false)).to_error (msg (1, "table", "boolean")) - expect (chk ({}, false)).to_error (msg (2, "function", "boolean")) + expect (chk (false)).to_error (badarg (1, "table", "boolean")) + expect (chk ({}, false)).to_error (badarg (2, "function", "boolean")) - it diagnoses too many arguments: - expect (chk ({}, nop, false)). - to_error (toomanyarg (fake_module, fname, 2, 3)) + expect (chk ({}, nop, false)).to_error (badarg (3)) - it accepts correct argument types: expect (chk ({}, nop)).to_be "MAGIC" + - context when checking optional argument function: - before: - fake_module = "base_spec.yaml" - M = { fake_module } - fname = "chk_function" - msg = function (...) return badarg (fake_module, fname, ...) end + M = { "base_spec.yaml" } + _, badarg = init (M, "chk_function") f (M, "chk_function ([int])", mkmagic) - chk = M[fname] + chk = M.chk_function - it diagnoses wrong argument types: - expect (chk (false)).to_error (msg (1, "int or nil", "boolean")) + expect (chk (false)).to_error (badarg (1, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (chk (1, nop)). - to_error (toomanyarg (fake_module, fname, 1, 2)) + expect (chk (1, nop)).to_error (badarg (2)) - it accepts correct argument types: expect (chk ()).to_be "MAGIC" expect (chk (1)).to_be "MAGIC" + - context when checking optional multi-argument function: - before: - fake_module = "base_spec.yaml" - M = { fake_module } - fname = "chk_function" - msg = function (...) return badarg (fake_module, fname, ...) end + M = { "base_spec.yaml" } + _, badarg = init (M, "chk_function") f (M, "chk_function ([int], string)", mkmagic) - chk = M[fname] + chk = M.chk_function - it diagnoses missing arguments: - expect (chk ()).to_error (msg (1, "int or string")) - expect (chk (1)).to_error (msg (2, "string")) + expect (chk ()).to_error (badarg (1, "int or string")) + expect (chk (1)).to_error (badarg (2, "string")) - it diagnoses wrong argument types: - expect (chk (false)).to_error (msg (1, "int or string", "boolean")) + expect (chk (false)).to_error (badarg (1, "int or string", "boolean")) - it diagnoses too many arguments: - expect (chk (1, "two", nop)). - to_error (toomanyarg (fake_module, fname, 2, 3)) + expect (chk (1, "two", nop)).to_error (badarg (3)) - it accepts correct argument types: expect (chk ("two")).to_be "MAGIC" expect (chk (1, "two")).to_be "MAGIC" + - context when checking self on an object method: - before: - fake_module = "base_spec.yaml" - M = { fake_module, "Object" } - fname = "chk_method" - msg = function (...) - return badarg (nil, fake_module .. ":" .. fname, ...) - end + M = { "base_spec.yaml", "Object" } + _, badarg = init (M, "chk_method") f (M, "chk_method (string)", function (self, x) return x end) Object = require "std.object" {} Bad = Object { _type = "Bad" } obj = Object { chk = M.chk_method } - it diagnoses missing arguments: - expect (obj.chk ()).to_error (msg (0, "Object")) - expect (obj:chk ()).to_error (msg (1, "string")) + expect (obj.chk ()).to_error (badarg (0, "Object")) + expect (obj:chk ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (obj.chk ({})).to_error (msg (0, "Object", "empty table")) - expect (obj.chk (Bad)).to_error (msg (0, "Object", "Bad")) - expect (obj:chk ({})).to_error (msg (1, "string", "empty table")) - expect (obj:chk (obj)).to_error (msg (1, "string", "Object")) + expect (obj.chk ({})).to_error (badarg (0, "Object", "empty table")) + expect (obj.chk (Bad)).to_error (badarg (0, "Object", "Bad")) + expect (obj:chk ({})).to_error (badarg (1, "string", "empty table")) + expect (obj:chk (obj)).to_error (badarg (1, "string", "Object")) - it diagnoses too many arguments: - expect (obj.chk (obj, "str", false)). - to_error (toomanyarg (nil, fake_module .. ":" .. fname, 1, 2)) - expect (obj:chk ("str", false)). - to_error (toomanyarg (nil, fake_module .. ":" .. fname, 1, 2)) + expect (obj.chk (obj, "str", false)).to_error (badarg (2)) + expect (obj:chk ("str", false)).to_error (badarg (2)) - it accepts correct argument types: expect (obj.chk (obj, "str")).to_be "str" expect (obj.chk (Object, "str")).to_be "str" diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 7bca7a8..dfc90c4 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -11,10 +11,12 @@ specify std.container: - describe construction: - context with table _init: - - it diagnoses missing arguments: - expect (Container ()).to_raise (badarg (nil, "Container", 1, "table")) + - it diagnoses missing arguments: | + expect (Container ()). + to_raise "bad argument #1 to 'Container' (table expected, got no value)" - it diagnoses too many arguments: - expect (Container ({}, false)).to_raise (toomanyarg (nil, "Container", 1, 2)) + expect (Container ({}, false)). + to_raise "too many arguments to 'Container' (no more than 1 expected, got 2)" - context with function _init: - before: Thing = Container { _type = "Thing", _init = function (obj) return obj end } diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index f63a52f..a690e10 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -46,26 +46,23 @@ specify std.debug: ]], tostring (level)) end - fname = "argerror" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "argerror") - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" - expect (f ()).to_error (msg (1, "string")) - expect (f "foo").to_error (msg (2, "int")) + expect (f ()).to_error (badarg (1, "string")) + expect (f "foo").to_error (badarg (2, "int")) - it diagnoses wrong argument types: pending "Lua 5.1 support is dropped" - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("foo", false)).to_error (msg (2, "int", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("foo", false)).to_error (badarg (2, "int", "boolean")) expect (f ("foo", 1, false)). - to_error (msg (3, "string or nil", "boolean")) + to_error (badarg (3, "string or nil", "boolean")) expect (f ("foo", 1, "bar", false)). - to_error (msg (4, "int or nil", "boolean")) + to_error (badarg (4, "int or nil", "boolean")) - it diagnoses too many arguments: pending "Lua 5.1 support is dropped" - expect (f ("foo", 1, "bar", 2, false)). - to_error (toomanyarg (this_module, fname, 4, 5)) + expect (f ("foo", 1, "bar", 2, false)).to_error (badarg (5)) - it blames the call site by default: | expect (luaproc (mkstack ())).to_contain_error ":4: bad argument" @@ -103,26 +100,23 @@ specify std.debug: ]], tostring (debugp), tostring (level)) end - fname = "argcheck" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "argcheck") - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" - expect (f ()).to_error (msg (1, "string")) - expect (f "foo").to_error (msg (2, "int")) - expect (f ("foo", 1)).to_error (msg (3, "string")) + expect (f ()).to_error (badarg (1, "string")) + expect (f "foo").to_error (badarg (2, "int")) + expect (f ("foo", 1)).to_error (badarg (3, "string")) - it diagnoses wrong argument types: pending "Lua 5.1 support is dropped" - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("foo", false)).to_error (msg (2, "int", "boolean")) - expect (f ("foo", 1, false)).to_error (msg (3, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("foo", false)).to_error (badarg (2, "int", "boolean")) + expect (f ("foo", 1, false)).to_error (badarg (3, "string", "boolean")) expect (f ("foo", 1, "bar", 2, false)). - to_error (msg (5, "int or nil", "boolean")) + to_error (badarg (5, "int or nil", "boolean")) - it diagnoses too many arguments: pending "Lua 5.1 support is dropped" - expect (f ("foo", 1, "bar", 2, 3, false)). - to_error (toomanyarg (this_module, fname, 5, 6)) + expect (f ("foo", 1, "bar", 2, 3, false)).to_error (badarg (6)) - it blames the calling function by default: | expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" @@ -446,24 +440,21 @@ specify std.debug: ]], tostring (debugp)) end - fname = "argscheck" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "argscheck") - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" - expect (f "foo").to_error (msg (2, "non-empty list")) - expect (f ("foo", {"bar"})).to_error (msg (3, "table")) + expect (f "foo").to_error (badarg (2, "non-empty list")) + expect (f ("foo", {"bar"})).to_error (badarg (3, "table")) - it diagnoses wrong argument types: pending "Lua 5.1 support is dropped" - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) expect (f ("foo", false)). - to_error (msg (2, "non-empty list", "boolean")) - expect (f ("foo", {"bar"}, false)).to_error (msg (3, "table", "boolean")) + to_error (badarg (2, "non-empty list", "boolean")) + expect (f ("foo", {"bar"}, false)).to_error (badarg (3, "table", "boolean")) - it diagnoses too many arguments: pending "Lua 5.1 support is dropped" - expect (f ("foo", {"bar"}, {}, false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f ("foo", {"bar"}, {}, false)).to_error (badarg (4)) - it blames the calling function: | expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" @@ -656,9 +647,7 @@ specify std.debug: - describe trace: - before: - fname = "trace" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f = init (M, "trace") - it does nothing when _DEBUG is disabled: expect (luaproc [[ diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 3509f09..b17fec4 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -31,25 +31,23 @@ specify std.functional: - describe bind: - before: fname = "bind" - f = M[fname] + f, badarg = init (M, fname) - # Long-hand badarg calls until we know bind meets specifications! - it diagnoses missing arguments: - expect (f ()).to_error (badarg (this_module, fname, 1, "function")) + expect (f ()).to_error (badarg (1, "function")) - it diagnoses wrong argument types: - expect (f (false)). - to_error (badarg (this_module, fname, 1, "function", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) - it writes a deprecation warning to standard error on first call: | -- Unwrap functable f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - _, err = capture (f, {badarg, this_module, fname}) + _, err = capture (f, {init, M, fname}) if err ~= nil then -- skip this test when using Specl < 12 capture stub expect (err).to_contain "multi-argument 'std.functional.bind' was deprecated" end - _, err = capture (f, {badarg, this_module, fname}) + _, err = capture (f, {init, M, fname}) expect (err).to_be (nil) - it does not affect normal operation if no arguments are bound: @@ -93,18 +91,15 @@ specify std.functional: default = function (s) return s end branches = { yes = yes, no = no, default } - fname = "case" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "case") - it diagnoses missing arguments: - expect (f ()).to_error (msg (2, "non-empty table")) + expect (f ()).to_error (badarg (2, "non-empty table")) - it diagnoses wrong argument types: expect (f ("no", false)). - to_error (msg (2, "non-empty table", "boolean")) + to_error (badarg (2, "non-empty table", "boolean")) - it diagnoses too many arguments: - expect (f (1, {2}, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (1, {2}, false)).to_error (badarg (3)) - it matches against branch keys: expect (f ("yes", branches)).to_be (true) @@ -136,12 +131,10 @@ specify std.functional: - describe collect: - before: - fname = "collect" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "collect") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function or any value")) + expect (f ()).to_error (badarg (1, "function or any value")) - it collects a list of single return value iterator results: expect (f (base.ielems, {"a", "b", "c"})).to_equal {"a", "b", "c"} @@ -154,15 +147,13 @@ specify std.functional: - describe compose: - before: - fname = "compose" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "compose") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) + expect (f ()).to_error (badarg (1, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, false)).to_error (msg (2, "function", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (f, false)).to_error (badarg (2, "function", "boolean")) - it composes a single function correctly: expect (f (M.id) (1)).to_be (1) @@ -178,9 +169,7 @@ specify std.functional: default = function (s) return s end branches = { yes = yes, no = no, default } - fname = "cond" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f = M.cond - it returns nil for no arguments: expect (f ()).to_be (nil) @@ -203,19 +192,16 @@ specify std.functional: - describe curry: - before: - fname = "curry" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "curry") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (f)).to_error (msg (2, "int")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (f)).to_error (badarg (2, "int")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, 1.234)).to_error (msg (2, "int", "number")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (f, 1.234)).to_error (badarg (2, "int", "number")) - it diagnoses too many arguments: - expect (f (f, 2, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (f, 2, false)).to_error (badarg (3)) - it returns a zero argument function uncurried: expect (f (f, 0)).to_be (f) @@ -233,9 +219,7 @@ specify std.functional: - describe eval: - before: - fname = "eval" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f = M.eval - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {"42"}) @@ -257,15 +241,14 @@ specify std.functional: - before: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) - fname = "filter" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + + f, badarg = init (M, "filter") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (f)).to_error (msg (2, "function or any value")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (f)).to_error (badarg (2, "function or any value")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) - it works with an empty table: expect (f (M.id, pairs, {})).to_equal {} @@ -295,17 +278,14 @@ specify std.functional: - before: t = {{{"one"}}, "two", {{"three"}, "four"}} - fname = "flatten" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "flatten") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (t, false)).to_error (badarg (2)) - it returns a table: expect (type (f (t))).to_be "table" @@ -316,8 +296,7 @@ specify std.functional: - describe fold: - before: - fname = "fold" - f = M[fname] + f = M.fold - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {M.id, 1, ipairs, {}}) @@ -344,20 +323,17 @@ specify std.functional: - describe foldl: - before: - fname = "foldl" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "foldl") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (f, nil)).to_error (msg (2, "any value or table")) - expect (f (f, 42)).to_error (msg (3, "table")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (f, nil)).to_error (badarg (2, "any value or table")) + expect (f (f, 42)).to_error (badarg (3, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, 42, false)).to_error (msg (3, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (f, 42, false)).to_error (badarg (3, "table", "boolean")) - it diagnoses too many arguments: - expect (f (f, 42, {}, false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f (f, 42, {}, false)).to_error (badarg (4)) - it works with an empty table: expect (f (M.op["+"], 10000, {})).to_be (10000) @@ -371,20 +347,17 @@ specify std.functional: - describe foldr: - before: - fname = "foldr" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "foldr") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (f, nil)).to_error (msg (2, "any value or table")) - expect (f (f, 42)).to_error (msg (3, "table")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (f, nil)).to_error (badarg (2, "any value or table")) + expect (f (f, 42)).to_error (badarg (3, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, 42, false)).to_error (msg (3, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (f, 42, false)).to_error (badarg (3, "table", "boolean")) - it diagnoses too many arguments: - expect (f (f, 42, {}, false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f (f, 42, {}, false)).to_error (badarg (4)) - it works with an empty table: expect (f (M.op["+"], 1, {})).to_be (1) @@ -408,16 +381,14 @@ specify std.functional: - describe lambda: - before: - fname = "lambda" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "lambda") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong arguments types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("foo", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("foo", false)).to_error (badarg (2)) - it diagnoses bad lambda string: expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" - it diagnoses an uncompilable expression: @@ -448,15 +419,14 @@ specify std.functional: - before: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) - fname = "map" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + + f, badarg = init (M, "map") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (f)).to_error (msg (2, "function or any value")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (f)).to_error (badarg (2, "function or any value")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) - it works with an empty table: expect (f (M.id, ipairs, {})).to_equal {} @@ -492,21 +462,18 @@ specify std.functional: t = {{1, 2, 3}, {4, 5}} fn = function (...) return select ("#", ...) end - fname = "map_with" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "map_with") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (fn)).to_error (msg (2, "table")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (fn)).to_error (badarg (2, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (fn, false)).to_error (msg (2, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (fn, false)).to_error (badarg (2, "table", "boolean")) expect (f (fn, {{}, false})). - to_error (msg (2, "table of tables", "boolean at index 2")) + to_error (badarg (2, "table of tables", "boolean at index 2")) - it diagnoses too many arguments: - expect (f (fn, t, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (fn, t, false)).to_error (badarg (3)) - it works for an empty table: expect (f (fn, {})).to_equal ({}) @@ -529,21 +496,19 @@ specify std.functional: - describe memoize: - before: - fname = "memoize" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "memoize") memfn = f (function (x) if x then return {x} else return nil, "bzzt" end end) - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) + expect (f ()).to_error (badarg (1, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, false)).to_error (msg (2, "function or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (f, false)).to_error (badarg (2, "function or nil", "boolean")) - it diagnoses too many arguments: - expect (f (f, f, false)).to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (f, f, false)).to_error (badarg (3)) - it propagates multiple return values: expect (select (2, memfn (false))).to_be "bzzt" @@ -583,17 +548,15 @@ specify std.functional: - describe reduce: - before: - fname = "reduce" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "reduce") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (f)).to_error (msg (2, "any value")) - expect (f (f, 1)).to_error (msg (3, "function")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (f)).to_error (badarg (2, "any value")) + expect (f (f, 1)).to_error (badarg (3, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (f, 1, false)).to_error (msg (3, "function", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (f, 1, false)).to_error (badarg (3, "function", "boolean")) - it works with an empty table: expect (f (M.op["+"], 2, ipairs, {})).to_be (2) @@ -613,17 +576,14 @@ specify std.functional: - before: tt = {{1, 2}, {3, 4}, {5, 6}} - fname = "zip" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "zip") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f (tt, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (tt, false)).to_error (badarg (2)) - it works for an empty table: expect (f {}).to_equal {} @@ -640,21 +600,18 @@ specify std.functional: tt = {{1, 2}, {3, 4}, {5}} fn = function (...) return tonumber (table.concat {...}) end - fname = "zip_with" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "zip_with") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) - expect (f (fn)).to_error (msg (2, "table")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (fn)).to_error (badarg (2, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (fn, false)).to_error (msg (2, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (fn, false)).to_error (badarg (2, "table", "boolean")) expect (f (fn, {{}, false})). - to_error (msg (2, "table of tables", "boolean at index 2")) + to_error (badarg (2, "table of tables", "boolean at index 2")) - it diagnoses too many arguments: - expect (f (fn, tt, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (fn, tt, false)).to_error (badarg (3)) - it works for an empty table: expect (f (fn, {})).to_equal {} diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 08e02c8..8cd961e 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -35,16 +35,14 @@ specify std.io: - describe catdir: - before: | - fname = "catdir" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "catdir") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("", false)).to_error (msg (2, "string", "boolean")) - expect (f ("", "false", false)).to_error (msg (3, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("", false)).to_error (badarg (2, "string", "boolean")) + expect (f ("", "false", false)).to_error (badarg (3, "string", "boolean")) - it treats initial empty string as root directory: expect (f ("")).to_be (dirsep) @@ -60,16 +58,14 @@ specify std.io: - describe catfile: - before: - fname = "catfile" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "catfile") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("", false)).to_error (msg (2, "string", "boolean")) - expect (f ("", "false", false)).to_error (msg (3, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("", false)).to_error (badarg (2, "string", "boolean")) + expect (f ("", "false", false)).to_error (badarg (3, "string", "boolean")) - it treats initial empty string as root directory: expect (f ("", "")).to_be (dirsep) @@ -87,14 +83,12 @@ specify std.io: - before: | script = [[require "std.io".die "By 'eck!"]] - fname = "die" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "die") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it outputs a message to stderr: expect (luaproc (script)).to_fail_with "By 'eck!\n" @@ -157,14 +151,13 @@ specify std.io: stderr = setmetatable ({}, mt), }, } - fname = "monkey_patch" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + + f, badarg = init (M, "monkey_patch") - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (t, false)).to_error (badarg (2)) - it installs readlines metamethod: f (t) @@ -190,20 +183,19 @@ specify std.io: catscript = [[ require "std.io".process_files (function () io.write (io.input ():read "*a") end) ]] - fname = "process_files" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + + f, badarg = init (M, "process_files") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "function")) + expect (f ()).to_error (badarg (1, "function")) - it diagnoses wrong argument types: | - expect (f (false)).to_error (msg (1, "function", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) 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 } - it diagnoses too many arguments: - expect (f (f, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (f, false)).to_error (badarg (2)) - it defaults to `-` if no arguments were passed: expect (luaproc (ascript)).to_output "-\n" @@ -233,23 +225,21 @@ specify std.io: h:close () defaultin = io.input () - fname = "readlines" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + + f, badarg = init (M, "readlines") - after: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end - it diagnoses wrong argument types: | - expect (f (false)).to_error (msg (1, "file, string or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "file, string or nil", "boolean")) expect (f "not-an-existing-file"). to_error "bad argument #1 to 'std.io.readlines' (" -- system dependent error message - it diagnoses closed file argument: closed = io.open (name, "r") closed:close () expect (f (closed)). - to_error (msg (1, "file, string or nil", "closed file")) + to_error (badarg (1, "file, string or nil", "closed file")) - it diagnoses too many arguments: - expect (f ("string", false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("string", false)).to_error (badarg (2)) - it closes file handle upon completion: h = io.open (name) @@ -267,17 +257,14 @@ specify std.io: - describe shell: - before: - fname = "shell" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "shell") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("string", false)).to_error (badarg (2)) - it returns the output from a shell command string: expect (f [[printf '%s\n' 'foo' 'bar']]).to_be "foo\nbar\n" @@ -291,23 +278,20 @@ specify std.io: h:close () defaultin = io.input () - fname = "slurp" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "slurp") - after: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end - it diagnoses wrong argument types: | - expect (f (false)).to_error (msg (1, "file, string or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "file, string or nil", "boolean")) expect (f "not-an-existing-file"). to_error "bad argument #1 to 'std.io.slurp' (" -- system dependent error message - it diagnoses closed file argument: closed = io.open (name, "r") closed:close () expect (f (closed)). - to_error (msg (1, "file, string or nil", "closed file")) + to_error (badarg (1, "file, string or nil", "closed file")) - it diagnoses too many arguments: - expect (f ("string", false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("string", false)).to_error (badarg (2)) - it reads content from an existing named file: expect (f (name)).to_be (content) @@ -325,17 +309,14 @@ specify std.io: - describe splitdir: - before: - fname = "splitdir" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "splitdir") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("string", false)).to_error (badarg (2)) - it returns a filename as a one element list: expect (f ("hello")).to_equal {"hello"} @@ -352,14 +333,12 @@ specify std.io: - describe warn: - before: script = [[require "std.io".warn "Ayup!"]] - fname = "warn" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "warn") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it outputs a message to stderr: expect (luaproc (script)).to_output_error "Ayup!\n" @@ -416,9 +395,7 @@ specify std.io: lines = M.readlines (io.open "specs/spec_helper.lua") defaultout = io.output () - fname = "writelines" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "writelines") - after: if io.type (defaultout) ~= "closed file" then io.output (defaultout) end h:close () @@ -426,11 +403,11 @@ specify std.io: - it diagnoses wrong argument types: | expect (f (false)). - to_error (msg (1, "file, string, number or nil", "boolean")) + to_error (badarg (1, "file, string, number or nil", "boolean")) - it diagnoses closed file argument: | closed = io.open (name) closed:close () expect (f (closed)). - to_error (msg (1, "file, string, number or nil", "closed file")) + to_error (badarg (1, "file, string, number or nil", "closed file")) - it does not close the file handle upon completion: expect (io.type (h)).not_to_be "closed file" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 2acda40..532c56d 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -75,18 +75,15 @@ specify std.list: - describe append: - before: - fname = "append" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "append") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) - expect (f (l)).to_error (msg (2, "any value")) + expect (f ()).to_error (badarg (1, "List")) + expect (f (l)).to_error (badarg (2, "any value")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) + expect (f (false)).to_error (badarg (1, "List", "boolean")) - it diagnoses too many arguments: - expect (f (l, 42, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (l, 42, false)).to_error (badarg (3)) - context when called as a list object method: - it returns a list object: @@ -114,19 +111,16 @@ specify std.list: - before: a, b = List {"foo", "bar"}, List {"foo", "baz"} - fname = "compare" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "compare") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) - expect (f (l)).to_error (msg (2, "List or table")) + expect (f ()).to_error (badarg (1, "List")) + expect (f (l)).to_error (badarg (2, "List or table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) - expect (f (a, false)).to_error (msg (2, "List or table", "boolean")) + expect (f (false)).to_error (badarg (1, "List", "boolean")) + expect (f (a, false)).to_error (badarg (2, "List or table", "boolean")) - it diagnoses too many arguments: - expect (f (a, b, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (a, b, false)).to_error (badarg (3)) - context when called as a list object method: - it returns -1 when the first list is less than the second: | @@ -190,18 +184,16 @@ specify std.list: - before: l = List {"foo", "bar"} - fname = "concat" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "concat") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) - expect (f (l)).to_error (msg (2, "List or table")) + expect (f ()).to_error (badarg (1, "List")) + expect (f (l)).to_error (badarg (2, "List or table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) - expect (f (l, false)).to_error (msg (2, "List or table", "boolean")) + expect (f (false)).to_error (badarg (1, "List", "boolean")) + expect (f (l, false)).to_error (badarg (2, "List or table", "boolean")) expect (f (l, l, false)). - to_error (msg (3, "List or table", "boolean")) + to_error (badarg (3, "List or table", "boolean")) - context when called as a list object method: - it returns a list object: @@ -235,8 +227,7 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning to standard error on first call: | -- Unwrap functable @@ -251,9 +242,9 @@ specify std.list: expect (err).to_be (nil) - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "any value")) + expect (f ()).to_error (badarg (1, "any value")) - it diagnoses wrong argument types: - expect (f ("head", false)).to_error (msg (2, "List or nil", "boolean")) + expect (f ("head", false)).to_error (badarg (2, "List or nil", "boolean")) - it returns a list object: expect (prototype (f ("head", l))).to_be "List" @@ -268,7 +259,6 @@ specify std.list: - context as a list object method: - before: - msg = bind (badarg, {this_module, fname}) f = l[fname] - it returns a list object: @@ -290,19 +280,20 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, fname) - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) + expect (f ()).to_error (badarg (1, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) + expect (f (false)).to_error (badarg (1, "List", "boolean")) expect (f (List {0})). - to_error (msg (1, "List of Lists", "number at index 1")) + to_error (badarg (1, "List of Lists", "number at index 1")) expect (f (List {{}})). - to_error (msg (1, "List of Lists", "empty table at index 1")) + to_error (badarg (1, "List of Lists", "empty table at index 1")) expect (f (List { List {"a", "b"}, ""})). - to_error (msg (1, "List of Lists", "string at index 2")) + to_error (badarg (1, "List of Lists", "string at index 2")) + - it diagnoses too many arguments: + expect (f (List { List {"a", "b"} }, false)).to_error (badarg (2)) - it returns a primitive table: expect (prototype (f (l))).to_be "table" @@ -392,16 +383,14 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, fname) - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it returns a list object: expect (prototype (f (t))).to_be "List" @@ -417,19 +406,16 @@ specify std.list: l = List {"foo", "bar", "baz", "quux"} p = function (e) return (e:match "a" ~= nil) end - fname = "filter" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "filter") - it diagnoses missing arguments: | - expect (f ()).to_error (msg (1, "function")) - expect (f (p)).to_error (msg (2, "List")) + expect (f ()).to_error (badarg (1, "function")) + expect (f (p)).to_error (badarg (2, "List")) - it diagnoses wrong argument types: | - expect (f (false)).to_error (msg (1, "function", "boolean")) - expect (f (p, false)).to_error (msg (2, "List", "boolean")) + expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (p, false)).to_error (badarg (2, "List", "boolean")) - it diagnoses too many arguments: - expect (f (p, l, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (p, l, false)).to_error (badarg (3)) - context when called as a list object method: - it returns a list object: @@ -450,7 +436,6 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) f = M[fname] - it writes a deprecation warning to standard error on first call: | @@ -496,7 +481,6 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) f = M[fname] - it writes a deprecation warning to standard error on first call: | @@ -562,7 +546,6 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) f = M[fname] - it writes a deprecation warning to standard error on first call: | @@ -775,7 +758,6 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) f = M[fname] - it writes a deprecation warning to standard error on first call: | @@ -834,7 +816,6 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) f = M[fname] - it writes a deprecation warning to standard error on first call: | @@ -901,22 +882,19 @@ specify std.list: {first = "1st", second = "2nd", third = "3rd"}, } - fname = "project" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "project") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "any value")) - expect (f (f)).to_error (msg (2, "List")) + expect (f ()).to_error (badarg (1, "any value")) + expect (f (f)).to_error (badarg (2, "List")) - it diagnoses wrong argument types: - expect (f (f, false)).to_error (msg (2, "List", "boolean")) + expect (f (f, false)).to_error (badarg (2, "List", "boolean")) expect (f (f, List {false})). - to_error (msg (2, "List of tables", "boolean at index 1")) + to_error (badarg (2, "List of tables", "boolean at index 1")) expect (f (f, List {{}, false})). - to_error (msg (2, "List of tables", "boolean at index 2")) + to_error (badarg (2, "List of tables", "boolean at index 2")) - it diagnoses too many arguments: - expect (f (f, l, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (f, l, false)).to_error (badarg (3)) - context when called as a list object method: - it returns a list object: @@ -990,19 +968,16 @@ specify std.list: - before: l = List {"foo", "bar"} - fname = "rep" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "rep") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) - expect (f (l)).to_error (msg (2, "int")) + expect (f ()).to_error (badarg (1, "List")) + expect (f (l)).to_error (badarg (2, "int")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) - expect (f (l, false)).to_error (msg (2, "int", "boolean")) + expect (f (false)).to_error (badarg (1, "List", "boolean")) + expect (f (l, false)).to_error (badarg (2, "int", "boolean")) - it diagnoses too many arguments: - expect (f (l, 2, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (l, 2, false)).to_error (badarg (3)) - context when called as a list object method: - it returns a list object: @@ -1079,19 +1054,16 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6} - fname = "shape" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "shape") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) - expect (f ({})).to_error (msg (2, "List")) + expect (f ()).to_error (badarg (1, "table")) + expect (f ({})).to_error (badarg (2, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) - expect (f ({}, false)).to_error (msg (2, "List", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_error (badarg (2, "List", "boolean")) - it diagnoses too many arguments: - expect (f ({}, l, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f ({}, l, false)).to_error (badarg (3)) - context when called as a list object method: - it returns a list object: | @@ -1120,19 +1092,16 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6, 7} - fname = "sub" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "sub") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) + expect (f ()).to_error (badarg (1, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) - expect (f (l, false)).to_error (msg (2, "int or nil", "boolean")) - expect (f (l, 1, false)).to_error (msg (3, "int or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "List", "boolean")) + expect (f (l, false)).to_error (badarg (2, "int or nil", "boolean")) + expect (f (l, 1, false)).to_error (badarg (3, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (f (l, 1, 2, false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f (l, 1, 2, false)).to_error (badarg (4)) - context when called as a list object method: - it returns a list object: | @@ -1157,17 +1126,14 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6, 7} - fname = "tail" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "tail") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "List")) + expect (f ()).to_error (badarg (1, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "List", "boolean")) + expect (f (false)).to_error (badarg (1, "List", "boolean")) - it diagnoses too many arguments: - expect (f (l, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (l, false)).to_error (badarg (2)) - context when called as a list object method: - it returns a list object: | @@ -1189,7 +1155,6 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) f = M[fname] - it writes a deprecation warning to standard error on first call: | @@ -1246,7 +1211,6 @@ specify std.list: - context as a module function: - before: - msg = bind (badarg, {this_module, fname}) f = M[fname] - it writes a deprecation warning to standard error on first call: | diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 8e6bd61..6df24ef 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -31,17 +31,15 @@ specify std.math: - describe floor: - before: - fname = "floor" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "floor") - it diagnoses missing arguments: | - expect (f ()).to_error (msg (1, "number")) + expect (f ()).to_error (badarg (1, "number")) - it diagnoses wrong argument types: | - expect (f (1.2, false)).to_error (msg (2, "int or nil", "boolean")) - expect (f (1.2, 3.4)).to_error (msg (2, "int or nil", "number")) + expect (f (1.2, false)).to_error (badarg (2, "int or nil", "boolean")) + expect (f (1.2, 3.4)).to_error (badarg (2, "int or nil", "number")) - it diagnoses too many arguments: - expect (f (1, 2, 3)).to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (1, 2, 3)).to_error (badarg (3)) - it rounds to the nearest smaller integer: expect (f (1.2)).to_be (1) @@ -63,14 +61,13 @@ specify std.math: t = { math = {}, } - fname = "monkey_patch" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + + f, badarg = init (M, "monkey_patch") - it diagnoses wrong argument types: | - expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (t, false)).to_error (badarg (2)) - it installs math.floor function: f (t) @@ -79,19 +76,17 @@ specify std.math: - describe round: - before: - fname = "round" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "round") - it diagnoses missing arguments: | - expect (f ()).to_error (msg (1, "number")) + expect (f ()).to_error (badarg (1, "number")) - it diagnoses wrong argument types: | expect (f (1.2, false)). - to_error (msg (2, "int or nil", "boolean")) + to_error (badarg (2, "int or nil", "boolean")) expect (f (1.2, 3.4)). - to_error (msg (2, "int or nil", "number")) + to_error (badarg (2, "int or nil", "number")) - it diagnoses too many arguments: - expect (f (1, 2, 3)).to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f (1, 2, 3)).to_error (badarg (3)) - it rounds to the nearest integer: expect (f (1.2)).to_be (1) diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index 817f2d6..f57b69a 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -41,22 +41,19 @@ specify std.package: - before: | path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) - fname = "find" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "find") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - expect (f (path)).to_error (msg (2, "string")) + expect (f ()).to_error (badarg (1, "string")) + expect (f (path)).to_error (badarg (2, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f (path, false)).to_error (msg (2, "string", "boolean")) - expect (f (path, "foo", false)).to_error (msg (3, "int or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (path, false)).to_error (badarg (2, "string", "boolean")) + expect (f (path, "foo", false)).to_error (badarg (3, "int or nil", "boolean")) expect (f (path, "foo", 1, 2)). - to_error (msg (4, "boolean, :plain or nil", "number")) + to_error (badarg (4, "boolean, :plain or nil", "number")) - it diagnoses too many arguments: - expect (f (path, "foo", 1, ":plain", false)). - to_error (toomanyarg (this_module, fname, 4, 5)) + expect (f (path, "foo", 1, ":plain", false)).to_error (badarg (5)) - it returns nil for unmatched element: expect (f (path, "unmatchable")).to_be (nil) @@ -76,20 +73,17 @@ specify std.package: - describe insert: - before: - fname = "insert" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "insert") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - expect (f (path)).to_error (msg (2, "int or string")) + expect (f ()).to_error (badarg (1, "string")) + expect (f (path)).to_error (badarg (2, "int or string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f (path, false)).to_error (msg (2, "int or string", "boolean")) - expect (f (path, 1, false)).to_error (msg (3, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (path, false)).to_error (badarg (2, "int or string", "boolean")) + expect (f (path, 1, false)).to_error (badarg (3, "string", "boolean")) - it diagnoses too many arguments: - expect (f (path, 1, "string", false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f (path, 1, "string", false)).to_error (badarg (4)) - it appends by default: expect (f (path, "new")). @@ -114,16 +108,14 @@ specify std.package: - before: | expected = require "std.string".split (path, M.pathsep) - fname = "mappath" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "mappath") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - expect (f ("")).to_error (msg (2, "function")) + expect (f ()).to_error (badarg (1, "string")) + expect (f ("")).to_error (badarg (2, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("", false)).to_error (msg (2, "function", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("", false)).to_error (badarg (2, "function", "boolean")) - it calls a function with each path element: t = {} @@ -141,15 +133,13 @@ specify std.package: - describe normalize: - before: - fname = "normalize" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "normalize") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("", false)).to_error (msg (2, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("", false)).to_error (badarg (2, "string", "boolean")) - context with a single element: - it strips redundant . directories: @@ -195,18 +185,15 @@ specify std.package: - describe remove: - before: - fname = "remove" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "remove") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("", false)).to_error (msg (2, "int or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("", false)).to_error (badarg (2, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", 2, false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f ("string", 2, false)).to_error (badarg (3)) - it removes the last item by default: expect (f (path)).to_be (M.normalize ("begin", "middle")) diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua.in index cfc761b..bac5d2a 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua.in @@ -94,31 +94,35 @@ end --- Return a formatted bad argument string. +-- @tparam table M module table -- @string fname base-name of the erroring function -- @int i argument number -- @string want expected argument type -- @string[opt="no value"] got actual argument type -- @usage --- expect (f ()).to_error (badarg (module, name, 1, "function")) -function badarg (mname, fname, i, want, got) - local name = fname - if mname then name = mname .. "." .. name end +-- expect (f ()).to_error (badarg (M, name, 1, "function")) +local function badarg (M, fname, i, want, got) + fname = tostring (M[1]) .. (rawget (M, 2) and ":" or ".") .. fname + if want == nil then i, want = i - 1, i end + + if got == nil and type (want) == "number" then + return string.format ("too many arguments to '%s' (no more than %d expected, got %d)", + fname, i, want) + end return string.format ("bad argument #%d to '%s' (%s expected, got %s)", - i, name, want, got or "no value") + i, fname, want, got or "no value") end ---- Return a formatted too many arguments string. --- @string fname base-name of the erroring function --- @int want maximum expected number of arguments --- @int got actual number of arguments --- @usage --- expect (f (1, 2)).to_error (toomanyarg (module, name, 1, 2)) -function toomanyarg (mname, fname, want, got) - local name = fname - if mname then name = mname .. "." .. name end - return string.format ("too many arguments to '%s' (no more than %d expected, got %d)", - name, want, got) +--- Initialise custom function and argument error handlers. +-- @tparam table M module table +-- @string fname function name to bind +-- @treturn string *fname* +-- @treturn function `M[fname]` if any, otherwise `nil` +-- @treturn function badarg with *M* and *fname* prebound +-- @treturn function toomanyarg with *M* and *fname* prebound +function init (M, fname) + return M[fname], bind (badarg, {M, fname}) end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index bc86c02..e597cb9 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -51,12 +51,10 @@ specify std: - describe assert: - before: - fname = "assert" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "assert") - it diagnoses wrong argument types: - expect (f (false, false)).to_error (msg (2, "string or nil", "boolean")) + expect (f (false, false)).to_error (badarg (2, "string or nil", "boolean")) - context when it does not trigger: - it has a truthy initial argument: @@ -94,16 +92,14 @@ specify std: table = {}, } - fname = "barrel" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "barrel") f (t) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it installs std.io monkey patches: expect (io_mt.readlines).to_be (M.io.readlines) @@ -169,16 +165,14 @@ specify std: - describe elems: - before: - fname = "elems" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "elems") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it is an iterator over table values: t = {} @@ -199,16 +193,14 @@ specify std: - describe eval: - before: - fname = "eval" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "eval") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("1", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("1", false)).to_error (badarg (2)) - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable "=" code. @@ -219,19 +211,16 @@ specify std: - describe getmetamethod: - before: - fname = "getmetamethod" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "getmetamethod") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "object or table")) - expect (f ({})).to_error (msg (2, "string")) + expect (f ()).to_error (badarg (1, "object or table")) + expect (f ({})).to_error (badarg (2, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "object or table", "boolean")) - expect (f ({}, false)).to_error (msg (2, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "object or table", "boolean")) + expect (f ({}, false)).to_error (badarg (2, "string", "boolean")) - it diagnoses too many arguments: - expect (f ({}, "foo", false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f ({}, "foo", false)).to_error (badarg (3)) - context with a table: - before: @@ -262,16 +251,14 @@ specify std: - describe ielems: - before: - fname = "ielems" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "ielems") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it is an iterator over integer-keyed table values: t = {} @@ -297,16 +284,14 @@ specify std: - describe ipairs: - before: - fname = "ipairs" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "ipairs") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it is an iterator over integer-keyed table values: t = {} @@ -332,16 +317,14 @@ specify std: - describe ireverse: - before: - fname = "ireverse" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "ireverse") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it returns a new list: t = {1, 2, 5} @@ -369,16 +352,14 @@ specify std: table = {}, } - fname = "monkey_patch" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "monkey_patch") f (t) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it installs std module functions: for _, v in ipairs (exported_apis) do @@ -390,16 +371,14 @@ specify std: - describe pairs: - before: - fname = "pairs" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "pairs") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it is an iterator over all table values: t = {} @@ -420,22 +399,20 @@ specify std: - describe require: - before: - fname = "require" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "require") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("module", false)).to_error (msg (2, "string or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("module", false)).to_error (badarg (2, "string or nil", "boolean")) expect (f ("module", "min", false)). - to_error (msg (3, "string or nil", "boolean")) + to_error (badarg (3, "string or nil", "boolean")) expect (f ("module", "min", "too_big", false)). - to_error (msg (4, "string or nil", "boolean")) + to_error (badarg (4, "string or nil", "boolean")) - it diagnoses too many arguments: expect (f ("module", "min", "too_big", "pattern", false)). - to_error (toomanyarg (this_module, fname, 4, 5)) + to_error (badarg (5)) - it diagnoses non-existent module: expect (f ("module-not-exists", "", "")).to_error "module-not-exists" @@ -502,17 +479,14 @@ specify std: - describe ripairs: - before: - fname = "ripairs" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "ripairs") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ({}, false)).to_error (badarg (2)) - it returns a function, the table and a number: fn, t, i = f {1, 2, 3} @@ -540,12 +514,10 @@ specify std: - describe tostring: - before: - fname = "tostring" - f = M[fname] + f, badarg = init (M, "tostring") - it diagnoses too many arguments: - expect (f (true, false)). - to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (true, false)).to_error (badarg (2)) - it renders primitives exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 69f402c..f863117 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -59,9 +59,7 @@ specify std.string: - describe assert: - before: - fname = "assert" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f = M.assert - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {"std.string"}) @@ -97,16 +95,14 @@ specify std.string: - describe caps: - before: - fname = "caps" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "caps") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("string", false)).to_error (badarg (2)) - it capitalises words of a string: target = "A String \n\n" @@ -125,16 +121,14 @@ specify std.string: - before: target = "a string \n" - fname = "chomp" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "chomp") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("string", false)).to_error (badarg (2)) - it removes a single trailing newline from a string: expect (f (subject)).to_be (target) @@ -157,16 +151,14 @@ specify std.string: magic[meta:sub (i, i)] = true end - fname = "escape_pattern" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "escape_pattern") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("string", false)).to_error (badarg (2)) - context with each printable ASCII char: - before: @@ -189,16 +181,14 @@ specify std.string: - describe escape_shell: - before: - fname = "escape_shell" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "escape_shell") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f ("string", false)).to_error (badarg (2)) - context with each printable ASCII char: - before: @@ -226,23 +216,21 @@ specify std.string: - before: subject = "abcd" - fname = "finds" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "finds") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - expect (f ("string")).to_error (msg (2, "string")) + expect (f ()).to_error (badarg (1, "string")) + expect (f ("string")).to_error (badarg (2, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("string", false)).to_error (msg (2, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_error (badarg (2, "string", "boolean")) expect (f ("string", "pattern", false)). - to_error (msg (3, "int or nil", "boolean")) + to_error (badarg (3, "int or nil", "boolean")) expect (f ("string", "pattern", nil, "plain")). - to_error (msg (4, "boolean, :plain or nil", "string")) + to_error (badarg (4, "boolean, :plain or nil", "string")) - it diagnoses too many arguments: expect (f ("string", "pattern", nil, false, function () end)). - to_error (toomanyarg (this_module, fname, 4, 5)) + to_error (badarg (5)) - context given a complex nested list: - before: @@ -270,14 +258,12 @@ specify std.string: - before: subject = "string=%s, number=%d" - fname = "format" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "format") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) - it returns a single argument without attempting formatting: expect (f (subject)).to_be (subject) @@ -293,19 +279,16 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - fname = "ltrim" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "ltrim") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) expect (f ("string", false)). - to_error (msg (2, "string or nil", "boolean")) + to_error (badarg (2, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", "pattern", false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f ("string", "pattern", false)).to_error (badarg (3)) - it removes whitespace from the start of a string: target = "a short string \t\r\n " @@ -324,17 +307,15 @@ specify std.string: - describe monkey_patch: - before: - fname = "monkey_patch" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "monkey_patch") t = {} f (t) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (t, false)).to_error (badarg (2)) - it installs concat metamethod: # FIXME: string metatable monkey-patches leak out! @@ -348,16 +329,14 @@ specify std.string: - describe numbertosi: - before: - fname = "numbertosi" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "numbertosi") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "number or string")) + expect (f ()).to_error (badarg (1, "number or string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "number or string", "boolean")) + expect (f (false)).to_error (badarg (1, "number or string", "boolean")) - it diagnoses too many arguments: - expect (f (1, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (1, false)).to_error (badarg (2)) - it returns a number using SI suffixes: target = {"1e-9", "1y", "1z", "1a", "1f", "1p", "1n", "1mu", "1m", "1", @@ -374,16 +353,14 @@ specify std.string: - describe ordinal_suffix: - before: - fname = "ordinal_suffix" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "ordinal_suffix") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "int or string")) + expect (f ()).to_error (badarg (1, "int or string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "int or string", "boolean")) + expect (f (false)).to_error (badarg (1, "int or string", "boolean")) - it diagnoses too many arguments: - expect (f (1, false)).to_error (toomanyarg (this_module, fname, 1, 2)) + expect (f (1, false)).to_error (badarg (2)) - it returns the English suffix for a number: subject, target = {}, {} @@ -406,21 +383,18 @@ specify std.string: - before: width = 20 - fname = "pad" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "pad") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - expect (f ("string")).to_error (msg (2, "int")) + expect (f ()).to_error (badarg (1, "string")) + expect (f ("string")).to_error (badarg (2, "int")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("string", false)).to_error (msg (2, "int", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_error (badarg (2, "int", "boolean")) expect (f ("string", 42, false)). - to_error (msg (3, "string or nil", "boolean")) + to_error (badarg (3, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", 42, "\t", false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f ("string", 42, "\t", false)).to_error (badarg (4)) - context when string is shorter than given width: - before: @@ -485,17 +459,14 @@ specify std.string: - describe prettytostring: - before: - fname = "prettytostring" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "prettytostring") - it diagnoses wrong argument types: - expect (f (true, false)).to_error (msg (2, "string or nil", "boolean")) + expect (f (true, false)).to_error (badarg (2, "string or nil", "boolean")) expect (f (true, "indent", false)). - to_error (msg (3, "string or nil", "boolean")) + to_error (badarg (3, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f (true, "indent", "spacing", false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f (true, "indent", "spacing", false)).to_error (badarg (4)) - it renders nil exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) @@ -541,28 +512,25 @@ specify std.string: t = {1, {{2, 3}, 4, {5}}} a = require "std.vector" {"a", "b", {{"c"},"d"},"e"} - fname = "render" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "render") - it diagnoses missing arguments: - expect (f (true)).to_error (msg (2, "function")) - expect (f (true, f)).to_error (msg (3, "function")) - expect (f (true, f, f)).to_error (msg (4, "function")) - expect (f (true, f, f, f)).to_error (msg (5, "function")) - expect (f (true, f, f, f, f)).to_error (msg (6, "function")) + expect (f (true)).to_error (badarg (2, "function")) + expect (f (true, f)).to_error (badarg (3, "function")) + expect (f (true, f, f)).to_error (badarg (4, "function")) + expect (f (true, f, f, f)).to_error (badarg (5, "function")) + expect (f (true, f, f, f, f)).to_error (badarg (6, "function")) - it diagnoses wrong argument types: - expect (f (true, false)).to_error (msg (2, "function", "boolean")) - expect (f (true, f, false)).to_error (msg (3, "function", "boolean")) - expect (f (true, f, f, false)).to_error (msg (4, "function", "boolean")) - expect (f (true, f, f, f, false)).to_error (msg (5, "function", "boolean")) + expect (f (true, false)).to_error (badarg (2, "function", "boolean")) + expect (f (true, f, false)).to_error (badarg (3, "function", "boolean")) + expect (f (true, f, f, false)).to_error (badarg (4, "function", "boolean")) + expect (f (true, f, f, f, false)).to_error (badarg (5, "function", "boolean")) expect (f (true, f, f, f, f, false)). - to_error (msg (6, "function", "boolean")) + to_error (badarg (6, "function", "boolean")) expect (f (true, f, f, f, f, f, false)). - to_error (msg (7, "table or nil", "boolean")) + to_error (badarg (7, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (true, f, f, f, f, f, {}, false)). - to_error (toomanyarg (this_module, fname, 7, 8)) + expect (f (true, f, f, f, f, f, {}, false)).to_error (badarg (8)) - it converts a primitive to a representative string: expect (r (nil)).to_be "nil" @@ -585,8 +553,7 @@ specify std.string: - describe require_version: - before: - fname = "require_version" - f = M[fname] + f = M.require_version - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {"std.string"}) @@ -647,18 +614,15 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - fname = "rtrim" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "rtrim") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("string", false)).to_error (msg (2, "string or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_error (badarg (2, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", "pattern", false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f ("string", "pattern", false)).to_error (badarg (3)) - it removes whitespace from the end of a string: target = " \t\r\n a short string" @@ -680,19 +644,16 @@ specify std.string: target = { "first", "the second one", "final entry" } subject = table.concat (target, ", ") - fname = "split" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "split") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) expect (f ("string", false)). - to_error (msg (2, "string or nil", "boolean")) + to_error (badarg (2, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", "pattern", false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f ("string", "pattern", false)).to_error (badarg (3)) - it falls back to "%s+" when no pattern is given: expect (f (subject)). @@ -732,23 +693,20 @@ specify std.string: - before: subject = "abc" - fname = "tfind" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "tfind") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) - expect (f ("string")).to_error (msg (2, "string")) + expect (f ()).to_error (badarg (1, "string")) + expect (f ("string")).to_error (badarg (2, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("string", false)).to_error (msg (2, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_error (badarg (2, "string", "boolean")) expect (f ("string", "pattern", false)). - to_error (msg (3, "int or nil", "boolean")) + to_error (badarg (3, "int or nil", "boolean")) expect (f ("string", "pattern", 1, "plain")). - to_error (msg (4, "boolean, :plain or nil", "string")) + to_error (badarg (4, "boolean, :plain or nil", "string")) - it diagnoses too many arguments: - expect (f ("string", "pattern", 1, true, false)). - to_error (toomanyarg (this_module, fname, 4, 5)) + expect (f ("string", "pattern", 1, true, false)).to_error (badarg (5)) - it creates a list of pattern captures: target = { 1, 3, { "a", "b", "c" } } @@ -773,8 +731,7 @@ specify std.string: - describe tostring: - before: - fname = "tostring" - f = M[fname] + f = M.tostring - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {"std.string"}) @@ -811,19 +768,16 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - fname = "trim" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "trim") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) expect (f ("string", false)). - to_error (msg (2, "string or nil", "boolean")) + to_error (badarg (2, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", "pattern", false)). - to_error (toomanyarg (this_module, fname, 2, 3)) + expect (f ("string", "pattern", false)).to_error (badarg (3)) - it removes whitespace from each end of a string: target = "a short string" @@ -848,22 +802,19 @@ specify std.string: "er the MIT license (the same license as Lua itself). There" .. " is no warranty." - fname = "wrap" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "wrap") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "string")) + expect (f ()).to_error (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "string", "boolean")) - expect (f ("string", false)).to_error (msg (2, "int or nil", "boolean")) + expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_error (badarg (2, "int or nil", "boolean")) expect (f ("string", 72, false)). - to_error (msg (3, "int or nil", "boolean")) + to_error (badarg (3, "int or nil", "boolean")) expect (f ("string", 72, 4, false)). - to_error (msg (4, "int or nil", "boolean")) + to_error (badarg (4, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", 72, 4, 8, false)). - to_error (toomanyarg (this_module, fname, 4, 5)) + expect (f ("string", 72, 4, 8, false)).to_error (badarg (5)) - it inserts newlines to wrap a string: target = "This is a collection of Lua libraries for Lua 5.1 a" .. diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index bff934d..2009236 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -35,21 +35,18 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) - fname = "clone" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "clone") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) expect (f ({}, "nometa")). - to_error (msg (2, "table, boolean, :nometa or nil", "string")) + to_error (badarg (2, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, "nometa")). - to_error (msg (3, "boolean, :nometa or nil", "string")) + to_error (badarg (3, "boolean, :nometa or nil", "string")) - it diagnoses too many arguments: - expect (f ({}, {}, nil, false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f ({}, {}, nil, false)).to_error (badarg (4)) - it does not just return the subject: expect (f (subject)).not_to_be (subject) @@ -120,21 +117,18 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) - fname = "clone_select" - msg = bind (badarg, {this_module, fname}) - f = M[fname] + f, badarg = init (M, "clone_select") - it diagnoses missing arguments: - expect (f ()).to_error (msg (1, "table")) + expect (f ()).to_error (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (msg (1, "table", "boolean")) + expect (f (false)).to_error (badarg (1, "table", "boolean")) expect (f ({}, "nometa")). - to_error (msg (2, "table, boolean, :nometa or nil", "string")) + to_error (badarg (2, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, "nometa")). - to_error (msg (3, "boolean, :nometa or nil", "string")) + to_error (badarg (3, "boolean, :nometa or nil", "string")) - it diagnoses too many arguments: - expect (f ({}, {}, nil, false)). - to_error (toomanyarg (this_module, fname, 3, 4)) + expect (f ({}, {}, nil, false)).to_error (badarg (4)) - it does not just return the subject: expect (f (subject)).not_to_be (subject) @@ -163,13 +157,15 @@ specify std.table: - describe empty: - before: - f = M.empty - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.empty' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.empty' (table expected, got boolean)" + f, badarg = init (M, "empty") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (badarg (2)) + - it returns true for an empty table: expect (f {}).to_be (true) expect (f {nil}).to_be (true) @@ -182,13 +178,16 @@ specify std.table: - describe invert: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } - f = M.invert - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.invert' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.invert' (table expected, got boolean)" + + f, badarg = init (M, "invert") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (badarg (2)) + - it returns a new table: expect (f (subject)).not_to_be (subject) - it inverts keys and values in the returned table: @@ -203,13 +202,16 @@ specify std.table: - describe keys: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } - f = M.keys - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.keys' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.keys' (table expected, got boolean)" + + f, badarg = init (M, "keys") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (badarg (2)) + - it returns an empty list when subject is empty: expect (f {}).to_equal {} - it makes a list of table keys: @@ -229,25 +231,28 @@ specify std.table: t1 = { k1 = {"v1"}, k2 = "if", k3 = {"?"} } t2 = { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = t1.k1 } t1mt = setmetatable (M.clone (t1), {"meta!"}) - f = M.merge - target = {} for k, v in pairs (t1) do target[k] = v end for k, v in pairs (t2) do target[k] = v end - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.merge' (table expected, got no value)" - expect (f ({})). - to_error "bad argument #2 to 'std.table.merge' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.merge' (table expected, got boolean)" - expect (f ({}, false)). - to_error "bad argument #2 to 'std.table.merge' (table expected, got boolean)" + + f, badarg = init (M, "merge") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "table")) + expect (f ({})).to_error (badarg (2, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_error (badarg (2, "table", "boolean")) expect (f ({}, {}, "nometa")). - to_error "bad argument #3 to 'std.table.merge' (table, boolean, :nometa or nil expected, got string)" + to_error (badarg (3, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, {}, "nometa")). - to_error "bad argument #4 to 'std.table.merge' (boolean, :nometa or nil expected, got string)" + to_error (badarg (4, "boolean, :nometa or nil", "string")) + - it diagnoses too many arguments: | + expect (f ({}, {}, {}, ":nometa", false)). + to_error (badarg (5)) + pending "issue #78" + expect (f ({}, {}, ":nometa", false)).to_error (badarg (4)) + - it does not create a whole new table: expect (f (t1, t2)).to_be (t1) - it does not change t1 when t2 is empty: @@ -293,25 +298,28 @@ specify std.table: t1mt = setmetatable (M.clone (t1), {"meta!"}) t2 = { ["if"] = true, [tablekey] = false, _ = "underscore", k3 = t1.k1 } t2keys = { "if", tablekey, "_", "k3" } - f = M.merge_select - target = {} for k, v in pairs (t1) do target[k] = v end for k, v in pairs (t2) do target[k] = v end - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.merge_select' (table expected, got no value)" - expect (f ({})). - to_error "bad argument #2 to 'std.table.merge_select' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.merge_select' (table expected, got boolean)" - expect (f ({}, false)). - to_error "bad argument #2 to 'std.table.merge_select' (table expected, got boolean)" + + f, badarg = init (M, "merge_select") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "table")) + expect (f ({})).to_error (badarg (2, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_error (badarg (2, "table", "boolean")) expect (f ({}, {}, "nometa")). - to_error "bad argument #3 to 'std.table.merge_select' (table, boolean, :nometa or nil expected, got string)" + to_error (badarg (3, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, {}, "nometa")). - to_error "bad argument #4 to 'std.table.merge_select' (boolean, :nometa or nil expected, got string)" + to_error (badarg (4, "boolean, :nometa or nil", "string")) + - it diagnoses too many arguments: | + expect (f ({}, {}, {}, ":nometa", false)). + to_error (badarg (5)) + pending "issue #78" + expect (f ({}, {}, ":nometa", false)).to_error (badarg (4)) + - it does not create a whole new table: expect (f (t1, t2)).to_be (t1) - it does not change t1 when t2 is empty: @@ -355,8 +363,7 @@ specify std.table: _method = objmethod, } - fname = "metamethod" - f = M[fname] + f = M.metamethod - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {{}, subject}) @@ -377,25 +384,30 @@ specify std.table: - describe monkey_patch: - before: - f = M.monkey_patch - t = { + f, badarg = init (M, "monkey_patch") + + t = { table = {}, } f (t) - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.monkey_patch' (table or nil expected, got boolean)" + + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (badarg (2)) + - it installs table.sort function: expect (t.table.sort).to_be (M.sort) - describe new: - before: - f = M.new + f, badarg = init (M, "new") - - it diagnoses wrong argument types: | - expect (f (nil, false)). - to_error "bad argument #2 to 'std.table.new' (table or nil expected, got boolean)" + - it diagnoses wrong argument types: + expect (f (nil, false)).to_error (badarg (2, "table or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, {}, false)).to_error (badarg (3)) - context when not setting a default: - before: default = nil @@ -443,8 +455,7 @@ specify std.table: - describe ripairs: - before: - fname = "ripairs" - f = M[fname] + f = M.ripairs - it writes a deprecation warning to standard error on first call: | _, err = capture (f, {{}, subject}) @@ -472,13 +483,16 @@ specify std.table: - before: | -- - 1 - --------- 2 ---------- -- 3 -- subject = { "one", { { "two" }, "three" }, four = 5 } - f = M.size - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.size' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.size' (table expected, got boolean)" + + f, badarg = init (M, "size") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (badarg (2)) + - it counts the number of keys in a table: expect (f (subject)).to_be (3) - it counts no keys in an empty table: @@ -490,15 +504,17 @@ specify std.table: 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 - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.sort' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.sort' (table expected, got boolean)" - expect (f ({}, false)). - to_error "bad argument #2 to 'std.table.sort' (function or nil expected, got boolean)" + + f, badarg = init (M, "sort") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_error (badarg (2, "function or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, f, false)).to_error (badarg (3)) + - it sorts elements in place: f (subject, cmp) expect (subject).to_equal (target) @@ -511,13 +527,16 @@ specify std.table: t = {"one", "two", "five"} mt = { _type = "MockObject", __totable = function (self) return self.content end } - f = M.totable - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.totable' (object, table or string expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.totable' (object, table or string expected, got boolean)" + + f, badarg = init (M, "totable") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "object, table or string")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "object, table or string", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (badarg (2)) + - it calls object's __totable metamethod: object = setmetatable ({content = t}, mt) expect (f (object)).to_be (t) @@ -530,13 +549,16 @@ specify std.table: - describe values: - before: subject = { k1 = {1}, k2 = {2}, k3 = {3} } - f = M.values - - it diagnoses missing arguments: | - expect (f ()). - to_error "bad argument #1 to 'std.table.values' (table expected, got no value)" - - it diagnoses wrong argument types: | - expect (f (false)). - to_error "bad argument #1 to 'std.table.values' (table expected, got boolean)" + + f, badarg = init (M, "values") + + - it diagnoses missing arguments: + expect (f ()).to_error (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_error (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_error (badarg (2)) + - it returns an empty list when subject is empty: expect (f {}).to_equal {} - it makes a list of table values: From 1632f3531772fc822d85efaa8ef7bb400930771c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 17 Aug 2014 02:46:32 +0100 Subject: [PATCH 366/703] refactor: use to_raise matcher alias consistently. Slingshot sanity checks flag strings with an initial capital following 'error' as bad style. Rather than switch off that check, use a matcher alias. * build-aux/sanity-cfg.mk (sc_error_message_uppercase): Remove spec-files from regexp. specs/base_spec.yaml, specs/container_spec.yaml, specs/debug_spec.yaml, specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/math_spec.yaml, specs/optparse_spec.yaml, specs/package_spec.yaml, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml, specs/tree_spec.yaml, specs/vector_spec.yaml: s/to_error/to_raise/ s/not_to_raise ()/not_to_raise "any error"/ Signed-off-by: Gary V. Vaughan --- build-aux/sanity-cfg.mk | 2 +- specs/base_spec.yaml | 80 ++++---- specs/container_spec.yaml | 6 +- specs/debug_spec.yaml | 386 ++++++++++++++++++------------------- specs/functional_spec.yaml | 126 ++++++------ specs/io_spec.yaml | 66 +++---- specs/list_spec.yaml | 108 +++++------ specs/math_spec.yaml | 20 +- specs/optparse_spec.yaml | 2 +- specs/package_spec.yaml | 48 ++--- specs/std_spec.yaml | 124 ++++++------ specs/string_spec.yaml | 212 ++++++++++---------- specs/table_spec.yaml | 108 +++++------ specs/tree_spec.yaml | 24 +-- specs/vector_spec.yaml | 78 ++++---- 15 files changed, 695 insertions(+), 695 deletions(-) diff --git a/build-aux/sanity-cfg.mk b/build-aux/sanity-cfg.mk index 069aad4..6bbf697 100644 --- a/build-aux/sanity-cfg.mk +++ b/build-aux/sanity-cfg.mk @@ -1,3 +1,3 @@ -exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua|specs/(base|debug|list)_spec.yaml$$ +exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua$$ EXTRA_DIST += build-aux/sanity-cfg.mk diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 6df9619..1fc9c85 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -20,22 +20,22 @@ specify std.base: f, badarg = init (M, "DEPRECATED") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) - expect (f "version").to_error (badarg (2, "string")) - expect (f ("version", "name")).to_error (badarg (3, "string or function")) - expect (f ("version", "name", "extramsg")).to_error (badarg (4, "function")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f "version").to_raise (badarg (2, "string")) + expect (f ("version", "name")).to_raise (badarg (3, "string or function")) + expect (f ("version", "name", "extramsg")).to_raise (badarg (4, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("version", false)).to_error (badarg (2, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("version", false)).to_raise (badarg (2, "string", "boolean")) expect (f ("version", "name", false)). - to_error (badarg (3, "string or function", "boolean")) + to_raise (badarg (3, "string or function", "boolean")) expect (f ("version", "name", "extramsg", false)). - to_error (badarg (4, "function", "boolean")) + to_raise (badarg (4, "function", "boolean")) - it diagnoses too many arguments: | expect (f ("version", "name", "extramsg", nop, false)). - to_error (badarg (5)) + to_raise (badarg (5)) pending "issue #76" - expect (f ("version", "name", nop, false)).to_error (badarg (4)) + expect (f ("version", "name", nop, false)).to_raise (badarg (4)) - it returns a function: expect (type (f ("0", "deprecated", nop))).to_be "function" @@ -110,15 +110,15 @@ specify std.base: f, badarg = init (M, "export") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) - expect (f ({})).to_error (badarg (2, "string")) - expect (f ({}, "")).to_error (badarg (3, "function")) + expect (f ()).to_raise (badarg (1, "table")) + expect (f ({})).to_raise (badarg (2, "string")) + expect (f ({}, "")).to_raise (badarg (3, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_error (badarg (2, "string", "boolean")) - expect (f ({}, "", false)).to_error (badarg (3, "function", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_raise (badarg (2, "string", "boolean")) + expect (f ({}, "", false)).to_raise (badarg (3, "function", "boolean")) - it diagnoses too many arguments: - expect (f ({}, "", function () end, false)).to_error (badarg (4)) + expect (f ({}, "", function () end, false)).to_raise (badarg (4)) - it diagnoses missing module name element at callsite: | expect (luaproc (mkstack ("", ""))). to_contain_error (":3: " .. badarg (1, "module name at index 1")) @@ -153,7 +153,7 @@ specify std.base: f (M, "chk_function ()", mkmagic) chk = M.chk_function - it diagnoses too many arguments: - expect (chk (false)).to_error (badarg (1)) + expect (chk (false)).to_raise (badarg (1)) - it accepts correct argument types: expect (chk ()).to_be "MAGIC" @@ -164,11 +164,11 @@ specify std.base: f (M, "chk_function (#table)", mkmagic) chk = M.chk_function - it diagnoses missing arguments: - expect (chk ()).to_error (badarg (1, "non-empty table")) + expect (chk ()).to_raise (badarg (1, "non-empty table")) - it diagnoses wrong argument types: - expect (chk {}).to_error (badarg (1, "non-empty table", "empty table")) + expect (chk {}).to_raise (badarg (1, "non-empty table", "empty table")) - it diagnoses too many arguments: - expect (chk ({1}, 2, nop, "", false)).to_error (badarg (1, 5)) + expect (chk ({1}, 2, nop, "", false)).to_raise (badarg (1, 5)) - it accepts correct argument types: expect (chk ({1})).to_be "MAGIC" @@ -179,13 +179,13 @@ specify std.base: f (M, "chk_function (table, function)", mkmagic) chk = M.chk_function - it diagnoses missing arguments: - expect (chk ()).to_error (badarg (1, "table")) - expect (chk ({})).to_error (badarg (2, "function")) + expect (chk ()).to_raise (badarg (1, "table")) + expect (chk ({})).to_raise (badarg (2, "function")) - it diagnoses wrong argument types: - expect (chk (false)).to_error (badarg (1, "table", "boolean")) - expect (chk ({}, false)).to_error (badarg (2, "function", "boolean")) + expect (chk (false)).to_raise (badarg (1, "table", "boolean")) + expect (chk ({}, false)).to_raise (badarg (2, "function", "boolean")) - it diagnoses too many arguments: - expect (chk ({}, nop, false)).to_error (badarg (3)) + expect (chk ({}, nop, false)).to_raise (badarg (3)) - it accepts correct argument types: expect (chk ({}, nop)).to_be "MAGIC" @@ -196,9 +196,9 @@ specify std.base: f (M, "chk_function ([int])", mkmagic) chk = M.chk_function - it diagnoses wrong argument types: - expect (chk (false)).to_error (badarg (1, "int or nil", "boolean")) + expect (chk (false)).to_raise (badarg (1, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (chk (1, nop)).to_error (badarg (2)) + expect (chk (1, nop)).to_raise (badarg (2)) - it accepts correct argument types: expect (chk ()).to_be "MAGIC" expect (chk (1)).to_be "MAGIC" @@ -210,12 +210,12 @@ specify std.base: f (M, "chk_function ([int], string)", mkmagic) chk = M.chk_function - it diagnoses missing arguments: - expect (chk ()).to_error (badarg (1, "int or string")) - expect (chk (1)).to_error (badarg (2, "string")) + expect (chk ()).to_raise (badarg (1, "int or string")) + expect (chk (1)).to_raise (badarg (2, "string")) - it diagnoses wrong argument types: - expect (chk (false)).to_error (badarg (1, "int or string", "boolean")) + expect (chk (false)).to_raise (badarg (1, "int or string", "boolean")) - it diagnoses too many arguments: - expect (chk (1, "two", nop)).to_error (badarg (3)) + expect (chk (1, "two", nop)).to_raise (badarg (3)) - it accepts correct argument types: expect (chk ("two")).to_be "MAGIC" expect (chk (1, "two")).to_be "MAGIC" @@ -229,16 +229,16 @@ specify std.base: Bad = Object { _type = "Bad" } obj = Object { chk = M.chk_method } - it diagnoses missing arguments: - expect (obj.chk ()).to_error (badarg (0, "Object")) - expect (obj:chk ()).to_error (badarg (1, "string")) + expect (obj.chk ()).to_raise (badarg (0, "Object")) + expect (obj:chk ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (obj.chk ({})).to_error (badarg (0, "Object", "empty table")) - expect (obj.chk (Bad)).to_error (badarg (0, "Object", "Bad")) - expect (obj:chk ({})).to_error (badarg (1, "string", "empty table")) - expect (obj:chk (obj)).to_error (badarg (1, "string", "Object")) + expect (obj.chk ({})).to_raise (badarg (0, "Object", "empty table")) + expect (obj.chk (Bad)).to_raise (badarg (0, "Object", "Bad")) + expect (obj:chk ({})).to_raise (badarg (1, "string", "empty table")) + expect (obj:chk (obj)).to_raise (badarg (1, "string", "Object")) - it diagnoses too many arguments: - expect (obj.chk (obj, "str", false)).to_error (badarg (2)) - expect (obj:chk ("str", false)).to_error (badarg (2)) + expect (obj.chk (obj, "str", false)).to_raise (badarg (2)) + expect (obj:chk ("str", false)).to_raise (badarg (2)) - it accepts correct argument types: expect (obj.chk (obj, "str")).to_be "str" expect (obj.chk (Object, "str")).to_be "str" diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index dfc90c4..05e20d6 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -21,9 +21,9 @@ specify std.container: - before: Thing = Container { _type = "Thing", _init = function (obj) return obj end } - it doesn't diagnose missing arguments: - expect (Thing ()).not_to_error () + expect (Thing ()).not_to_raise "any error" - it doesn't diagnose too many args: - expect (Thing ({}, false)).not_to_error () + expect (Thing ({}, false)).not_to_raise "any error" - context from Container prototype: - before: @@ -66,7 +66,7 @@ specify std.container: expect (things.count).to_be (nil) - it does not provide object methods: | things = Bag {} - expect (things:count ()).to_error "attempt to call method 'count'" + expect (things:count ()).to_raise "attempt to call method 'count'" - it does retain module functions: things = Bag { apples = 1, oranges = 3 } expect (Bag.count (things)).to_be (4) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index a690e10..3bb5b0c 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -50,19 +50,19 @@ specify std.debug: - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" - expect (f ()).to_error (badarg (1, "string")) - expect (f "foo").to_error (badarg (2, "int")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f "foo").to_raise (badarg (2, "int")) - it diagnoses wrong argument types: pending "Lua 5.1 support is dropped" - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("foo", false)).to_error (badarg (2, "int", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("foo", false)).to_raise (badarg (2, "int", "boolean")) expect (f ("foo", 1, false)). - to_error (badarg (3, "string or nil", "boolean")) + to_raise (badarg (3, "string or nil", "boolean")) expect (f ("foo", 1, "bar", false)). - to_error (badarg (4, "int or nil", "boolean")) + to_raise (badarg (4, "int or nil", "boolean")) - it diagnoses too many arguments: pending "Lua 5.1 support is dropped" - expect (f ("foo", 1, "bar", 2, false)).to_error (badarg (5)) + expect (f ("foo", 1, "bar", 2, false)).to_raise (badarg (5)) - it blames the call site by default: | expect (luaproc (mkstack ())).to_contain_error ":4: bad argument" @@ -70,11 +70,11 @@ specify std.debug: expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" - it reports the calling function name: - expect (f ('expect', 1)).to_error "'expect'" + expect (f ('expect', 1)).to_raise "'expect'" - it reports the argument number: | - expect (f ('expect', 12345)).to_error "#12345" + expect (f ('expect', 12345)).to_raise "#12345" - it reports extra message in parentheses: - expect (f ('expect', 1, "extramsg")).to_error " (extramsg)" + expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" - describe argcheck: @@ -104,19 +104,19 @@ specify std.debug: - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" - expect (f ()).to_error (badarg (1, "string")) - expect (f "foo").to_error (badarg (2, "int")) - expect (f ("foo", 1)).to_error (badarg (3, "string")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f "foo").to_raise (badarg (2, "int")) + expect (f ("foo", 1)).to_raise (badarg (3, "string")) - it diagnoses wrong argument types: pending "Lua 5.1 support is dropped" - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("foo", false)).to_error (badarg (2, "int", "boolean")) - expect (f ("foo", 1, false)).to_error (badarg (3, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (f ("foo", 1, false)).to_raise (badarg (3, "string", "boolean")) expect (f ("foo", 1, "bar", 2, false)). - to_error (badarg (5, "int or nil", "boolean")) + to_raise (badarg (5, "int or nil", "boolean")) - it diagnoses too many arguments: pending "Lua 5.1 support is dropped" - expect (f ("foo", 1, "bar", 2, 3, false)).to_error (badarg (6)) + expect (f ("foo", 1, "bar", 2, 3, false)).to_raise (badarg (6)) - it blames the calling function by default: | expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" @@ -139,286 +139,286 @@ specify std.debug: - context with primitives: - it diagnoses missing types: - expect (fn ("boolean", nil)).to_error "boolean expected, got no value" - expect (fn ("file", nil)).to_error "file expected, got no value" - expect (fn ("number", nil)).to_error "number expected, got no value" - expect (fn ("string", nil)).to_error "string expected, got no value" - expect (fn ("table", nil)).to_error "table expected, got no value" + expect (fn ("boolean", nil)).to_raise "boolean expected, got no value" + expect (fn ("file", nil)).to_raise "file expected, got no value" + expect (fn ("number", nil)).to_raise "number expected, got no value" + expect (fn ("string", nil)).to_raise "string expected, got no value" + expect (fn ("table", nil)).to_raise "table expected, got no value" - it diagnoses mismatched types: - expect (fn ("boolean", {0})).to_error "boolean expected, got table" - expect (fn ("file", {0})).to_error "file expected, got table" - expect (fn ("number", {0})).to_error "number expected, got table" - expect (fn ("string", {0})).to_error "string expected, got table" - expect (fn ("table", false)).to_error "table expected, got boolean" + expect (fn ("boolean", {0})).to_raise "boolean expected, got table" + expect (fn ("file", {0})).to_raise "file expected, got table" + expect (fn ("number", {0})).to_raise "number expected, got table" + expect (fn ("string", {0})).to_raise "string expected, got table" + expect (fn ("table", false)).to_raise "table expected, got boolean" expect (fn ("table", require "std.object")). - to_error "table expected, got Object" + to_raise "table expected, got Object" - it matches types: - expect (fn ("boolean", true)).not_to_error () - expect (fn ("file", io.stderr)).not_to_error () - expect (fn ("number", 1)).not_to_error () - expect (fn ("string", "s")).not_to_error () - expect (fn ("table", {})).not_to_error () + expect (fn ("boolean", true)).not_to_raise "any error" + expect (fn ("file", io.stderr)).not_to_raise "any error" + expect (fn ("number", 1)).not_to_raise "any error" + expect (fn ("string", "s")).not_to_raise "any error" + expect (fn ("table", {})).not_to_raise "any error" - context with int: - it diagnoses missing types: - expect (fn ("int", nil)).to_error "int expected, got no value" + expect (fn ("int", nil)).to_raise "int expected, got no value" - it diagnoses mismatched types: - expect (fn ("int", false)).to_error "int expected, got boolean" - expect (fn ("int", 1.234)).to_error "int expected, got number" - expect (fn ("int", 1234e-3)).to_error "int expected, got number" + expect (fn ("int", false)).to_raise "int expected, got boolean" + expect (fn ("int", 1.234)).to_raise "int expected, got number" + expect (fn ("int", 1234e-3)).to_raise "int expected, got number" - it matches types: - expect (fn ("int", 1)).not_to_error () - expect (fn ("int", 1.0)).not_to_error () - expect (fn ("int", 0x1234)).not_to_error () - expect (fn ("int", 1.234e3)).not_to_error () + expect (fn ("int", 1)).not_to_raise "any error" + expect (fn ("int", 1.0)).not_to_raise "any error" + expect (fn ("int", 0x1234)).not_to_raise "any error" + expect (fn ("int", 1.234e3)).not_to_raise "any error" - context with constant string: - it diagnoses missing types: - expect (fn (":foo", nil)).to_error ":foo expected, got no value" + expect (fn (":foo", nil)).to_raise ":foo expected, got no value" - it diagnoses mismatched types: - expect (fn (":foo", false)).to_error ":foo expected, got boolean" - expect (fn (":foo", ":bar")).to_error ":foo expected, got :bar" - expect (fn (":foo", "foo")).to_error ":foo expected, got string" + expect (fn (":foo", false)).to_raise ":foo expected, got boolean" + expect (fn (":foo", ":bar")).to_raise ":foo expected, got :bar" + expect (fn (":foo", "foo")).to_raise ":foo expected, got string" - it matches types: - expect (fn (":foo", ":foo")).not_to_error () + expect (fn (":foo", ":foo")).not_to_raise "any error" - context with callable types: - it diagnoses missing types: - expect (fn ("function", nil)).to_error "function expected, got no value" + expect (fn ("function", nil)).to_raise "function expected, got no value" - it diagnoses mismatched types: - expect (fn ("function", {0})).to_error "function expected, got table" + expect (fn ("function", {0})).to_raise "function expected, got table" - it matches types: - expect (fn ("function", function () end)).not_to_error () + expect (fn ("function", function () end)).not_to_raise "any error" expect (fn ("function", setmetatable ({}, {__call = function () end}))). - not_to_error () + not_to_raise "any error" - context with table of homogenous elements: - it diagnoses missing types: expect (fn ("table of boolean", nil)). - to_error "table expected, got no value" + to_raise "table expected, got no value" expect (fn ("table of booleans", nil)). - to_error "table expected, got no value" + to_raise "table expected, got no value" - it diagnoses mismatched types: expect (fn ("table of file", io.stderr)). - to_error "table expected, got file" + to_raise "table expected, got file" expect (fn ("table of files", io.stderr)). - to_error "table expected, got file" + to_raise "table expected, got file" - it diagnoses mismatched element types: expect (fn ("table of number", {false})). - to_error "table of numbers expected, got boolean at index 1" + to_raise "table of numbers expected, got boolean at index 1" expect (fn ("table of numbers", {1, 2, "3"})). - to_error "table of numbers expected, got string at index 3" + to_raise "table of numbers expected, got string at index 3" expect (fn ("table of numbers", {a=1, b=2, c="3"})). - to_error "table of numbers expected, got string at index c" + to_raise "table of numbers expected, got string at index c" - it matches types: - expect (fn ("table of string", {})).not_to_error () - expect (fn ("table of string", {"foo"})).not_to_error () - expect (fn ("table of string", {"f", "o", "o"})).not_to_error () - expect (fn ("table of string", {b="b", a="a", r="r"})).not_to_error () + expect (fn ("table of string", {})).not_to_raise "any error" + expect (fn ("table of string", {"foo"})).not_to_raise "any error" + expect (fn ("table of string", {"f", "o", "o"})).not_to_raise "any error" + expect (fn ("table of string", {b="b", a="a", r="r"})).not_to_raise "any error" - context with non-empty table types: - it diagnoses missing types: expect (fn ("#table", nil)). - to_error "non-empty table expected, got no value" + to_raise "non-empty table expected, got no value" - it diagnoses mismatched types: expect (fn ("#table", false)). - to_error "non-empty table expected, got boolean" + to_raise "non-empty table expected, got boolean" expect (fn ("#table", {})). - to_error "non-empty table expected, got empty table" + to_raise "non-empty table expected, got empty table" - it matches types: - expect (fn ("#table", {0})).not_to_error () + expect (fn ("#table", {0})).not_to_raise "any error" - context with non-empty table of homogenous elements: - it diagnoses missing types: expect (fn ("#table of boolean", nil)). - to_error "non-empty table expected, got no value" + to_raise "non-empty table expected, got no value" expect (fn ("#table of booleans", nil)). - to_error "non-empty table expected, got no value" + to_raise "non-empty table expected, got no value" - it diagnoses mismatched types: expect (fn ("#table of file", {})). - to_error "non-empty table expected, got empty table" + to_raise "non-empty table expected, got empty table" expect (fn ("#table of file", io.stderr)). - to_error "non-empty table expected, got file" + to_raise "non-empty table expected, got file" - it diagnoses mismatched element types: expect (fn ("#table of number", {false})). - to_error "non-empty table of numbers expected, got boolean at index 1" + to_raise "non-empty table of numbers expected, got boolean at index 1" expect (fn ("#table of numbers", {1, 2, "3"})). - to_error "non-empty table of numbers expected, got string at index 3" + to_raise "non-empty table of numbers expected, got string at index 3" expect (fn ("#table of numbers", {a=1, b=2, c="3"})). - to_error "non-empty table of numbers expected, got string at index c" + to_raise "non-empty table of numbers expected, got string at index c" - it matches types: - expect (fn ("#table of string", {"foo"})).not_to_error () - expect (fn ("#table of string", {"f", "o", "o"})).not_to_error () - expect (fn ("#table of string", {b="b", a="a", r="r"})).not_to_error () + expect (fn ("#table of string", {"foo"})).not_to_raise "any error" + expect (fn ("#table of string", {"f", "o", "o"})).not_to_raise "any error" + expect (fn ("#table of string", {b="b", a="a", r="r"})).not_to_raise "any error" - context with list: - it diagnonses missing types: expect (fn ("list", nil)). - to_error "list expected, got no value" + to_raise "list expected, got no value" - it diagnoses mismatched types: expect (fn ("list", false)). - to_error "list expected, got boolean" + to_raise "list expected, got boolean" expect (fn ("list", {foo=1})). - to_error "list expected, got table" + to_raise "list expected, got table" expect (fn ("list", Object)). - to_error "list expected, got Object" + to_raise "list expected, got Object" - it matches types: - expect (fn ("list", {})).not_to_error () - expect (fn ("list", {1})).not_to_error () + expect (fn ("list", {})).not_to_raise "any error" + expect (fn ("list", {1})).not_to_raise "any error" - context with list of homogenous elements: - it diagnoses missing types: expect (fn ("list of boolean", nil)). - to_error "list expected, got no value" + to_raise "list expected, got no value" expect (fn ("list of booleans", nil)). - to_error "list expected, got no value" + to_raise "list expected, got no value" - it diagnoses mismatched types: expect (fn ("list of file", io.stderr)). - to_error "list expected, got file" + to_raise "list expected, got file" expect (fn ("list of files", io.stderr)). - to_error "list expected, got file" + to_raise "list expected, got file" expect (fn ("list of files", {file=io.stderr})). - to_error "list expected, got table" + to_raise "list expected, got table" - it diagnoses mismatched element types: expect (fn ("list of number", {false})). - to_error "list of numbers expected, got boolean at index 1" + to_raise "list of numbers expected, got boolean at index 1" expect (fn ("list of numbers", {1, 2, "3"})). - to_error "list of numbers expected, got string at index 3" + to_raise "list of numbers expected, got string at index 3" - it matches types: - expect (fn ("list of string", {})).not_to_error () - expect (fn ("list of string", {"foo"})).not_to_error () - expect (fn ("list of string", {"f", "o", "o"})).not_to_error () + expect (fn ("list of string", {})).not_to_raise "any error" + expect (fn ("list of string", {"foo"})).not_to_raise "any error" + expect (fn ("list of string", {"f", "o", "o"})).not_to_raise "any error" - context with non-empty list: - it diagnonses missing types: expect (fn ("#list", nil)). - to_error "non-empty list expected, got no value" + to_raise "non-empty list expected, got no value" - it diagnoses mismatched types: expect (fn ("#list", false)). - to_error "non-empty list expected, got boolean" + to_raise "non-empty list expected, got boolean" expect (fn ("#list", {})). - to_error "non-empty list expected, got empty list" + to_raise "non-empty list expected, got empty list" expect (fn ("#list", {foo=1})). - to_error "non-empty list expected, got table" + to_raise "non-empty list expected, got table" expect (fn ("#list", Object)). - to_error "non-empty list expected, got empty Object" + to_raise "non-empty list expected, got empty Object" expect (fn ("#list", List {})). - to_error "non-empty list expected, got empty List" + to_raise "non-empty list expected, got empty List" - it matches types: - expect (fn ("#list", {1})).not_to_error () + expect (fn ("#list", {1})).not_to_raise "any error" - context with non-empty list of homogenous elements: - it diagnoses missing types: expect (fn ("#list of boolean", nil)). - to_error "non-empty list expected, got no value" + to_raise "non-empty list expected, got no value" expect (fn ("#list of booleans", nil)). - to_error "non-empty list expected, got no value" + to_raise "non-empty list expected, got no value" - it diagnoses mismatched types: expect (fn ("#list of file", {})). - to_error "non-empty list expected, got empty table" + to_raise "non-empty list expected, got empty table" expect (fn ("#list of file", io.stderr)). - to_error "non-empty list expected, got file" + to_raise "non-empty list expected, got file" expect (fn ("#list of files", {file=io.stderr})). - to_error "non-empty list expected, got table" + to_raise "non-empty list expected, got table" - it diagnoses mismatched element types: expect (fn ("#list of number", {false})). - to_error "non-empty list of numbers expected, got boolean at index 1" + to_raise "non-empty list of numbers expected, got boolean at index 1" expect (fn ("#list of numbers", {1, 2, "3"})). - to_error "non-empty list of numbers expected, got string at index 3" + to_raise "non-empty list of numbers expected, got string at index 3" - it matches types: - expect (fn ("#list of string", {"foo"})).not_to_error () - expect (fn ("#list of string", {"f", "o", "o"})).not_to_error () + expect (fn ("#list of string", {"foo"})).not_to_raise "any error" + expect (fn ("#list of string", {"f", "o", "o"})).not_to_raise "any error" - context with container: - it diagnoses missing types: expect (fn ("List of boolean", nil)). - to_error "List expected, got no value" + to_raise "List expected, got no value" expect (fn ("List of booleans", nil)). - to_error "List expected, got no value" + to_raise "List expected, got no value" - it diagnoses mismatched types: expect (fn ("List of file", io.stderr)). - to_error "List expected, got file" + to_raise "List expected, got file" expect (fn ("List of files", io.stderr)). - to_error "List expected, got file" + to_raise "List expected, got file" expect (fn ("List of files", {file=io.stderr})). - to_error "List expected, got table" + to_raise "List expected, got table" - it diagnoses mismatched element types: expect (fn ("List of number", List {false})). - to_error "List of numbers expected, got boolean at index 1" + to_raise "List of numbers expected, got boolean at index 1" expect (fn ("List of numbers", List {1, 2, "3"})). - to_error "List of numbers expected, got string at index 3" + to_raise "List of numbers expected, got string at index 3" - it matches types: - expect (fn ("list of string", List {})).not_to_error () - expect (fn ("list of string", List {"foo"})).not_to_error () - expect (fn ("list of string", List {"f", "o", "o"})).not_to_error () + expect (fn ("list of string", List {})).not_to_raise "any error" + expect (fn ("list of string", List {"foo"})).not_to_raise "any error" + expect (fn ("list of string", List {"f", "o", "o"})).not_to_raise "any error" - context with object: - it diagnoses missing types: - expect (fn ("object", nil)).to_error "object expected, got no value" - expect (fn ("Object", nil)).to_error "Object expected, got no value" - expect (fn ("Foo", nil)).to_error "Foo expected, got no value" - expect (fn ("any", nil)).to_error "any value expected, got no value" + expect (fn ("object", nil)).to_raise "object expected, got no value" + expect (fn ("Object", nil)).to_raise "Object expected, got no value" + expect (fn ("Foo", nil)).to_raise "Foo expected, got no value" + expect (fn ("any", nil)).to_raise "any value expected, got no value" - it diagnoses mismatched types: - expect (fn ("object", {0})).to_error "object expected, got table" - expect (fn ("Object", {0})).to_error "Object expected, got table" - expect (fn ("object", {_type="Object"})).to_error "object expected, got table" - expect (fn ("Object", {_type="Object"})).to_error "Object expected, got table" - expect (fn ("Object", Foo)).to_error "Object expected, got Foo" - expect (fn ("Foo", {0})).to_error "Foo expected, got table" - expect (fn ("Foo", Object)).to_error "Foo expected, got Object" + expect (fn ("object", {0})).to_raise "object expected, got table" + expect (fn ("Object", {0})).to_raise "Object expected, got table" + expect (fn ("object", {_type="Object"})).to_raise "object expected, got table" + expect (fn ("Object", {_type="Object"})).to_raise "Object expected, got table" + expect (fn ("Object", Foo)).to_raise "Object expected, got Foo" + expect (fn ("Foo", {0})).to_raise "Foo expected, got table" + expect (fn ("Foo", Object)).to_raise "Foo expected, got Object" - it matches types: - expect (fn ("object", Object)).not_to_error () - expect (fn ("object", Object {})).not_to_error () - expect (fn ("object", Foo)).not_to_error () - expect (fn ("object", Foo {})).not_to_error () + expect (fn ("object", Object)).not_to_raise "any error" + expect (fn ("object", Object {})).not_to_raise "any error" + expect (fn ("object", Foo)).not_to_raise "any error" + expect (fn ("object", Foo {})).not_to_raise "any error" - it matches anything: - expect (fn ("any", true)).not_to_error () - expect (fn ("any", {})).not_to_error () - expect (fn ("any", Object)).not_to_error () - expect (fn ("any", Foo {})).not_to_error () + expect (fn ("any", true)).not_to_raise "any error" + expect (fn ("any", {})).not_to_raise "any error" + expect (fn ("any", Object)).not_to_raise "any error" + expect (fn ("any", Foo {})).not_to_raise "any error" - context with a list of valid types: - it diagnoses missing elements: expect (fn ("string|table", nil)). - to_error "string or table expected, got no value" + to_raise "string or table expected, got no value" expect (fn ("string|list|#table", nil)). - to_error "string, list or non-empty table expected, got no value" + to_raise "string, list or non-empty table expected, got no value" expect (fn ("string|number|list|object", nil)). - to_error "string, number, list or object expected, got no value" + to_raise "string, number, list or object expected, got no value" - it diagnoses mismatched elements: expect (fn ("string|table", false)). - to_error "string or table expected, got boolean" + to_raise "string or table expected, got boolean" expect (fn ("string|#table", {})). - to_error "string or non-empty table expected, got empty table" + to_raise "string or non-empty table expected, got empty table" expect (fn ("string|number|#list|object", {})). - to_error "string, number, non-empty list or object expected, got empty table" + to_raise "string, number, non-empty list or object expected, got empty table" - it matches any type from a list: - expect (fn ("string|table", "foo")).not_to_error () - expect (fn ("string|table", {})).not_to_error () - expect (fn ("string|table", {0})).not_to_error () - expect (fn ("table|table", {})).not_to_error () - expect (fn ("#table|table", {})).not_to_error () + expect (fn ("string|table", "foo")).not_to_raise "any error" + expect (fn ("string|table", {})).not_to_raise "any error" + expect (fn ("string|table", {0})).not_to_raise "any error" + expect (fn ("table|table", {})).not_to_raise "any error" + expect (fn ("#table|table", {})).not_to_raise "any error" - context with an optional type element: - it diagnoses mismatched elements: expect (fn ("boolean?", "string")). - to_error "boolean or nil expected, got string" + to_raise "boolean or nil expected, got string" expect (fn ("boolean?|:symbol", {})). - to_error "boolean, :symbol or nil expected, got empty table" + to_raise "boolean, :symbol or nil expected, got empty table" - it matches nil against a single type: - expect (fn ("any?", nil)).not_to_error () - expect (fn ("boolean?", nil)).not_to_error () - expect (fn ("string?", nil)).not_to_error () + expect (fn ("any?", nil)).not_to_raise "any error" + expect (fn ("boolean?", nil)).not_to_raise "any error" + expect (fn ("string?", nil)).not_to_raise "any error" - it matches nil against a list of types: - expect (fn ("boolean?|table", nil)).not_to_error () - expect (fn ("string?|table", nil)).not_to_error () - expect (fn ("table?|#table", nil)).not_to_error () - expect (fn ("#table?|table", nil)).not_to_error () + expect (fn ("boolean?|table", nil)).not_to_raise "any error" + expect (fn ("string?|table", nil)).not_to_raise "any error" + expect (fn ("table?|#table", nil)).not_to_raise "any error" + expect (fn ("#table?|table", nil)).not_to_raise "any error" - it matches nil against a list of optional types: - expect (fn ("boolean?|table?", nil)).not_to_error () - expect (fn ("string?|table?", nil)).not_to_error () - expect (fn ("table?|#table?", nil)).not_to_error () - expect (fn ("#table?|table?", nil)).not_to_error () + expect (fn ("boolean?|table?", nil)).not_to_raise "any error" + expect (fn ("string?|table?", nil)).not_to_raise "any error" + expect (fn ("table?|#table?", nil)).not_to_raise "any error" + expect (fn ("#table?|table?", nil)).not_to_raise "any error" - it matches any named type: - expect (fn ("any?", false)).not_to_error () - expect (fn ("boolean?", false)).not_to_error () - expect (fn ("string?", "string")).not_to_error () + expect (fn ("any?", false)).not_to_raise "any error" + expect (fn ("boolean?", false)).not_to_raise "any error" + expect (fn ("string?", "string")).not_to_raise "any error" - it matches any type from a list: - expect (fn ("boolean?|table", {})).not_to_error () - expect (fn ("string?|table", {0})).not_to_error () - expect (fn ("table?|#table", {})).not_to_error () - expect (fn ("#table?|table", {})).not_to_error () + expect (fn ("boolean?|table", {})).not_to_raise "any error" + expect (fn ("string?|table", {0})).not_to_raise "any error" + expect (fn ("table?|#table", {})).not_to_raise "any error" + expect (fn ("#table?|table", {})).not_to_raise "any error" - it matches any type from a list with several optional specifiers: - expect (fn ("boolean?|table?", {})).not_to_error () - expect (fn ("string?|table?", {0})).not_to_error () - expect (fn ("table?|table?", {})).not_to_error () - expect (fn ("#table?|table?", {})).not_to_error () + expect (fn ("boolean?|table?", {})).not_to_raise "any error" + expect (fn ("string?|table?", {0})).not_to_raise "any error" + expect (fn ("table?|table?", {})).not_to_raise "any error" + expect (fn ("#table?|table?", {})).not_to_raise "any error" - describe argscheck: @@ -444,17 +444,17 @@ specify std.debug: - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" - expect (f "foo").to_error (badarg (2, "non-empty list")) - expect (f ("foo", {"bar"})).to_error (badarg (3, "table")) + expect (f "foo").to_raise (badarg (2, "non-empty list")) + expect (f ("foo", {"bar"})).to_raise (badarg (3, "table")) - it diagnoses wrong argument types: pending "Lua 5.1 support is dropped" - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) expect (f ("foo", false)). - to_error (badarg (2, "non-empty list", "boolean")) - expect (f ("foo", {"bar"}, false)).to_error (badarg (3, "table", "boolean")) + to_raise (badarg (2, "non-empty list", "boolean")) + expect (f ("foo", {"bar"}, false)).to_raise (badarg (3, "table", "boolean")) - it diagnoses too many arguments: pending "Lua 5.1 support is dropped" - expect (f ("foo", {"bar"}, {}, false)).to_error (badarg (4)) + expect (f ("foo", {"bar"}, {}, false)).to_raise (badarg (4)) - it blames the calling function: | expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" @@ -473,39 +473,39 @@ specify std.debug: - context with single argument table: - it diagnoses missing argument: expect (fn ({"boolean"}, {nil})). - to_error "boolean expected, got no value" + to_raise "boolean expected, got no value" - it reports the correct missing argument number: - expect (fn ({"boolean"}, {nil})).to_error "#1 " + expect (fn ({"boolean"}, {nil})).to_raise "#1 " - it diagnoses mismatched argument: expect (fn ({"boolean"}, {"false"})). - to_error "boolean expected, got string" + to_raise "boolean expected, got string" - it reports the correct mismatched argument number: - expect (fn ({"boolean"}, {"false"})).to_error "#1 " + expect (fn ({"boolean"}, {"false"})).to_raise "#1 " - it matches argument type: - expect (fn ({"boolean"}, {false})).not_to_error () + expect (fn ({"boolean"}, {false})).not_to_raise "any error" - context with multi-argument table: - it diagnoses missing argument: expect (fn ({"boolean", "table"}, {false, nil})). - to_error "table expected, got no value" + to_raise "table expected, got no value" expect (fn ({"boolean", "table", "string"}, {false, nil, "nil"})). - to_error "table expected, got no value" + to_raise "table expected, got no value" - it reports the correct missing argument number: - expect (fn ({"boolean", "table"}, {false, nil})).to_error "#2 " + expect (fn ({"boolean", "table"}, {false, nil})).to_raise "#2 " expect (fn ({"boolean", "table", "string"}, {false, nil, "nil"})). - to_error "#2 " + to_raise "#2 " - it diagnoses mismatched argument: expect (fn ({"boolean", "table"}, {false, "false"})). - to_error "table expected, got string" + to_raise "table expected, got string" expect (fn ({"boolean", "table", "string"}, {false, "nil", "nil"})). - to_error "table expected, got string" + to_raise "table expected, got string" - it reports the correct mismatched argument number: - expect (fn ({"boolean", "table"}, {false, "false"})).to_error "#2 " + expect (fn ({"boolean", "table"}, {false, "false"})).to_raise "#2 " expect (fn ({"boolean", "table", "string"}, {false, "nil", "nil"})). - to_error "#2 " + to_raise "#2 " - it matches argument type: - expect (fn ({"boolean", "table"}, {false, {}})).not_to_error () + expect (fn ({"boolean", "table"}, {false, {}})).not_to_raise "any error" expect (fn ({"boolean", "table", "string"}, {false, {}, "{}"})). - not_to_error () + not_to_raise "any error" - describe debug: diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index b17fec4..d352c77 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -34,9 +34,9 @@ specify std.functional: f, badarg = init (M, fname) - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) + expect (f ()).to_raise (badarg (1, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) - it writes a deprecation warning to standard error on first call: | -- Unwrap functable @@ -94,12 +94,12 @@ specify std.functional: f, badarg = init (M, "case") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (2, "non-empty table")) + expect (f ()).to_raise (badarg (2, "non-empty table")) - it diagnoses wrong argument types: expect (f ("no", false)). - to_error (badarg (2, "non-empty table", "boolean")) + to_raise (badarg (2, "non-empty table", "boolean")) - it diagnoses too many arguments: - expect (f (1, {2}, false)).to_error (badarg (3)) + expect (f (1, {2}, false)).to_raise (badarg (3)) - it matches against branch keys: expect (f ("yes", branches)).to_be (true) @@ -134,7 +134,7 @@ specify std.functional: f, badarg = init (M, "collect") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function or any value")) + expect (f ()).to_raise (badarg (1, "function or any value")) - it collects a list of single return value iterator results: expect (f (base.ielems, {"a", "b", "c"})).to_equal {"a", "b", "c"} @@ -150,10 +150,10 @@ specify std.functional: f, badarg = init (M, "compose") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) + expect (f ()).to_raise (badarg (1, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (f, false)).to_error (badarg (2, "function", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (f, false)).to_raise (badarg (2, "function", "boolean")) - it composes a single function correctly: expect (f (M.id) (1)).to_be (1) @@ -195,13 +195,13 @@ specify std.functional: f, badarg = init (M, "curry") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) - expect (f (f)).to_error (badarg (2, "int")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (f)).to_raise (badarg (2, "int")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (f, 1.234)).to_error (badarg (2, "int", "number")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (f, 1.234)).to_raise (badarg (2, "int", "number")) - it diagnoses too many arguments: - expect (f (f, 2, false)).to_error (badarg (3)) + expect (f (f, 2, false)).to_raise (badarg (3)) - it returns a zero argument function uncurried: expect (f (f, 0)).to_be (f) @@ -232,7 +232,7 @@ specify std.functional: - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable "=" code. - expect (f "=").to_error () + expect (f "=").to_raise () - it evaluates a string of lua code: expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) @@ -245,10 +245,10 @@ specify std.functional: f, badarg = init (M, "filter") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) - expect (f (f)).to_error (badarg (2, "function or any value")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (f)).to_raise (badarg (2, "function or any value")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) - it works with an empty table: expect (f (M.id, pairs, {})).to_equal {} @@ -281,11 +281,11 @@ specify std.functional: f, badarg = init (M, "flatten") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)).to_error (badarg (2)) + expect (f (t, false)).to_raise (badarg (2)) - it returns a table: expect (type (f (t))).to_be "table" @@ -326,14 +326,14 @@ specify std.functional: f, badarg = init (M, "foldl") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) - expect (f (f, nil)).to_error (badarg (2, "any value or table")) - expect (f (f, 42)).to_error (badarg (3, "table")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (f, nil)).to_raise (badarg (2, "any value or table")) + expect (f (f, 42)).to_raise (badarg (3, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (f, 42, false)).to_error (badarg (3, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (f, 42, false)).to_raise (badarg (3, "table", "boolean")) - it diagnoses too many arguments: - expect (f (f, 42, {}, false)).to_error (badarg (4)) + expect (f (f, 42, {}, false)).to_raise (badarg (4)) - it works with an empty table: expect (f (M.op["+"], 10000, {})).to_be (10000) @@ -350,14 +350,14 @@ specify std.functional: f, badarg = init (M, "foldr") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) - expect (f (f, nil)).to_error (badarg (2, "any value or table")) - expect (f (f, 42)).to_error (badarg (3, "table")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (f, nil)).to_raise (badarg (2, "any value or table")) + expect (f (f, 42)).to_raise (badarg (3, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (f, 42, false)).to_error (badarg (3, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (f, 42, false)).to_raise (badarg (3, "table", "boolean")) - it diagnoses too many arguments: - expect (f (f, 42, {}, false)).to_error (badarg (4)) + expect (f (f, 42, {}, false)).to_raise (badarg (4)) - it works with an empty table: expect (f (M.op["+"], 1, {})).to_be (1) @@ -384,11 +384,11 @@ specify std.functional: f, badarg = init (M, "lambda") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong arguments types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("foo", false)).to_error (badarg (2)) + expect (f ("foo", false)).to_raise (badarg (2)) - it diagnoses bad lambda string: expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" - it diagnoses an uncompilable expression: @@ -423,10 +423,10 @@ specify std.functional: f, badarg = init (M, "map") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) - expect (f (f)).to_error (badarg (2, "function or any value")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (f)).to_raise (badarg (2, "function or any value")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) - it works with an empty table: expect (f (M.id, ipairs, {})).to_equal {} @@ -465,15 +465,15 @@ specify std.functional: f, badarg = init (M, "map_with") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) - expect (f (fn)).to_error (badarg (2, "table")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (fn)).to_raise (badarg (2, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (fn, false)).to_error (badarg (2, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (fn, false)).to_raise (badarg (2, "table", "boolean")) expect (f (fn, {{}, false})). - to_error (badarg (2, "table of tables", "boolean at index 2")) + to_raise (badarg (2, "table of tables", "boolean at index 2")) - it diagnoses too many arguments: - expect (f (fn, t, false)).to_error (badarg (3)) + expect (f (fn, t, false)).to_raise (badarg (3)) - it works for an empty table: expect (f (fn, {})).to_equal ({}) @@ -503,12 +503,12 @@ specify std.functional: end) - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) + expect (f ()).to_raise (badarg (1, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (f, false)).to_error (badarg (2, "function or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (f, false)).to_raise (badarg (2, "function or nil", "boolean")) - it diagnoses too many arguments: - expect (f (f, f, false)).to_error (badarg (3)) + expect (f (f, f, false)).to_raise (badarg (3)) - it propagates multiple return values: expect (select (2, memfn (false))).to_be "bzzt" @@ -551,12 +551,12 @@ specify std.functional: f, badarg = init (M, "reduce") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) - expect (f (f)).to_error (badarg (2, "any value")) - expect (f (f, 1)).to_error (badarg (3, "function")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (f)).to_raise (badarg (2, "any value")) + expect (f (f, 1)).to_raise (badarg (3, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (f, 1, false)).to_error (badarg (3, "function", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (f, 1, false)).to_raise (badarg (3, "function", "boolean")) - it works with an empty table: expect (f (M.op["+"], 2, ipairs, {})).to_be (2) @@ -579,11 +579,11 @@ specify std.functional: f, badarg = init (M, "zip") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f (tt, false)).to_error (badarg (2)) + expect (f (tt, false)).to_raise (badarg (2)) - it works for an empty table: expect (f {}).to_equal {} @@ -603,15 +603,15 @@ specify std.functional: f, badarg = init (M, "zip_with") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) - expect (f (fn)).to_error (badarg (2, "table")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (fn)).to_raise (badarg (2, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (fn, false)).to_error (badarg (2, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (fn, false)).to_raise (badarg (2, "table", "boolean")) expect (f (fn, {{}, false})). - to_error (badarg (2, "table of tables", "boolean at index 2")) + to_raise (badarg (2, "table of tables", "boolean at index 2")) - it diagnoses too many arguments: - expect (f (fn, tt, false)).to_error (badarg (3)) + expect (f (fn, tt, false)).to_raise (badarg (3)) - it works for an empty table: expect (f (fn, {})).to_equal {} diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 8cd961e..02dd37f 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -38,11 +38,11 @@ specify std.io: f, badarg = init (M, "catdir") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("", false)).to_error (badarg (2, "string", "boolean")) - expect (f ("", "false", false)).to_error (badarg (3, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("", false)).to_raise (badarg (2, "string", "boolean")) + expect (f ("", "false", false)).to_raise (badarg (3, "string", "boolean")) - it treats initial empty string as root directory: expect (f ("")).to_be (dirsep) @@ -61,11 +61,11 @@ specify std.io: f, badarg = init (M, "catfile") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("", false)).to_error (badarg (2, "string", "boolean")) - expect (f ("", "false", false)).to_error (badarg (3, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("", false)).to_raise (badarg (2, "string", "boolean")) + expect (f ("", "false", false)).to_raise (badarg (3, "string", "boolean")) - it treats initial empty string as root directory: expect (f ("", "")).to_be (dirsep) @@ -86,9 +86,9 @@ specify std.io: f, badarg = init (M, "die") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it outputs a message to stderr: expect (luaproc (script)).to_fail_with "By 'eck!\n" @@ -155,9 +155,9 @@ specify std.io: f, badarg = init (M, "monkey_patch") - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)).to_error (badarg (2)) + expect (f (t, false)).to_raise (badarg (2)) - it installs readlines metamethod: f (t) @@ -187,15 +187,15 @@ specify std.io: f, badarg = init (M, "process_files") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "function")) + expect (f ()).to_raise (badarg (1, "function")) - it diagnoses wrong argument types: | - expect (f (false)).to_error (badarg (1, "function", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) 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 } - it diagnoses too many arguments: - expect (f (f, false)).to_error (badarg (2)) + expect (f (f, false)).to_raise (badarg (2)) - it defaults to `-` if no arguments were passed: expect (luaproc (ascript)).to_output "-\n" @@ -231,15 +231,15 @@ specify std.io: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end - it diagnoses wrong argument types: | - expect (f (false)).to_error (badarg (1, "file, string or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "file, string or nil", "boolean")) expect (f "not-an-existing-file"). - to_error "bad argument #1 to 'std.io.readlines' (" -- system dependent error message + to_raise "bad argument #1 to 'std.io.readlines' (" -- system dependent error message - it diagnoses closed file argument: closed = io.open (name, "r") closed:close () expect (f (closed)). - to_error (badarg (1, "file, string or nil", "closed file")) + to_raise (badarg (1, "file, string or nil", "closed file")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (badarg (2)) + expect (f ("string", false)).to_raise (badarg (2)) - it closes file handle upon completion: h = io.open (name) @@ -260,11 +260,11 @@ specify std.io: f, badarg = init (M, "shell") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (badarg (2)) + expect (f ("string", false)).to_raise (badarg (2)) - it returns the output from a shell command string: expect (f [[printf '%s\n' 'foo' 'bar']]).to_be "foo\nbar\n" @@ -283,15 +283,15 @@ specify std.io: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end - it diagnoses wrong argument types: | - expect (f (false)).to_error (badarg (1, "file, string or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "file, string or nil", "boolean")) expect (f "not-an-existing-file"). - to_error "bad argument #1 to 'std.io.slurp' (" -- system dependent error message + to_raise "bad argument #1 to 'std.io.slurp' (" -- system dependent error message - it diagnoses closed file argument: closed = io.open (name, "r") closed:close () expect (f (closed)). - to_error (badarg (1, "file, string or nil", "closed file")) + to_raise (badarg (1, "file, string or nil", "closed file")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (badarg (2)) + expect (f ("string", false)).to_raise (badarg (2)) - it reads content from an existing named file: expect (f (name)).to_be (content) @@ -312,11 +312,11 @@ specify std.io: f, badarg = init (M, "splitdir") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (badarg (2)) + expect (f ("string", false)).to_raise (badarg (2)) - it returns a filename as a one element list: expect (f ("hello")).to_equal {"hello"} @@ -336,9 +336,9 @@ specify std.io: f, badarg = init (M, "warn") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it outputs a message to stderr: expect (luaproc (script)).to_output_error "Ayup!\n" @@ -403,11 +403,11 @@ specify std.io: - it diagnoses wrong argument types: | expect (f (false)). - to_error (badarg (1, "file, string, number or nil", "boolean")) + to_raise (badarg (1, "file, string, number or nil", "boolean")) - it diagnoses closed file argument: | closed = io.open (name) closed:close () expect (f (closed)). - to_error (badarg (1, "file, string, number or nil", "closed file")) + to_raise (badarg (1, "file, string, number or nil", "closed file")) - it does not close the file handle upon completion: expect (io.type (h)).not_to_be "closed file" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 532c56d..1e917e8 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -78,12 +78,12 @@ specify std.list: f, badarg = init (M, "append") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "List")) - expect (f (l)).to_error (badarg (2, "any value")) + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "any value")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "List", "boolean")) + expect (f (false)).to_raise (badarg (1, "List", "boolean")) - it diagnoses too many arguments: - expect (f (l, 42, false)).to_error (badarg (3)) + expect (f (l, 42, false)).to_raise (badarg (3)) - context when called as a list object method: - it returns a list object: @@ -114,13 +114,13 @@ specify std.list: f, badarg = init (M, "compare") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "List")) - expect (f (l)).to_error (badarg (2, "List or table")) + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "List or table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "List", "boolean")) - expect (f (a, false)).to_error (badarg (2, "List or table", "boolean")) + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (a, false)).to_raise (badarg (2, "List or table", "boolean")) - it diagnoses too many arguments: - expect (f (a, b, false)).to_error (badarg (3)) + expect (f (a, b, false)).to_raise (badarg (3)) - context when called as a list object method: - it returns -1 when the first list is less than the second: | @@ -187,13 +187,13 @@ specify std.list: f, badarg = init (M, "concat") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "List")) - expect (f (l)).to_error (badarg (2, "List or table")) + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "List or table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "List", "boolean")) - expect (f (l, false)).to_error (badarg (2, "List or table", "boolean")) + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "List or table", "boolean")) expect (f (l, l, false)). - to_error (badarg (3, "List or table", "boolean")) + to_raise (badarg (3, "List or table", "boolean")) - context when called as a list object method: - it returns a list object: @@ -242,9 +242,9 @@ specify std.list: expect (err).to_be (nil) - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "any value")) + expect (f ()).to_raise (badarg (1, "any value")) - it diagnoses wrong argument types: - expect (f ("head", false)).to_error (badarg (2, "List or nil", "boolean")) + expect (f ("head", false)).to_raise (badarg (2, "List or nil", "boolean")) - it returns a list object: expect (prototype (f ("head", l))).to_be "List" @@ -283,17 +283,17 @@ specify std.list: f, badarg = init (M, fname) - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "List")) + expect (f ()).to_raise (badarg (1, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "List", "boolean")) + expect (f (false)).to_raise (badarg (1, "List", "boolean")) expect (f (List {0})). - to_error (badarg (1, "List of Lists", "number at index 1")) + to_raise (badarg (1, "List of Lists", "number at index 1")) expect (f (List {{}})). - to_error (badarg (1, "List of Lists", "empty table at index 1")) + to_raise (badarg (1, "List of Lists", "empty table at index 1")) expect (f (List { List {"a", "b"}, ""})). - to_error (badarg (1, "List of Lists", "string at index 2")) + to_raise (badarg (1, "List of Lists", "string at index 2")) - it diagnoses too many arguments: - expect (f (List { List {"a", "b"} }, false)).to_error (badarg (2)) + expect (f (List { List {"a", "b"} }, false)).to_raise (badarg (2)) - it returns a primitive table: expect (prototype (f (l))).to_be "table" @@ -386,11 +386,11 @@ specify std.list: f, badarg = init (M, fname) - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it returns a list object: expect (prototype (f (t))).to_be "List" @@ -409,13 +409,13 @@ specify std.list: f, badarg = init (M, "filter") - it diagnoses missing arguments: | - expect (f ()).to_error (badarg (1, "function")) - expect (f (p)).to_error (badarg (2, "List")) + expect (f ()).to_raise (badarg (1, "function")) + expect (f (p)).to_raise (badarg (2, "List")) - it diagnoses wrong argument types: | - expect (f (false)).to_error (badarg (1, "function", "boolean")) - expect (f (p, false)).to_error (badarg (2, "List", "boolean")) + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (p, false)).to_raise (badarg (2, "List", "boolean")) - it diagnoses too many arguments: - expect (f (p, l, false)).to_error (badarg (3)) + expect (f (p, l, false)).to_raise (badarg (3)) - context when called as a list object method: - it returns a list object: @@ -885,16 +885,16 @@ specify std.list: f, badarg = init (M, "project") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "any value")) - expect (f (f)).to_error (badarg (2, "List")) + expect (f ()).to_raise (badarg (1, "any value")) + expect (f (f)).to_raise (badarg (2, "List")) - it diagnoses wrong argument types: - expect (f (f, false)).to_error (badarg (2, "List", "boolean")) + expect (f (f, false)).to_raise (badarg (2, "List", "boolean")) expect (f (f, List {false})). - to_error (badarg (2, "List of tables", "boolean at index 1")) + to_raise (badarg (2, "List of tables", "boolean at index 1")) expect (f (f, List {{}, false})). - to_error (badarg (2, "List of tables", "boolean at index 2")) + to_raise (badarg (2, "List of tables", "boolean at index 2")) - it diagnoses too many arguments: - expect (f (f, l, false)).to_error (badarg (3)) + expect (f (f, l, false)).to_raise (badarg (3)) - context when called as a list object method: - it returns a list object: @@ -971,13 +971,13 @@ specify std.list: f, badarg = init (M, "rep") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "List")) - expect (f (l)).to_error (badarg (2, "int")) + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "int")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "List", "boolean")) - expect (f (l, false)).to_error (badarg (2, "int", "boolean")) + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "int", "boolean")) - it diagnoses too many arguments: - expect (f (l, 2, false)).to_error (badarg (3)) + expect (f (l, 2, false)).to_raise (badarg (3)) - context when called as a list object method: - it returns a list object: @@ -1057,13 +1057,13 @@ specify std.list: f, badarg = init (M, "shape") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) - expect (f ({})).to_error (badarg (2, "List")) + expect (f ()).to_raise (badarg (1, "table")) + expect (f ({})).to_raise (badarg (2, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_error (badarg (2, "List", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_raise (badarg (2, "List", "boolean")) - it diagnoses too many arguments: - expect (f ({}, l, false)).to_error (badarg (3)) + expect (f ({}, l, false)).to_raise (badarg (3)) - context when called as a list object method: - it returns a list object: | @@ -1095,13 +1095,13 @@ specify std.list: f, badarg = init (M, "sub") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "List")) + expect (f ()).to_raise (badarg (1, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "List", "boolean")) - expect (f (l, false)).to_error (badarg (2, "int or nil", "boolean")) - expect (f (l, 1, false)).to_error (badarg (3, "int or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "int or nil", "boolean")) + expect (f (l, 1, false)).to_raise (badarg (3, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (f (l, 1, 2, false)).to_error (badarg (4)) + expect (f (l, 1, 2, false)).to_raise (badarg (4)) - context when called as a list object method: - it returns a list object: | @@ -1129,11 +1129,11 @@ specify std.list: f, badarg = init (M, "tail") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "List")) + expect (f ()).to_raise (badarg (1, "List")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "List", "boolean")) + expect (f (false)).to_raise (badarg (1, "List", "boolean")) - it diagnoses too many arguments: - expect (f (l, false)).to_error (badarg (2)) + expect (f (l, false)).to_raise (badarg (2)) - context when called as a list object method: - it returns a list object: | diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 6df24ef..f254bde 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -34,12 +34,12 @@ specify std.math: f, badarg = init (M, "floor") - it diagnoses missing arguments: | - expect (f ()).to_error (badarg (1, "number")) + expect (f ()).to_raise (badarg (1, "number")) - it diagnoses wrong argument types: | - expect (f (1.2, false)).to_error (badarg (2, "int or nil", "boolean")) - expect (f (1.2, 3.4)).to_error (badarg (2, "int or nil", "number")) + expect (f (1.2, false)).to_raise (badarg (2, "int or nil", "boolean")) + expect (f (1.2, 3.4)).to_raise (badarg (2, "int or nil", "number")) - it diagnoses too many arguments: - expect (f (1, 2, 3)).to_error (badarg (3)) + expect (f (1, 2, 3)).to_raise (badarg (3)) - it rounds to the nearest smaller integer: expect (f (1.2)).to_be (1) @@ -65,9 +65,9 @@ specify std.math: f, badarg = init (M, "monkey_patch") - it diagnoses wrong argument types: | - expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)).to_error (badarg (2)) + expect (f (t, false)).to_raise (badarg (2)) - it installs math.floor function: f (t) @@ -79,14 +79,14 @@ specify std.math: f, badarg = init (M, "round") - it diagnoses missing arguments: | - expect (f ()).to_error (badarg (1, "number")) + expect (f ()).to_raise (badarg (1, "number")) - it diagnoses wrong argument types: | expect (f (1.2, false)). - to_error (badarg (2, "int or nil", "boolean")) + to_raise (badarg (2, "int or nil", "boolean")) expect (f (1.2, 3.4)). - to_error (badarg (2, "int or nil", "number")) + to_raise (badarg (2, "int or nil", "number")) - it diagnoses too many arguments: - expect (f (1, 2, 3)).to_error (badarg (3)) + expect (f (1, 2, 3)).to_raise (badarg (3)) - it rounds to the nearest integer: expect (f (1.2)).to_be (1) diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index 46bddc5..2280fee 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -58,7 +58,7 @@ specify std.optparse: to_match ("^Usage: parseme .*Banner .*Long .*Options:.*" .. "Footer .*/issues>%.") - it diagnoses incorrect input text: - expect (OptionParser "garbage in").to_error "argument must match" + expect (OptionParser "garbage in").to_raise "argument must match" - describe parser: - before: | diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index f57b69a..fd9b418 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -44,16 +44,16 @@ specify std.package: f, badarg = init (M, "find") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) - expect (f (path)).to_error (badarg (2, "string")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f (path)).to_raise (badarg (2, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f (path, false)).to_error (badarg (2, "string", "boolean")) - expect (f (path, "foo", false)).to_error (badarg (3, "int or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f (path, false)).to_raise (badarg (2, "string", "boolean")) + expect (f (path, "foo", false)).to_raise (badarg (3, "int or nil", "boolean")) expect (f (path, "foo", 1, 2)). - to_error (badarg (4, "boolean, :plain or nil", "number")) + to_raise (badarg (4, "boolean, :plain or nil", "number")) - it diagnoses too many arguments: - expect (f (path, "foo", 1, ":plain", false)).to_error (badarg (5)) + expect (f (path, "foo", 1, ":plain", false)).to_raise (badarg (5)) - it returns nil for unmatched element: expect (f (path, "unmatchable")).to_be (nil) @@ -76,14 +76,14 @@ specify std.package: f, badarg = init (M, "insert") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) - expect (f (path)).to_error (badarg (2, "int or string")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f (path)).to_raise (badarg (2, "int or string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f (path, false)).to_error (badarg (2, "int or string", "boolean")) - expect (f (path, 1, false)).to_error (badarg (3, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f (path, false)).to_raise (badarg (2, "int or string", "boolean")) + expect (f (path, 1, false)).to_raise (badarg (3, "string", "boolean")) - it diagnoses too many arguments: - expect (f (path, 1, "string", false)).to_error (badarg (4)) + expect (f (path, 1, "string", false)).to_raise (badarg (4)) - it appends by default: expect (f (path, "new")). @@ -111,11 +111,11 @@ specify std.package: f, badarg = init (M, "mappath") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) - expect (f ("")).to_error (badarg (2, "function")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f ("")).to_raise (badarg (2, "function")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("", false)).to_error (badarg (2, "function", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("", false)).to_raise (badarg (2, "function", "boolean")) - it calls a function with each path element: t = {} @@ -136,10 +136,10 @@ specify std.package: f, badarg = init (M, "normalize") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("", false)).to_error (badarg (2, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("", false)).to_raise (badarg (2, "string", "boolean")) - context with a single element: - it strips redundant . directories: @@ -188,12 +188,12 @@ specify std.package: f, badarg = init (M, "remove") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("", false)).to_error (badarg (2, "int or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("", false)).to_raise (badarg (2, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", 2, false)).to_error (badarg (3)) + expect (f ("string", 2, false)).to_raise (badarg (3)) - it removes the last item by default: expect (f (path)).to_be (M.normalize ("begin", "middle")) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index e597cb9..24987f1 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -54,14 +54,14 @@ specify std: f, badarg = init (M, "assert") - it diagnoses wrong argument types: - expect (f (false, false)).to_error (badarg (2, "string or nil", "boolean")) + expect (f (false, false)).to_raise (badarg (2, "string or nil", "boolean")) - context when it does not trigger: - it has a truthy initial argument: - expect (f (1)).not_to_error () - expect (f (true)).not_to_error () - expect (f "yes").not_to_error () - expect (f (false == false)).not_to_error () + 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) @@ -69,14 +69,14 @@ specify std: expect (f (false == false)).to_be (true) - context when it triggers: - it has a falsey initial argument: - expect (f ()).to_error () - expect (f (false)).to_error () - expect (f (1 == 0)).to_error () + 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_error "ah boo" + 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_error (string.format ("%s %d: %q", "here", 42, "a string")) + to_raise (string.format ("%s %d: %q", "here", 42, "a string")) - describe barrel: @@ -97,9 +97,9 @@ specify std: f (t) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it installs std.io monkey patches: expect (io_mt.readlines).to_be (M.io.readlines) @@ -168,11 +168,11 @@ specify std: f, badarg = init (M, "elems") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it is an iterator over table values: t = {} @@ -196,15 +196,15 @@ specify std: f, badarg = init (M, "eval") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("1", false)).to_error (badarg (2)) + expect (f ("1", false)).to_raise (badarg (2)) - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable "=" code. - expect (f "=").to_error () + expect (f "=").to_raise () - it evaluates a string of lua code: expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) @@ -214,13 +214,13 @@ specify std: f, badarg = init (M, "getmetamethod") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "object or table")) - expect (f ({})).to_error (badarg (2, "string")) + expect (f ()).to_raise (badarg (1, "object or table")) + expect (f ({})).to_raise (badarg (2, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "object or table", "boolean")) - expect (f ({}, false)).to_error (badarg (2, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "object or table", "boolean")) + expect (f ({}, false)).to_raise (badarg (2, "string", "boolean")) - it diagnoses too many arguments: - expect (f ({}, "foo", false)).to_error (badarg (3)) + expect (f ({}, "foo", false)).to_raise (badarg (3)) - context with a table: - before: @@ -254,11 +254,11 @@ specify std: f, badarg = init (M, "ielems") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it is an iterator over integer-keyed table values: t = {} @@ -287,11 +287,11 @@ specify std: f, badarg = init (M, "ipairs") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it is an iterator over integer-keyed table values: t = {} @@ -320,11 +320,11 @@ specify std: f, badarg = init (M, "ireverse") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it returns a new list: t = {1, 2, 5} @@ -357,9 +357,9 @@ specify std: f (t) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it installs std module functions: for _, v in ipairs (exported_apis) do @@ -374,11 +374,11 @@ specify std: f, badarg = init (M, "pairs") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it is an iterator over all table values: t = {} @@ -402,24 +402,24 @@ specify std: f, badarg = init (M, "require") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("module", false)).to_error (badarg (2, "string or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("module", false)).to_raise (badarg (2, "string or nil", "boolean")) expect (f ("module", "min", false)). - to_error (badarg (3, "string or nil", "boolean")) + to_raise (badarg (3, "string or nil", "boolean")) expect (f ("module", "min", "too_big", false)). - to_error (badarg (4, "string or nil", "boolean")) + to_raise (badarg (4, "string or nil", "boolean")) - it diagnoses too many arguments: expect (f ("module", "min", "too_big", "pattern", false)). - to_error (badarg (5)) + to_raise (badarg (5)) - it diagnoses non-existent module: - expect (f ("module-not-exists", "", "")).to_error "module-not-exists" + expect (f ("module-not-exists", "", "")).to_raise "module-not-exists" - it diagnoses module too old: - expect (f ("std", "9999", "9999")).to_error () + expect (f ("std", "9999", "9999")).to_raise () - it diagnoses module too new: - expect (f ("std", "0", "0")).to_error () + expect (f ("std", "0", "0")).to_raise () - context when the module version is compatible: - it returns the module table: expect (f ("std", "0", "9999")).to_be (require "std") @@ -440,16 +440,16 @@ specify std: - after: std.version = ver - it diagnoses module too old: - expect (f ("std", "1.2.4")).to_error () - expect (f ("std", "1.3")).to_error () - expect (f ("std", "2.1.2")).to_error () - expect (f ("std", "2")).to_error () - expect (f ("std", "1.2.10")).to_error () + expect (f ("std", "1.2.4")).to_raise () + expect (f ("std", "1.3")).to_raise () + expect (f ("std", "2.1.2")).to_raise () + expect (f ("std", "2")).to_raise () + expect (f ("std", "1.2.10")).to_raise () - it diagnoses module too new: - expect (f ("std", nil, "1.2.2")).to_error () - expect (f ("std", nil, "1.1")).to_error () - expect (f ("std", nil, "1.1.2")).to_error () - expect (f ("std", nil, "1")).to_error () + expect (f ("std", nil, "1.2.2")).to_raise () + expect (f ("std", nil, "1.1")).to_raise () + expect (f ("std", nil, "1.1.2")).to_raise () + expect (f ("std", nil, "1")).to_raise () - it returns modules with version in range: expect (f ("std")).to_be (std) expect (f ("std", "1")).to_be (std) @@ -466,9 +466,9 @@ specify std: - after: std.version = ver - it diagnoses module too old: - expect (f ("std", "42")).to_error () + expect (f ("std", "42")).to_raise () - it diagnoses module too new: - expect (f ("std", nil, "40")).to_error () + 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) @@ -482,11 +482,11 @@ specify std: f, badarg = init (M, "ripairs") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it returns a function, the table and a number: fn, t, i = f {1, 2, 3} @@ -517,7 +517,7 @@ specify std: f, badarg = init (M, "tostring") - it diagnoses too many arguments: - expect (f (true, false)).to_error (badarg (2)) + expect (f (true, false)).to_raise (badarg (2)) - it renders primitives exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index f863117..ee89fe7 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -72,10 +72,10 @@ specify std.string: - context when it does not trigger: - it has a truthy initial argument: - expect (f (1)).not_to_error () - expect (f (true)).not_to_error () - expect (f "yes").not_to_error () - expect (f (false == false)).not_to_error () + 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) @@ -83,14 +83,14 @@ specify std.string: expect (f (false == false)).to_be (true) - context when it triggers: - it has a falsey initial argument: - expect (f ()).to_error () - expect (f (false)).to_error () - expect (f (1 == 0)).to_error () + 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_error "ah boo" + 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_error (string.format ("%s %d: %q", "here", 42, "a string")) + to_raise (string.format ("%s %d: %q", "here", 42, "a string")) - describe caps: @@ -98,11 +98,11 @@ specify std.string: f, badarg = init (M, "caps") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (badarg (2)) + expect (f ("string", false)).to_raise (badarg (2)) - it capitalises words of a string: target = "A String \n\n" @@ -124,11 +124,11 @@ specify std.string: f, badarg = init (M, "chomp") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (badarg (2)) + expect (f ("string", false)).to_raise (badarg (2)) - it removes a single trailing newline from a string: expect (f (subject)).to_be (target) @@ -154,11 +154,11 @@ specify std.string: f, badarg = init (M, "escape_pattern") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (badarg (2)) + expect (f ("string", false)).to_raise (badarg (2)) - context with each printable ASCII char: - before: @@ -184,11 +184,11 @@ specify std.string: f, badarg = init (M, "escape_shell") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it diagnoses too many arguments: - expect (f ("string", false)).to_error (badarg (2)) + expect (f ("string", false)).to_raise (badarg (2)) - context with each printable ASCII char: - before: @@ -208,8 +208,8 @@ specify std.string: newstring = f (subject) expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") + expect (f ()).to_raise ("string expected") + expect (f {"a table"}).to_raise ("string expected") - describe finds: @@ -219,18 +219,18 @@ specify std.string: f, badarg = init (M, "finds") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) - expect (f ("string")).to_error (badarg (2, "string")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f ("string")).to_raise (badarg (2, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_error (badarg (2, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_raise (badarg (2, "string", "boolean")) expect (f ("string", "pattern", false)). - to_error (badarg (3, "int or nil", "boolean")) + to_raise (badarg (3, "int or nil", "boolean")) expect (f ("string", "pattern", nil, "plain")). - to_error (badarg (4, "boolean, :plain or nil", "string")) + to_raise (badarg (4, "boolean, :plain or nil", "string")) - it diagnoses too many arguments: expect (f ("string", "pattern", nil, false, function () end)). - to_error (badarg (5)) + to_raise (badarg (5)) - context given a complex nested list: - before: @@ -261,9 +261,9 @@ specify std.string: f, badarg = init (M, "format") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) - it returns a single argument without attempting formatting: expect (f (subject)).to_be (subject) @@ -282,13 +282,13 @@ specify std.string: f, badarg = init (M, "ltrim") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) expect (f ("string", false)). - to_error (badarg (2, "string or nil", "boolean")) + to_raise (badarg (2, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", "pattern", false)).to_error (badarg (3)) + expect (f ("string", "pattern", false)).to_raise (badarg (3)) - it removes whitespace from the start of a string: target = "a short string \t\r\n " @@ -313,9 +313,9 @@ specify std.string: f (t) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (t, false)).to_error (badarg (2)) + expect (f (t, false)).to_raise (badarg (2)) - it installs concat metamethod: # FIXME: string metatable monkey-patches leak out! @@ -332,11 +332,11 @@ specify std.string: f, badarg = init (M, "numbertosi") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "number or string")) + expect (f ()).to_raise (badarg (1, "number or string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "number or string", "boolean")) + expect (f (false)).to_raise (badarg (1, "number or string", "boolean")) - it diagnoses too many arguments: - expect (f (1, false)).to_error (badarg (2)) + expect (f (1, false)).to_raise (badarg (2)) - it returns a number using SI suffixes: target = {"1e-9", "1y", "1z", "1a", "1f", "1p", "1n", "1mu", "1m", "1", @@ -356,11 +356,11 @@ specify std.string: f, badarg = init (M, "ordinal_suffix") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "int or string")) + expect (f ()).to_raise (badarg (1, "int or string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "int or string", "boolean")) + expect (f (false)).to_raise (badarg (1, "int or string", "boolean")) - it diagnoses too many arguments: - expect (f (1, false)).to_error (badarg (2)) + expect (f (1, false)).to_raise (badarg (2)) - it returns the English suffix for a number: subject, target = {}, {} @@ -386,15 +386,15 @@ specify std.string: f, badarg = init (M, "pad") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) - expect (f ("string")).to_error (badarg (2, "int")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f ("string")).to_raise (badarg (2, "int")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_error (badarg (2, "int", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_raise (badarg (2, "int", "boolean")) expect (f ("string", 42, false)). - to_error (badarg (3, "string or nil", "boolean")) + to_raise (badarg (3, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", 42, "\t", false)).to_error (badarg (4)) + expect (f ("string", 42, "\t", false)).to_raise (badarg (4)) - context when string is shorter than given width: - before: @@ -462,11 +462,11 @@ specify std.string: f, badarg = init (M, "prettytostring") - it diagnoses wrong argument types: - expect (f (true, false)).to_error (badarg (2, "string or nil", "boolean")) + expect (f (true, false)).to_raise (badarg (2, "string or nil", "boolean")) expect (f (true, "indent", false)). - to_error (badarg (3, "string or nil", "boolean")) + to_raise (badarg (3, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f (true, "indent", "spacing", false)).to_error (badarg (4)) + expect (f (true, "indent", "spacing", false)).to_raise (badarg (4)) - it renders nil exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) @@ -515,22 +515,22 @@ specify std.string: f, badarg = init (M, "render") - it diagnoses missing arguments: - expect (f (true)).to_error (badarg (2, "function")) - expect (f (true, f)).to_error (badarg (3, "function")) - expect (f (true, f, f)).to_error (badarg (4, "function")) - expect (f (true, f, f, f)).to_error (badarg (5, "function")) - expect (f (true, f, f, f, f)).to_error (badarg (6, "function")) + expect (f (true)).to_raise (badarg (2, "function")) + expect (f (true, f)).to_raise (badarg (3, "function")) + expect (f (true, f, f)).to_raise (badarg (4, "function")) + expect (f (true, f, f, f)).to_raise (badarg (5, "function")) + expect (f (true, f, f, f, f)).to_raise (badarg (6, "function")) - it diagnoses wrong argument types: - expect (f (true, false)).to_error (badarg (2, "function", "boolean")) - expect (f (true, f, false)).to_error (badarg (3, "function", "boolean")) - expect (f (true, f, f, false)).to_error (badarg (4, "function", "boolean")) - expect (f (true, f, f, f, false)).to_error (badarg (5, "function", "boolean")) + expect (f (true, false)).to_raise (badarg (2, "function", "boolean")) + expect (f (true, f, false)).to_raise (badarg (3, "function", "boolean")) + expect (f (true, f, f, false)).to_raise (badarg (4, "function", "boolean")) + expect (f (true, f, f, f, false)).to_raise (badarg (5, "function", "boolean")) expect (f (true, f, f, f, f, false)). - to_error (badarg (6, "function", "boolean")) + to_raise (badarg (6, "function", "boolean")) expect (f (true, f, f, f, f, f, false)). - to_error (badarg (7, "table or nil", "boolean")) + to_raise (badarg (7, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f (true, f, f, f, f, f, {}, false)).to_error (badarg (8)) + expect (f (true, f, f, f, f, f, {}, false)).to_raise (badarg (8)) - it converts a primitive to a representative string: expect (r (nil)).to_be "nil" @@ -565,11 +565,11 @@ specify std.string: expect (err).to_be (nil) - it diagnoses non-existent module: - expect (f ("module-not-exists", "", "")).to_error "module-not-exists" + expect (f ("module-not-exists", "", "")).to_raise "module-not-exists" - it diagnoses module too old: - expect (f ("std", "9999", "9999")).to_error () + expect (f ("std", "9999", "9999")).to_raise () - it diagnoses module too new: - expect (f ("std", "0", "0")).to_error () + expect (f ("std", "0", "0")).to_raise () - context when the module version is compatible: - it returns the module table: expect (f ("std", "0", "9999")).to_be (require "std") @@ -590,16 +590,16 @@ specify std.string: - after: std.version = ver - it diagnoses module too old: - expect (f ("std", "1.2.4")).to_error () - expect (f ("std", "1.3")).to_error () - expect (f ("std", "2.1.2")).to_error () - expect (f ("std", "2")).to_error () - expect (f ("std", "1.2.10")).to_error () + expect (f ("std", "1.2.4")).to_raise () + expect (f ("std", "1.3")).to_raise () + expect (f ("std", "2.1.2")).to_raise () + expect (f ("std", "2")).to_raise () + expect (f ("std", "1.2.10")).to_raise () - it diagnoses module too new: - expect (f ("std", nil, "1.2.2")).to_error () - expect (f ("std", nil, "1.1")).to_error () - expect (f ("std", nil, "1.1.2")).to_error () - expect (f ("std", nil, "1")).to_error () + expect (f ("std", nil, "1.2.2")).to_raise () + expect (f ("std", nil, "1.1")).to_raise () + expect (f ("std", nil, "1.1.2")).to_raise () + expect (f ("std", nil, "1")).to_raise () - it returns modules with version in range: expect (f ("std")).to_be (std) expect (f ("std", "1")).to_be (std) @@ -617,12 +617,12 @@ specify std.string: f, badarg = init (M, "rtrim") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_error (badarg (2, "string or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_raise (badarg (2, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", "pattern", false)).to_error (badarg (3)) + expect (f ("string", "pattern", false)).to_raise (badarg (3)) - it removes whitespace from the end of a string: target = " \t\r\n a short string" @@ -647,13 +647,13 @@ specify std.string: f, badarg = init (M, "split") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) expect (f ("string", false)). - to_error (badarg (2, "string or nil", "boolean")) + to_raise (badarg (2, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", "pattern", false)).to_error (badarg (3)) + expect (f ("string", "pattern", false)).to_raise (badarg (3)) - it falls back to "%s+" when no pattern is given: expect (f (subject)). @@ -696,17 +696,17 @@ specify std.string: f, badarg = init (M, "tfind") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) - expect (f ("string")).to_error (badarg (2, "string")) + expect (f ()).to_raise (badarg (1, "string")) + expect (f ("string")).to_raise (badarg (2, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_error (badarg (2, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_raise (badarg (2, "string", "boolean")) expect (f ("string", "pattern", false)). - to_error (badarg (3, "int or nil", "boolean")) + to_raise (badarg (3, "int or nil", "boolean")) expect (f ("string", "pattern", 1, "plain")). - to_error (badarg (4, "boolean, :plain or nil", "string")) + to_raise (badarg (4, "boolean, :plain or nil", "string")) - it diagnoses too many arguments: - expect (f ("string", "pattern", 1, true, false)).to_error (badarg (5)) + expect (f ("string", "pattern", 1, true, false)).to_raise (badarg (5)) - it creates a list of pattern captures: target = { 1, 3, { "a", "b", "c" } } @@ -771,13 +771,13 @@ specify std.string: f, badarg = init (M, "trim") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) expect (f ("string", false)). - to_error (badarg (2, "string or nil", "boolean")) + to_raise (badarg (2, "string or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", "pattern", false)).to_error (badarg (3)) + expect (f ("string", "pattern", false)).to_raise (badarg (3)) - it removes whitespace from each end of a string: target = "a short string" @@ -805,16 +805,16 @@ specify std.string: f, badarg = init (M, "wrap") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "string")) + expect (f ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_error (badarg (2, "int or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("string", false)).to_raise (badarg (2, "int or nil", "boolean")) expect (f ("string", 72, false)). - to_error (badarg (3, "int or nil", "boolean")) + to_raise (badarg (3, "int or nil", "boolean")) expect (f ("string", 72, 4, false)). - to_error (badarg (4, "int or nil", "boolean")) + to_raise (badarg (4, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (f ("string", 72, 4, 8, false)).to_error (badarg (5)) + expect (f ("string", 72, 4, 8, false)).to_raise (badarg (5)) - it inserts newlines to wrap a string: target = "This is a collection of Lua libraries for Lua 5.1 a" .. @@ -853,8 +853,8 @@ specify std.string: newstring = f (subject, 55, 5) expect (subject).to_be (original) - it diagnoses indent greater than line width: - expect (f (subject, 10, 12)).to_error ("less than the line width") - expect (f (subject, 99, 99)).to_error ("less than the 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: - expect (f ()).to_error ("string expected") - expect (f {"a table"}).to_error ("string expected") + expect (f ()).to_raise ("string expected") + expect (f {"a table"}).to_raise ("string expected") diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 2009236..e7f4bb9 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -38,15 +38,15 @@ specify std.table: f, badarg = init (M, "clone") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) expect (f ({}, "nometa")). - to_error (badarg (2, "table, boolean, :nometa or nil", "string")) + to_raise (badarg (2, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, "nometa")). - to_error (badarg (3, "boolean, :nometa or nil", "string")) + to_raise (badarg (3, "boolean, :nometa or nil", "string")) - it diagnoses too many arguments: - expect (f ({}, {}, nil, false)).to_error (badarg (4)) + expect (f ({}, {}, nil, false)).to_raise (badarg (4)) - it does not just return the subject: expect (f (subject)).not_to_be (subject) @@ -108,8 +108,8 @@ specify std.table: expect (f ({k1 = "newkey"}, subject).newkey).to_be (subject.k1) - it diagnoses non-table arguments: - expect (f {}).to_error ("table expected") - expect (f ({}, "foo")).to_error ("table expected") + expect (f {}).to_raise ("table expected") + expect (f ({}, "foo")).to_raise ("table expected") - describe clone_select: @@ -120,15 +120,15 @@ specify std.table: f, badarg = init (M, "clone_select") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) expect (f ({}, "nometa")). - to_error (badarg (2, "table, boolean, :nometa or nil", "string")) + to_raise (badarg (2, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, "nometa")). - to_error (badarg (3, "boolean, :nometa or nil", "string")) + to_raise (badarg (3, "boolean, :nometa or nil", "string")) - it diagnoses too many arguments: - expect (f ({}, {}, nil, false)).to_error (badarg (4)) + expect (f ({}, {}, nil, false)).to_raise (badarg (4)) - it does not just return the subject: expect (f (subject)).not_to_be (subject) @@ -160,11 +160,11 @@ specify std.table: f, badarg = init (M, "empty") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it returns true for an empty table: expect (f {}).to_be (true) @@ -182,11 +182,11 @@ specify std.table: f, badarg = init (M, "invert") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it returns a new table: expect (f (subject)).not_to_be (subject) @@ -206,11 +206,11 @@ specify std.table: f, badarg = init (M, "keys") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it returns an empty list when subject is empty: expect (f {}).to_equal {} @@ -238,20 +238,20 @@ specify std.table: f, badarg = init (M, "merge") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) - expect (f ({})).to_error (badarg (2, "table")) + expect (f ()).to_raise (badarg (1, "table")) + expect (f ({})).to_raise (badarg (2, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_error (badarg (2, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_raise (badarg (2, "table", "boolean")) expect (f ({}, {}, "nometa")). - to_error (badarg (3, "table, boolean, :nometa or nil", "string")) + to_raise (badarg (3, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, {}, "nometa")). - to_error (badarg (4, "boolean, :nometa or nil", "string")) + to_raise (badarg (4, "boolean, :nometa or nil", "string")) - it diagnoses too many arguments: | expect (f ({}, {}, {}, ":nometa", false)). - to_error (badarg (5)) + to_raise (badarg (5)) pending "issue #78" - expect (f ({}, {}, ":nometa", false)).to_error (badarg (4)) + expect (f ({}, {}, ":nometa", false)).to_raise (badarg (4)) - it does not create a whole new table: expect (f (t1, t2)).to_be (t1) @@ -305,20 +305,20 @@ specify std.table: f, badarg = init (M, "merge_select") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) - expect (f ({})).to_error (badarg (2, "table")) + expect (f ()).to_raise (badarg (1, "table")) + expect (f ({})).to_raise (badarg (2, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_error (badarg (2, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_raise (badarg (2, "table", "boolean")) expect (f ({}, {}, "nometa")). - to_error (badarg (3, "table, boolean, :nometa or nil", "string")) + to_raise (badarg (3, "table, boolean, :nometa or nil", "string")) expect (f ({}, {}, {}, "nometa")). - to_error (badarg (4, "boolean, :nometa or nil", "string")) + to_raise (badarg (4, "boolean, :nometa or nil", "string")) - it diagnoses too many arguments: | expect (f ({}, {}, {}, ":nometa", false)). - to_error (badarg (5)) + to_raise (badarg (5)) pending "issue #78" - expect (f ({}, {}, ":nometa", false)).to_error (badarg (4)) + expect (f ({}, {}, ":nometa", false)).to_raise (badarg (4)) - it does not create a whole new table: expect (f (t1, t2)).to_be (t1) @@ -392,9 +392,9 @@ specify std.table: f (t) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it installs table.sort function: expect (t.table.sort).to_be (M.sort) @@ -405,9 +405,9 @@ specify std.table: f, badarg = init (M, "new") - it diagnoses wrong argument types: - expect (f (nil, false)).to_error (badarg (2, "table or nil", "boolean")) + expect (f (nil, false)).to_raise (badarg (2, "table or nil", "boolean")) - it diagnoses too many arguments: - expect (f ({}, {}, false)).to_error (badarg (3)) + expect (f ({}, {}, false)).to_raise (badarg (3)) - context when not setting a default: - before: default = nil @@ -487,11 +487,11 @@ specify std.table: f, badarg = init (M, "size") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it counts the number of keys in a table: expect (f (subject)).to_be (3) @@ -508,12 +508,12 @@ specify std.table: f, badarg = init (M, "sort") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_error (badarg (2, "function or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_raise (badarg (2, "function or nil", "boolean")) - it diagnoses too many arguments: - expect (f ({}, f, false)).to_error (badarg (3)) + expect (f ({}, f, false)).to_raise (badarg (3)) - it sorts elements in place: f (subject, cmp) @@ -531,11 +531,11 @@ specify std.table: f, badarg = init (M, "totable") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "object, table or string")) + expect (f ()).to_raise (badarg (1, "object, table or string")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "object, table or string", "boolean")) + expect (f (false)).to_raise (badarg (1, "object, table or string", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it calls object's __totable metamethod: object = setmetatable ({content = t}, mt) @@ -553,11 +553,11 @@ specify std.table: f, badarg = init (M, "values") - it diagnoses missing arguments: - expect (f ()).to_error (badarg (1, "table")) + expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: - expect (f (false)).to_error (badarg (1, "table", "boolean")) + expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses too many arguments: - expect (f ({}, false)).to_error (badarg (2)) + expect (f ({}, false)).to_raise (badarg (2)) - it returns an empty list when subject is empty: expect (f {}).to_equal {} diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 04d99c1..6b1b70e 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -58,8 +58,8 @@ specify std.tree: expect (subject).to_equal (target) expect (subject).to_be (subject) - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "foo").to_error ("table expected") + expect (f ()).to_raise ("table expected") + expect (f "foo").to_raise ("table expected") - describe ileaves: @@ -93,8 +93,8 @@ specify std.tree: end expect (l).to_equal {"one", "three", "five"} - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "string").to_error ("table expected") + expect (f ()).to_raise ("table expected") + expect (f "string").to_raise ("table expected") - describe inodes: @@ -179,8 +179,8 @@ specify std.tree: {"leaf", {2}, subject[2]}, -- five, {"join", {}, subject}} -- } - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "string").to_error ("table expected") + expect (f ()).to_raise ("table expected") + expect (f "string").to_raise ("table expected") - describe leaves: @@ -216,8 +216,8 @@ specify std.tree: expect (l).to_contain. a_permutation_of {"one", 2, "three", 4, "bar", "five"} - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "string").to_error ("table expected") + expect (f ()).to_raise ("table expected") + expect (f "string").to_raise ("table expected") - describe merge: @@ -246,8 +246,8 @@ specify std.tree: expect (m.k3).to_be (t2.k3) expect (m.k3).not_to_be (original.k3) - it diagnoses non-table arguments: - expect (f (nil, {})).to_error ("table expected") - expect (f ({}, nil)).to_error ("table expected") + expect (f (nil, {})).to_raise ("table expected") + expect (f ({}, nil)).to_raise ("table expected") - describe nodes: @@ -355,8 +355,8 @@ specify std.tree: {"leaf", {3}, subject[{3}]}, -- 3rd, {"join", {}, subject[{}]}} -- } - it diagnoses non-table arguments: - expect (f ()).to_error ("table expected") - expect (f "string").to_error ("table expected") + expect (f ()).to_raise ("table expected") + expect (f "string").to_raise ("table expected") - describe __index: diff --git a/specs/vector_spec.yaml b/specs/vector_spec.yaml index 6d2e57a..a759ee9 100644 --- a/specs/vector_spec.yaml +++ b/specs/vector_spec.yaml @@ -20,11 +20,11 @@ specify Vector: - describe __call: - it diagnoses wrong argument types: | expect (Vector (1, 2)). - to_error "bad argument #1 to 'Vector' (string expected, got number)" + to_raise "bad argument #1 to 'Vector' (string expected, got number)" expect (Vector (function () end)). - to_error "bad argument #1 to 'Vector' (int, string or table expected, got function)" + to_raise "bad argument #1 to 'Vector' (int, string or table expected, got function)" expect (Vector ("int", function () end)). - to_error "bad argument #2 to 'Vector' (int or table expected, got function)" + to_raise "bad argument #2 to 'Vector' (int or table expected, got function)" - context with inherited element type: - it constructs an empty vector: vector = Vector () @@ -274,7 +274,7 @@ specify Vector: expect (type (vector.push)).to_be "function" expect (type (vector.pop)).to_be "function" - it diagnoses undefined methods: - expect (vector.notamethod ()).to_error "attempt to call field 'notamethod'" + expect (vector.notamethod ()).to_raise "attempt to call field 'notamethod'" - describe __newindex: - it sets a new value at that index: @@ -289,7 +289,7 @@ specify Vector: for _, i in ipairs {vector.length * -2, -1 - vector.length, 0, vector.length + 1, vector.length * 2} do expect ((function () vector[i] = i end) ()). - to_error "out of bounds" + to_raise "out of bounds" end - describe __tostring: @@ -306,7 +306,7 @@ specify Vector: vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - it diagnoses missing arguments: | expect (Vector.pop ()). - to_error "bad argument #1 to 'pop' (Vector expected, got no value)" + to_raise "bad argument #1 to 'pop' (Vector expected, got no value)" - it returns nil for an empty vector: vector = Vector "char" expect (Vector.pop (vector)).to_be (nil) @@ -359,18 +359,18 @@ specify Vector: vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - it diagnoses missing arguments: | expect (Vector.push ()). - to_error "bad argument #1 to 'push' (Vector expected, got no value)" + to_raise "bad argument #1 to 'push' (Vector expected, got no value)" if vector.allocated > 0 then -- non-alien managed vectors don't require number valued argument #2 expect (Vector.push (vector)). - to_error "bad argument #2 to 'push' (number expected, got no value)" + to_raise "bad argument #2 to 'push' (number expected, got no value)" end - it diagnoses wrong argument types: | expect (Vector.push (1234)). - to_error "bad argument #1 to 'push' (Vector expected, got number)" + to_raise "bad argument #1 to 'push' (Vector expected, got number)" if vector.allocated > 0 then expect (Vector.push (vector, "short")). - to_error "bad argument #2 to 'push' (number expected, got string)" + to_raise "bad argument #2 to 'push' (number expected, got string)" end - it adds a single element to an empty vector: vector = Vector "int" @@ -397,12 +397,12 @@ specify Vector: - it diagnoses missing arguments: | if vector.allocated > 0 then expect (vector:push ()). - to_error "bad argument #2 to 'push' (number expected, got no value)" + to_raise "bad argument #2 to 'push' (number expected, got no value)" end - it diagnoses wrong argument type: | if vector.allocated > 0 then expect (vector:push ("short")). - to_error "bad argument #2 to 'push' (number expected, got string)" + to_raise "bad argument #2 to 'push' (number expected, got string)" end - it adds a single element to an empty vector: vector = Vector "int" @@ -429,14 +429,14 @@ specify Vector: - context when called as a module function: - it diagnoses missing arguments: | expect (Vector.realloc ()). - to_error "bad argument #1 to 'realloc' (Vector expected, got no value)" + to_raise "bad argument #1 to 'realloc' (Vector expected, got no value)" expect (Vector.realloc (vector)). - to_error "bad argument #2 to 'realloc' (int expected, got no value)" + to_raise "bad argument #2 to 'realloc' (int expected, got no value)" - it diagnoses wrong argument types: | expect (Vector.realloc (1234)). - to_error "bad argument #1 to 'realloc' (Vector expected, got number)" + to_raise "bad argument #1 to 'realloc' (Vector expected, got number)" expect (Vector.realloc (vector, "string")). - to_error "bad argument #2 to 'realloc' (int expected, got string)" + to_raise "bad argument #2 to 'realloc' (int expected, got string)" - it reduces the number of usable elements: vector = Vector (100) Vector.realloc (vector, 50) @@ -466,10 +466,10 @@ specify Vector: - context when called as an object method: - it diagnoses missing arguments: | expect (vector:realloc ()). - to_error "bad argument #2 to 'realloc' (int expected, got no value)" + to_raise "bad argument #2 to 'realloc' (int expected, got no value)" - it diagnoses wrong argument types: | expect (vector:realloc "string"). - to_error "bad argument #2 to 'realloc' (int expected, got string)" + to_raise "bad argument #2 to 'realloc' (int expected, got string)" - it reduces the number of usable elements: vector = Vector (100):realloc (50) expect (vector.length).to_be (50) @@ -496,26 +496,26 @@ specify Vector: - context when called as a module function: - it diagnoses missing arguments: | expect (Vector.set ()). - to_error "bad argument #1 to 'set' (Vector expected, got no value)" + to_raise "bad argument #1 to 'set' (Vector expected, got no value)" expect (Vector.set (vector)). - to_error "bad argument #2 to 'set' (int expected, got no value)" + to_raise "bad argument #2 to 'set' (int expected, got no value)" if vector.allocated > 0 then expect (Vector.set (vector, 1)). - to_error "bad argument #3 to 'set' (number expected, got no value)" + to_raise "bad argument #3 to 'set' (number expected, got no value)" end expect (Vector.set (vector, 1, 0)). - to_error "bad argument #4 to 'set' (int expected, got no value)" + to_raise "bad argument #4 to 'set' (int expected, got no value)" - it diagnoses wrong argument types: | expect (Vector.set (100)). - to_error "bad argument #1 to 'set' (Vector expected, got number)" + to_raise "bad argument #1 to 'set' (Vector expected, got number)" expect (Vector.set (vector, "bogus")). - to_error "bad argument #2 to 'set' (int expected, got string)" + to_raise "bad argument #2 to 'set' (int expected, got string)" if vector.allocated > 0 then expect (Vector.set (vector, 1, {0})). - to_error "bad argument #3 to 'set' (number expected, got table)" + to_raise "bad argument #3 to 'set' (number expected, got table)" end expect (Vector.set (vector, 1, 0, function () end)). - to_error "bad argument #4 to 'set' (int expected, got function)" + to_raise "bad argument #4 to 'set' (int expected, got function)" - it changes the value of a subsequence of elements: vector = Vector (100) Vector.set (vector, 25, 1, 50) @@ -557,22 +557,22 @@ specify Vector: - context when called as an object method: - it diagnoses missing arguments: | expect (vector:set ()). - to_error "bad argument #2 to 'set' (int expected, got no value)" + to_raise "bad argument #2 to 'set' (int expected, got no value)" if vector.allocated > 0 then expect (vector:set (1)). - to_error "bad argument #3 to 'set' (number expected, got no value)" + to_raise "bad argument #3 to 'set' (number expected, got no value)" end expect (vector:set (1, 0)). - to_error "bad argument #4 to 'set' (int expected, got no value)" + to_raise "bad argument #4 to 'set' (int expected, got no value)" - it diagnoses wrong argument types: | expect (vector:set "bogus"). - to_error "bad argument #2 to 'set' (int expected, got string)" + to_raise "bad argument #2 to 'set' (int expected, got string)" if vector.allocated > 0 then expect (vector:set (1, {0})). - to_error "bad argument #3 to 'set' (number expected, got table)" + to_raise "bad argument #3 to 'set' (number expected, got table)" end expect (vector:set (1, 0, function () end)). - to_error "bad argument #4 to 'set' (int expected, got function)" + to_raise "bad argument #4 to 'set' (int expected, got function)" - it changes the value of a subsequence of elements: vector = Vector (100):set (25, 1, 50) for i = 1, vector.length do @@ -616,7 +616,7 @@ specify Vector: vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - it diagnoses missing arguments: | expect (Vector.shift ()). - to_error "bad argument #1 to 'shift' (Vector expected, got no value)" + to_raise "bad argument #1 to 'shift' (Vector expected, got no value)" - it returns nil for an empty vector: vector = Vector "char" expect (Vector.shift (vector)).to_be (nil) @@ -671,17 +671,17 @@ specify Vector: vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - it diagnoses missing arguments: | expect (Vector.unshift ()). - to_error "bad argument #1 to 'unshift' (Vector expected, got no value)" + to_raise "bad argument #1 to 'unshift' (Vector expected, got no value)" if vector.allocated > 0 then expect (Vector.unshift (vector)). - to_error "bad argument #2 to 'unshift' (number expected, got no value)" + to_raise "bad argument #2 to 'unshift' (number expected, got no value)" end - it diagnoses wrong argument types: | expect (Vector.unshift (1234)). - to_error "bad argument #1 to 'unshift' (Vector expected, got number)" + to_raise "bad argument #1 to 'unshift' (Vector expected, got number)" if vector.allocated > 0 then expect (Vector.unshift (vector, "short")). - to_error "bad argument #2 to 'unshift' (number expected, got string)" + to_raise "bad argument #2 to 'unshift' (number expected, got string)" end - it adds a single element to an empty vector: vector = Vector "int" @@ -709,12 +709,12 @@ specify Vector: - it diagnoses missing arguments: | if vector.allocated > 0 then expect (vector:unshift ()). - to_error "bad argument #2 to 'unshift' (number expected, got no value)" + to_raise "bad argument #2 to 'unshift' (number expected, got no value)" end - it diagnoses wrong argument type: | if vector.allocated > 0 then expect (vector:unshift ("short")). - to_error "bad argument #2 to 'unshift' (number expected, got string)" + to_raise "bad argument #2 to 'unshift' (number expected, got string)" end - it adds a single element to an empty vector: vector = Vector "int" From 04c45bcff24a16926b64c425fdd04ef24f5f1c24 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 00:29:50 +0100 Subject: [PATCH 367/703] maint: remove stale files from .gitignore. * .gitignore: Remove unused /m4/ax_compare_version.m4. Signed-off-by: Gary V. Vaughan --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4634fab..e866906 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ /doc/modules /lib/std.lua /luarocks -/m4/ax_compare_version.m4 /m4/ax_lua.m4 /m4/slingshot.m4 /specs/spec_helper.lua From a964d6b588a2d643d167077ea2488612cc48a436 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 01:50:28 +0100 Subject: [PATCH 368/703] slingshot: sync with upstream, for slack notifications. * slingshot: Sync with upstream. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 11 +++++------ slingshot | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7755175..5b56034 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ env: - PACKAGE=stdlib - ROCKSPEC=$PACKAGE-git-1.rockspec - LUAROCKS_CONFIG=build-aux/luarocks-config.lua + - LUAROCKS_BASE=luarocks-2.2.0 - LUAROCKS="$LUA $HOME/bin/luarocks" matrix: - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 LUA_SUFFIX=5.1 @@ -26,10 +27,11 @@ install: - sudo apt-get install lua5.2 - sudo apt-get install liblua5.2-dev - # Install a luarocks beta locally for everything else. - - git clone --depth=1 https://github.com/keplerproject/luarocks.git + # Install a recent luarocks release locally for everything else. + - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz + - tar zxvpf $LUAROCKS_BASE.tar.gz # LuaRocks configure --with-lua argument is just a prefix! - - ( cd luarocks; + - ( cd $LUAROCKS_BASE; ./configure --prefix=$HOME --with-lua=/usr --lua-version=$LUA_SUFFIX --lua-suffix=$LUA_SUFFIX --with-lua-include=$LUA_INCDIR; @@ -67,6 +69,3 @@ script: LUA_CPATH=`pwd`'/ext/?.so;'"${LUA_CPATH-;}" LUA_INIT= LUA_INIT_5_2= make check V=1 - -notifications: - slack: aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih diff --git a/slingshot b/slingshot index 2218547..8779cd8 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 221854778ca93195232d48e5b6bc659242bd65a6 +Subproject commit 8779cd8abe3f49ec65e76590f3ac4e8c339a0f04 From 563d9a08338ea973dd5d57b73a127653a7ad71d8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 01:59:57 +0100 Subject: [PATCH 369/703] travis: add .slackid for slack notifications. * .slackid: New file. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .slackid | 1 + .travis.yml | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 .slackid diff --git a/.slackid b/.slackid new file mode 100644 index 0000000..4fa5980 --- /dev/null +++ b/.slackid @@ -0,0 +1 @@ +aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih diff --git a/.travis.yml b/.travis.yml index 5b56034..f90af29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,3 +69,6 @@ script: LUA_CPATH=`pwd`'/ext/?.so;'"${LUA_CPATH-;}" LUA_INIT= LUA_INIT_5_2= make check V=1 + +notifications: + slack: aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih From 00cc4d9dce6ce25ac19c7470a3c013939144c938 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 02:00:42 +0100 Subject: [PATCH 370/703] configury: use static specs/spec_helper.lua. No need to generate this file just to substitute @LUA@, just use os.getenv "LUA" in a static file instead. * specs/spec_helper.lua.in: Move from here... * specs/spec_helper.lua: New file. ...to here. (LUA): Set from LUA environment, or call `which lua` or just rely on path search for "lua". * .gitignore: Remove specs/spec_helper.lua. * configure.ac (AC_CONFIG_FILES): Remove specs/spec_helper.lua generator. * specs/specs.mk (specs_path, spec-check-local): Remove. (SPECL_ENV): Simplify. (EXTRA_DIST): Adjust. * specs/io_spec.yaml (process_files, readlines, slurp) (writelines): Don't rely on specs/spec_helper.lua being in the builddir, in case of VPATH builds. Signed-off-by: Gary V. Vaughan --- .gitignore | 1 - configure.ac | 1 - specs/io_spec.yaml | 10 +++++----- specs/{spec_helper.lua.in => spec_helper.lua} | 9 ++++----- specs/specs.mk | 7 ++----- 5 files changed, 11 insertions(+), 17 deletions(-) rename specs/{spec_helper.lua.in => spec_helper.lua} (96%) diff --git a/.gitignore b/.gitignore index e866906..9168ea4 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,5 @@ /luarocks /m4/ax_lua.m4 /m4/slingshot.m4 -/specs/spec_helper.lua /stdlib-*.tar.gz /travis.yml.in diff --git a/configure.ac b/configure.ac index 41d16c3..6732509 100644 --- a/configure.ac +++ b/configure.ac @@ -38,5 +38,4 @@ AC_PROG_SED dnl Generate output files SS_CONFIG_TRAVIS([ldoc specl]) AC_CONFIG_FILES([Makefile build-aux/config.ld]) -AC_CONFIG_FILES([specs/spec_helper.lua], [chmod a-w specs/spec_helper.lua]) AC_OUTPUT diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 02dd37f..173bd7b 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -169,8 +169,8 @@ specify std.io: - describe process_files: - before: - name = "specs/spec_helper.lua" - names = {"Makefile", "config.log", "config.status", "specs/spec_helper.lua"} + name = "Makefile" + names = {"Makefile", "config.log", "config.status", "build-aux/config.ld"} ascript = [[ require "std.io".process_files (function (a) print (a) end) ]] @@ -219,7 +219,7 @@ specify std.io: - describe readlines: - before: | - name = "specs/spec_helper.lua" + name = "Makefile" h = io.open (name) lines = {} for l in h:lines () do lines[#lines + 1] = l end h:close () @@ -272,7 +272,7 @@ specify std.io: - describe slurp: - before: | - name = "specs/spec_helper.lua" + name = "Makefile" h = io.open (name) content = h:read "*a" h:close () @@ -392,7 +392,7 @@ specify std.io: - before: | name = os.tmpname () h = io.open (name, "w") - lines = M.readlines (io.open "specs/spec_helper.lua") + lines = M.readlines (io.open "Makefile") defaultout = io.output () f, badarg = init (M, "writelines") diff --git a/specs/spec_helper.lua.in b/specs/spec_helper.lua similarity index 96% rename from specs/spec_helper.lua.in rename to specs/spec_helper.lua index bac5d2a..86bb271 100644 --- a/specs/spec_helper.lua.in +++ b/specs/spec_helper.lua @@ -5,11 +5,10 @@ local std = require "specl.std" package.path = std.package.normalize ("lib/?.lua", "lib/?/init.lua", package.path) --- Substitute configured LUA so that hell.spawn doesn't pick up --- a different Lua binary to the one used by Specl itself. If --- we could rely on luaposix availability `posix.getenv` would --- be a nicer way to find this... -local LUA = "@LUA@" +-- 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 os.execute "which lua" +if LUA == "" then LUA = "lua" end -- A copy of base.lua:prototype, so that an unloadable base.lua doesn't diff --git a/specs/specs.mk b/specs/specs.mk index dd0056b..4c64227 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -8,8 +8,7 @@ ## !!WARNING!! When bootstrap.conf:buildreq specl setting requires specl ## 12 or higher, remove this entire Environment section! -specs_path = $(abs_builddir)/specs/?.lua -SPECL_ENV = LUA_PATH="$(specs_path);$(std_path);$(LUA_PATH)" LUA_INIT= LUA_INIT_5_2= +SPECL_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" LUA_INIT= LUA_INIT_5_2= ## ------ ## @@ -45,9 +44,7 @@ specl_SPECS = \ $(NOTHING_ELSE) EXTRA_DIST += \ - $(srcdir)/specs/spec_helper.lua.in \ + $(srcdir)/specs/spec_helper.lua \ $(NOTHING_ELSE) -specl-check-local: specs/spec_helper.lua - include build-aux/specl.mk From 393e89d411e600ca66c0cce1a4eae153976cbeac Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 02:25:15 +0100 Subject: [PATCH 371/703] specs: fix os.execute thinko. * specs/spec_helper.lua (LUA): No need to repeat ourselves by looking for lua in the PATH with which and again at runtime. Signed-off-by: Gary V. Vaughan --- specs/spec_helper.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 86bb271..649f9b6 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -7,8 +7,7 @@ package.path = std.package.normalize ("lib/?.lua", "lib/?/init.lua", package.pat -- 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 os.execute "which lua" -if LUA == "" then LUA = "lua" end +local LUA = os.getenv "LUA" or "lua" -- A copy of base.lua:prototype, so that an unloadable base.lua doesn't From 4753518f53e489a206a0c399c1fb8eb452cdc066 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 10:44:34 +0100 Subject: [PATCH 372/703] refactor: simplify deprecation specs. * lib/std/base.lua (DEPRECATED): Use `name` as the key into the table for recording whether each deprecation message has been output yet... it's more likely to be unique than the inner function address, which might be shared between a module function and object method. * specs/functional_spec.yaml, specs/list_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Simplify specifications for deprecation warning. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 4 +- specs/functional_spec.yaml | 40 ++--- specs/list_spec.yaml | 319 ++++++++++--------------------------- specs/string_spec.yaml | 33 ++-- specs/table_spec.yaml | 33 ++-- 5 files changed, 117 insertions(+), 312 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 6ffc91f..8609bea 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -861,9 +861,9 @@ function (version, name, extramsg, fn) if fn == nil then fn, extramsg = extramsg, nil end return function (...) - if not getcompat (fn) then + if not getcompat (name) then io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) - setcompat (fn) + setcompat (name) end return fn (...) end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index d352c77..f91ee9f 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -38,17 +38,11 @@ specify std.functional: - it diagnoses wrong argument types: expect (f (false)).to_raise (badarg (1, "function", "boolean")) - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {init, M, fname}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "multi-argument 'std.functional.bind' was deprecated" - end - _, err = capture (f, {init, M, fname}) - expect (err).to_be (nil) + - it writes an argument passing deprecation warning on first call: + expect (capture (f, {init, M, fname})). + to_contain_error "was deprecated" + expect (capture (f, {init, M, fname})). + not_to_contain_error "was deprecated" - it does not affect normal operation if no arguments are bound: expect (f (math.min, {}) (2, 3, 4)).to_be (2) @@ -221,14 +215,9 @@ specify std.functional: - before: f = M.eval - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {"42"}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.functional.eval' was deprecated" - end - _, err = capture (f, {"42"}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {"42"})).to_contain_error "was deprecated" + expect (capture (f, {"42"})).not_to_contain_error "was deprecated" - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable "=" code. @@ -298,14 +287,11 @@ specify std.functional: - before: f = M.fold - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {M.id, 1, ipairs, {}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.functional.fold' was deprecated" - end - _, err = capture (f, {M.id, 1, ipairs, {}}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {M.id, 1, ipairs, {}})). + to_contain_error "was deprecated" + expect (capture (f, {M.id, 1, ipairs, {}})). + not_to_contain_error "was deprecated" - it works with an empty table: expect (f (M.op["+"], 2, ipairs, {})).to_be (2) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 1e917e8..76dc893 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -229,17 +229,9 @@ specify std.list: - before: f, badarg = init (M, fname) - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {l, "head"}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.cons' with list argument first was deprecated" - end - _, err = capture (f, {l, "head"}) - expect (err).to_be (nil) + - it writes a argument order deprecation warning on first call: + expect (capture (f, {l, "x"})).to_contain_error "was deprecated" + expect (capture (f, {l, "x"})).not_to_contain_error "was deprecated" - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "any value")) @@ -307,13 +299,9 @@ specify std.list: - before: f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:depair' was deprecated" - end - expect (select (2, capture (f, {l}))).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a primitive table: expect (prototype (l:depair ())).to_be "table" @@ -332,16 +320,9 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {{}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.elems' was deprecated" - end - expect (select (2, capture (f, {{}}))).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {{}})).to_contain_error "was deprecated" + expect (capture (f, {{}})).not_to_contain_error "was deprecated" - it is an iterator over list members: t = {} @@ -356,14 +337,9 @@ specify std.list: - before: f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:elems' was deprecated" - end - _, err = capture (f, {l}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it is an iterator over list members: t = {} @@ -438,17 +414,9 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.flatten' was deprecated" - end - _, err = capture (f, {l}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a list object: expect (prototype (f (l))).to_be "List" @@ -483,17 +451,11 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {op["+"], 1, {10}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.foldl' was deprecated" - end - _, err = capture (f, {op["+"], 1, {10}}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {op["+"], 1, {10}})). + to_contain_error "was deprecated" + expect (capture (f, {op["+"], 1, {10}})). + not_to_contain_error "was deprecated" - context with a table: - it works with an empty table: @@ -517,17 +479,11 @@ specify std.list: l = List {3, 4} f = l[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {l, op["+"], 1}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:foldl' was deprecated" - end - _, err = capture (f, {l, op["+"], 1}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l, op["+"], 1})). + to_contain_error "was deprecated" + expect (capture (f, {l, op["+"], 1})). + not_to_contain_error "was deprecated" - it works with an empty list: l = List {} @@ -548,17 +504,11 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {op["+"], 1, {10}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.foldr' was deprecated" - end - _, err = capture (f, {op["+"], 1, {10}}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {op["+"], 1, {10}})). + to_contain_error "was deprecated" + expect (capture (f, {op["+"], 1, {10}})). + not_to_contain_error "was deprecated" - context with a table: - it works with an empty table: @@ -583,17 +533,11 @@ specify std.list: l = List {10000, 100} f = l[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {l, op["+"], 1}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:foldr' was deprecated" - end - _, err = capture (f, {l, op["+"], 1}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l, op["+"], 1})). + to_contain_error "was deprecated" + expect (capture (f, {l, op["+"], 1})). + not_to_contain_error "was deprecated" - it works with an empty list: l = List {} @@ -612,17 +556,11 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {1, List {{1}}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.index_key' was deprecated" - end - _, err = capture (f, {1, List {{1}}}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {1, List {{1}}})). + to_contain_error "was deprecated" + expect (capture (f, {1, List {{1}}})). + not_to_contain_error "was deprecated" - it makes a map of matched table field values to table list offsets: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} @@ -647,14 +585,9 @@ specify std.list: - before: f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l, 1}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:index_key' was deprecated" - end - _, err = capture (f, {l, 1}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l, 1})).to_contain_error "was deprecated" + expect (capture (f, {l, 1})).not_to_contain_error "was deprecated" - it makes a map of matched table field values to table list offsets: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} @@ -684,17 +617,11 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {1, List {{1}}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.index_value' was deprecated" - end - _, err = capture (f, {1, List {{1}}}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {1, List {{1}}})). + to_contain_error "was deprecated" + expect (capture (f, {1, List {{1}}})). + not_to_contain_error "was deprecated" - it makes a table of matched table field values to table list references: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} @@ -720,14 +647,9 @@ specify std.list: l = List {{1}} f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l, 1}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:index_value' was deprecated" - end - _, err = capture (f, {l, 1}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l, 1})).to_contain_error "was deprecated" + expect (capture (f, {l, 1})).not_to_contain_error "was deprecated" - it makes a table of matched table field values to table list references: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} @@ -760,17 +682,9 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {sq, l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.map' was deprecated" - end - _, err = capture (f, {sq, l}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {sq, l})).to_contain_error "was deprecated" + expect (capture (f, {sq, l})).not_to_contain_error "was deprecated" - it returns a list object: m = f (sq, l) @@ -818,17 +732,9 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {fn, l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.map_with' was deprecated" - end - _, err = capture (f, {fn, l}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {fn, l})).to_contain_error "was deprecated" + expect (capture (f, {fn, l})).not_to_contain_error "was deprecated" - it returns a list object: m = f (fn, l) @@ -849,14 +755,9 @@ specify std.list: - before: f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l, fn}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:map_with' was deprecated" - end - _, err = capture (f, {l, fn}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l, fn})).to_contain_error "was deprecated" + expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - it returns a list object: m = f (l, fn) @@ -919,17 +820,9 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {{}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.relems' was deprecated" - end - _, err = capture (f, {{}}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it is a reverse iterator over list members: t = {} @@ -944,14 +837,9 @@ specify std.list: - before: f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:relems' was deprecated" - end - _, err = capture (f, {l}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it is a reverse iterator over list members: t = {} @@ -1000,17 +888,9 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {{}}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.reverse' was deprecated" - end - _, err = capture (f, {{}}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {{}})).to_contain_error "was deprecated" + expect (capture (f, {{}})).not_to_contain_error "was deprecated" - it returns a list object: expect (prototype (f (l))).to_be "List" @@ -1028,14 +908,9 @@ specify std.list: - before: f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:reverse' was deprecated" - end - _, err = capture (f, {l}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a list object: expect (prototype (l:reverse ())).to_be "List" @@ -1157,17 +1032,9 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.transpose' was deprecated" - end - _, err = capture (f, {l}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it transposes rows and columns: expect (f (l)).to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) @@ -1178,14 +1045,9 @@ specify std.list: - before: f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:transpose' was deprecated" - end - _, err = capture (f, {l}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a list object: | expect (prototype (l:transpose ())).to_be "List" @@ -1213,17 +1075,9 @@ specify std.list: - before: f = M[fname] - - it writes a deprecation warning to standard error on first call: | - -- Unwrap functable - f = type (f) == "function" and f or f.call or (getmetatable (f) or {}).__call - - _, err = capture (f, {l, fn}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list.zip_with' was deprecated" - end - _, err = capture (f, {l, fn}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l, fn})).to_contain_error "was deprecated" + expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - it returns a list object: expect (prototype (f (l, fn))).to_be "List" @@ -1242,14 +1096,9 @@ specify std.list: - before: f = l[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {l, fn}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.list:zip_with' was deprecated" - end - _, err = capture (f, {l, fn}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {l, fn})).to_contain_error "was deprecated" + expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - it returns a list object: | expect (prototype (f (l, fn))).to_be "List" diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index ee89fe7..e7b0c32 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -61,14 +61,9 @@ specify std.string: - before: f = M.assert - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {"std.string"}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.string.assert' was deprecated" - end - _, err = capture (f, {"std.string"}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {"std.string"})).to_contain_error "was deprecated" + expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" - context when it does not trigger: - it has a truthy initial argument: @@ -555,14 +550,9 @@ specify std.string: - before: f = M.require_version - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {"std.string"}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.string.require_version' was deprecated" - end - _, err = capture (f, {"std.string"}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {"std.string"})).to_contain_error "was deprecated" + expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" - it diagnoses non-existent module: expect (f ("module-not-exists", "", "")).to_raise "module-not-exists" @@ -733,14 +723,9 @@ specify std.string: - before: f = M.tostring - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {"std.string"}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.string.tostring' was deprecated" - end - _, err = capture (f, {"std.string"}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {"std.string"})).to_contain_error "was deprecated" + expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" - it renders primitives exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index e7f4bb9..76ba6bd 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -85,14 +85,9 @@ specify std.table: fname = "clone_rename" f = M[fname] - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {{}, subject}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.table.clone_rename' was deprecated" - end - _, err = capture (f, {{}, subject}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {{}, subject})).to_contain_error "was deprecated" + expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" - it copies the subject: expect (f ({}, subject)).to_copy (subject) @@ -365,14 +360,9 @@ specify std.table: f = M.metamethod - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {{}, subject}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.table.metamethod' was deprecated" - end - _, err = capture (f, {{}, subject}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {{}, subject})).to_contain_error "was deprecated" + expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" - it returns nil for missing metamethods: expect (f (obj, "not a method on obj")).to_be (nil) @@ -457,14 +447,9 @@ specify std.table: - before: f = M.ripairs - - it writes a deprecation warning to standard error on first call: | - _, err = capture (f, {{}, subject}) - if err ~= nil then - -- skip this test when using Specl < 12 capture stub - expect (err).to_contain "'std.table.ripairs' was deprecated" - end - _, err = capture (f, {{}, subject}) - expect (err).to_be (nil) + - it writes a deprecation warning on first call: + expect (capture (f, {{}, subject})).to_contain_error "was deprecated" + expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" - it returns a function, the table and a number: fn, t, i = f {1, 2, 3} From 07ecd869ac7dea75d915bcde6b5e2f8d6ee1bf0a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 16:24:42 +0100 Subject: [PATCH 373/703] list: export all apis for automatic argument type checking. * specs/list_spec.yaml: Specify argument checking of all apis. * lib/std/functional.lua (map): Move core function from here... * lib/std/base/functional.lua (map): ...to here. * lib/std/list.lua (project, map, map_with, transpose): Use it. (m): Collect exported object methods here. (List.__index): Set to m. Signed-off-by: Gary V. Vaughan --- lib/std/base/functional.lua | 24 + lib/std/functional.lua | 25 +- lib/std/list.lua | 412 +++++++------- specs/list_spec.yaml | 1001 ++++++++++++++++++++++------------- 4 files changed, 890 insertions(+), 572 deletions(-) diff --git a/lib/std/base/functional.lua b/lib/std/base/functional.lua index bbdfb00..743456d 100644 --- a/lib/std/base/functional.lua +++ b/lib/std/base/functional.lua @@ -15,6 +15,29 @@ local function callable (x) end +local function map (mapfn, ifn, ...) + local argt = {...} + if not callable (ifn) or not next (argt) then + ifn, argt = pairs, {ifn, ...} + end + + local nextfn, state, k = ifn (unpack (argt)) + local mapargs = {nextfn (state, k)} + + local r = {} + while mapargs[1] ~= nil do + k = mapargs[1] + local d, v = mapfn (unpack (mapargs)) + if v == nil then d, v = #r + 1, d end + if v ~= nil then + r[d] = v + end + mapargs = {nextfn (state, k)} + end + return r +end + + local function reduce (fn, d, ifn, ...) local nextfn, state, k = ifn (...) local t = {nextfn (state, k)} @@ -30,5 +53,6 @@ end return { callable = callable, + map = map, reduce = reduce, } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index fb41f6a..4f86c43 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -13,7 +13,8 @@ local operator = require "std.operator" local export, ielems, ipairs, ireverse, len, pairs = base.export, base.ielems, base.ipairs, base.ireverse, base.len, base.pairs -local callable, reduce = base.functional.callable, base.functional.reduce +local callable, map, reduce = + base.functional.callable, base.functional.map, base.functional.reduce local leaves = base.tree.leaves local M = { "std.functional" } @@ -406,27 +407,7 @@ end, M.id)) -- @usage -- --> {1, 4, 9, 16} -- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) -export (M, "map (func, [func], any*)", function (mapfn, ifn, ...) - local argt = {...} - if not callable (ifn) then - ifn, argt = pairs, {ifn, ...} - end - - local nextfn, state, k = ifn (unpack (argt)) - local mapargs = {nextfn (state, k)} - - local r = {} - while mapargs[1] ~= nil do - k = mapargs[1] - local d, v = mapfn (unpack (mapargs)) - if v == nil then d, v = #r + 1, d end - if v ~= nil then - r[d] = v - end - mapargs = {nextfn (state, k)} - end - return r -end) +export (M, "map (func, [func], any*)", map) --- Map a function over a table of argument lists. diff --git a/lib/std/list.lua b/lib/std/list.lua index a951923..d2bd5c5 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -1,10 +1,10 @@ --[[-- Tables as lists. - Every list is also an object, and thus inherits all of the `std.object` - methods, particularly use of object cloning for making new list objects. + Every List is also an Object, and thus inherits all of the `std.object` + methods, particularly use of object cloning for making new List objects. - In addition to calling methods on list objects in OO style... + In addition to calling methods on List objects in OO style... local list = require "std.list" -- module table local List = list {} -- prototype object @@ -35,15 +35,18 @@ local Object = object {} local List -- forward declaration local ipairs, pairs = base.ipairs, base.pairs -local argerror, argscheck, export, ielems, prototype, ireverse = - base.argerror, base.argscheck, base.export, base.ielems, base.prototype, base.ireverse - +local argscheck, export, ielems, prototype = + base.argscheck, base.export, base.ielems, base.prototype local M = { "std.list" } ------- --- An Object derived List. --- @table List +local map = function (...) return List (base.functional.map (...)) end + + +--[[ ================= ]]-- +--[[ Module Functions. ]]-- +--[[ ================= ]]-- + --- Append an item to a list. -- @static @@ -112,19 +115,23 @@ end) -- @tparam List l a list -- @param x item -- @treturn List new list containing `{x, unpack (l)}` -M.cons = function (x, l) +local cons = function (x, l, ...) if prototype (x) == "List" and prototype (l) ~= "List" then if not base.getcompat (M.cons) then io.stderr:write (base.DEPRECATIONMSG ("41", - "'std.list.cons' with list argument first", 2)) + "'std.list.cons' with List argument first", 2)) base.setcompat (M.cons) end x, l = l, x end argscheck ("std.list.cons", {"any", "List?"}, {x, l}) + if next {...} then + error (string.format (base.toomanyarg_fmt, "std.list.cons", 2, 2 + base.len {...}), 2) + end return List {x, unpack (l or {})} end +M.cons = cons --- Turn a list of pairs into a table. @@ -179,8 +186,8 @@ end) -- @tparam List l a list of tables -- @treturn List list of `f` fields -- @see std.list:project -local project = export (M, "project (any, List of tables)", function (f, l) - return List (func.map (function (t) return t[f] end, ielems, l)) +local project = export (M, "project (any, List of tables)", function (x, l) + return map (function (t) return t[x] end, ielems, l) end) @@ -223,7 +230,11 @@ end) -- @see std.list:shape local function flatten (l) - return List (func.collect (base.tree.leaves, ipairs, l)) + local r = List {} + for v in base.tree.leaves (ipairs, l) do + r[#r + 1] = v + end + return r end local shape = export (M, "shape (table, List)", function (s, l) @@ -300,26 +311,123 @@ end) ---[[ ============= ]]-- ---[[ Deprecations. ]]-- ---[[ ============= ]]-- +--[[ =============== ]]-- +--[[ Object Methods. ]]-- +--[[ =============== ]]-- -local DEPRECATED = base.DEPRECATED +local m = { "std.list", "List" } -M.elems = DEPRECATED ("41", "'std.list.elems'", - "use 'std.ielems' instead", base.ielems) +--- Append an item to a list. +-- @function append +-- @param x item +-- @treturn List new list containing `{self[1], ..., self[#self], x}` +export (m, "append (any)", append) -local function relems (l) return base.ielems (base.ireverse (l)) end +--- Compare two lists element-by-element, from left-to-right. +-- +-- if a_list:compare (another_list) == 0 then print "same" end +-- @function compare +-- @tparam table l a list +-- @return -1 if `self` is less than `l`, 0 if they are the same, and 1 +-- if `self` is greater than `l` +export (m, "compare (List|table)", compare) + + +--- Concatenate arguments into a list. +-- @function concat +-- @param ... tuple of lists +-- @treturn List new list containing +-- `{self[1], ..., self[#self], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` +export (m, "concat (List|table*)", concat) + + +--- Prepend an item to a list. +-- @function cons +-- @param x item +-- @treturn List new list containing `{x, unpack (self)}` +export (m, "cons (any)", + function (self, x) return cons (x, self) end) + -M.relems = DEPRECATED ("41", "'std.list.relems'", - "compose 'std.ielems' and 'std.ireverse' instead", relems) +--- Filter a list according to a predicate. +-- @function filter +-- @func p predicate function, of one argument returning a boolean +-- @treturn List new list containing elements `e` of `self` for which +-- `p (e)` is true +-- @see std.list.filter +export (m, "filter (func)", + function (self, p) return filter (p, self) end) -M.flatten = DEPRECATED ("41", "'std.list.flatten'", - "use 'std.functional.flatten' instead", flatten) +--- Flatten a list. +-- @function flatten +-- @treturn List flattened list +export (m, "flatten ()", flatten) + + +--- Map a function over a list. +-- @function map +-- @func fn map function +-- @treturn List new list containing +-- `{fn (self[1]), ..., fn (self[#self])}` +-- @see std.list.map +export (m, "map (func)", + function (self, fn) return map (fn, self) end) + + +--- Project a list of fields from a list of tables. +-- @function project +-- @param f field to project +-- @treturn List list of `f` fields +-- @see std.list.project +export (m, "project (any)", function (self, x) + base.argcheck ("std.list:project", 0, "List of tables", self) + return project (x, self) +end) + + +--- Repeat a list. +-- @function rep +-- @int n number of times to repeat +-- @treturn List `n` copies of `self` appended together +export (m, "rep (int)", rep) + + +--- Shape a list according to a list of dimensions. +-- @function shape +-- @tparam table s `{d1, ..., dn}` +-- @return reshaped list +-- @see std.list.shape +export (m, "shape (table)", + function (self, s) return shape (s, self) end) + + +--- Return a sub-range of a list. +-- (The equivalent of `string.sub` on strings; negative list indices +-- count from the end of the list.) +-- @function sub +-- @int from start of range (default: 1) +-- @int to end of range (default: `#self`) +-- @treturn List new list containing `{self[from], ..., self[to]}` +export (m, "sub (int?, int?)", sub) + + +--- Return a list with its first element removed. +-- @function tail +-- @treturn List new list containing `{self[2], ..., self[#self]}` +export (m, "tail ()", tail) + + + +--[[ ============= ]]-- +--[[ Deprecations. ]]-- +--[[ ============= ]]-- + + +local DEPRECATED = base.DEPRECATED local function foldl (fn, d, t) @@ -331,9 +439,6 @@ local function foldl (fn, d, t) return base.functional.reduce (fn, d, ipairs, t) end -M.foldl = DEPRECATED ("41", "'std.list.foldl'", - "use 'std.functional.foldl' instead", foldl) - local function foldr (fn, d, t) if t == nil then @@ -342,12 +447,9 @@ local function foldr (fn, d, t) d, t = d[last], u end return base.functional.reduce ( - function (x, y) return fn (y, x) end, d, ipairs, ireverse (t)) + function (x, y) return fn (y, x) end, d, ipairs, base.ireverse (t)) end -M.foldr = DEPRECATED ("41", "'std.list.foldr'", - "use 'std.functional.foldr' instead", foldr) - local function index_key (f, l) local r = {} @@ -360,9 +462,6 @@ local function index_key (f, l) return r end -M.index_key = DEPRECATED ("41", "'std.list.index_key'", - "compose 'std.list.filter' and 'std.table.invert' instead", index_key) - local function index_value (f, l) local r = {} @@ -375,67 +474,129 @@ local function index_value (f, l) return r end -M.index_value = DEPRECATED ("41", "'std.list.index_value'", - "compose 'std.list.filter' and 'std.table.invert' instead", index_value) - - -local function map (fn, l) return List (func.map (fn, ielems, l)) end - -M.map = DEPRECATED ("41", "'std.list.map'", - "use 'std.functional.map' instead", map) - local function map_with (fn, ls) - return List (func.map (func.compose (unpack, fn), ielems, ls)) + return map (function (...) return fn (unpack (...)) end, ielems, ls) end -M.map_with = DEPRECATED ("41", "'std.list.map_with'", - "use 'std.functional.map_with' instead", map_with) +local function relems (l) return base.ielems (base.ireverse (l)) end -local function reverse (l) return List (ireverse (l)) end -M.reverse = DEPRECATED ("41", "'std.list.reverse'", - "use 'std.ireverse' instead", reverse) +local function reverse (l) return List (base.ireverse (l)) end local function transpose (ls) - local rs, len, dims = List {}, base.len (ls), func.map (base.len, ielems, ls) + local rs, len, dims = List {}, base.len (ls), map (base.len, ielems, ls) if #dims > 0 then for i = 1, math.max (unpack (dims)) do rs[i] = List {} for j = 1, len do - rs[i][j] = ls[j][i] + -- FIXME: the if wrapper is only needed to stop the i index + -- falling through to the metatable[2] index :( + if i <= #ls[j] then rs[i][j] = ls[j][i] end end end end return rs end -M.transpose = DEPRECATED ("41", "'std.list.transpose'", - "use 'std.functional.zip' instead", transpose) - local function zip_with (ls, fn) return map_with (fn, transpose (ls)) end -M.zip_with = DEPRECATED ("41", "'std.list.zip_with'", - "use 'std.functional.zip_with' instead", zip_with) +m.depair = DEPRECATED ("38", "'std.list:depair'", depair) +m.map_with = DEPRECATED ("38", "'std.list:map_with'", + function (self, fn) return map_with (fn, self) end) +m.transpose = DEPRECATED ("38", "'std.list:transpose'", transpose) +m.zip_with = DEPRECATED ("38", "'std.list:zip_with'", zip_with) + + +M.elems = DEPRECATED ("41", "'std.list.elems'", + "use 'std.ielems' instead", base.ielems) +m.elems = DEPRECATED ("41", "'std.list:elems'", + "use 'std.ielems' instead", base.ielems) + +M.flatten = DEPRECATED ("41", "'std.list.flatten'", + "use 'std.functional.flatten' instead", flatten) + +M.foldl = DEPRECATED ("41", "'std.list.foldl'", + "use 'std.functional.foldl' instead", foldl) +m.foldl = DEPRECATED ("41", "'std.list:foldl'", + "use 'std.functional.foldl' instead", + function (self, fn, e) + if e ~= nil then return foldl (fn, e, self) end + return foldl (fn, self) + end) + +M.foldr = DEPRECATED ("41", "'std.list.foldr'", + "use 'std.functional.foldr' instead", foldr) +m.foldr = DEPRECATED ("41", "'std.list:foldr'", + "use 'std.functional.foldr' instead", + function (self, fn, e) + if e ~= nil then return foldr (fn, e, self) end + return foldr (fn, self) + end) + +M.index_key = DEPRECATED ("41", "'std.list.index_key'", + "compose 'std.list.filter' and 'std.table.invert' instead", + index_key) +m.index_key = DEPRECATED ("41", "'std.list:index_key'", + function (self, fn) return index_key (fn, self) end) + + +M.index_value = DEPRECATED ("41", "'std.list.index_value'", + "compose 'std.list.filter' and 'std.table.invert' instead", + index_value) +m.index_value = DEPRECATED ("41", "'std.list:index_value'", + function (self, fn) return index_value (fn, self) end) + + +M.map = DEPRECATED ("41", "'std.list.map'", + "use 'std.functional.map' instead", map) + +M.map_with = DEPRECATED ("41", "'std.list.map_with'", + "use 'std.functional.map_with' instead", map_with) + +M.relems = DEPRECATED ("41", "'std.list.relems'", + "compose 'std.ielems' and 'std.ireverse' instead", relems) +m.relems = DEPRECATED ("41", "'std.list:relems'", relems) +M.reverse = DEPRECATED ("41", "'std.list.reverse'", + "use 'std.ireverse' instead", reverse) +m.reverse = DEPRECATED ("41", "'std.list:reverse'", + "use 'std.ireverse' instead", reverse) + +M.transpose = DEPRECATED ("41", "'std.list.transpose'", + "use 'std.functional.zip' instead", transpose) + +M.zip_with = DEPRECATED ("41", "'std.list.zip_with'", + "use 'std.functional.zip_with' instead", zip_with) + + + +--[[ ================== ]]-- +--[[ Type Declarations. ]]-- +--[[ ================== ]]-- + + +--- An Object derived List. +-- @table List List = Object { -- Derived object type. - _type = "List", - + _type = "List", + _functions = M, + __index = m, ------ -- Concatenate lists. -- new = list .. table -- @function __concat -- @tparam List list a list - -- @tparam table table another list, hash part is ignored + -- @tparam table table another list, hash part is ignored -- @see concat __concat = concat, @@ -444,9 +605,9 @@ List = Object { -- list = list + element -- @function __add -- @tparam List list a list - -- @param element element to append + -- @param element element to append -- @see append - __add = append, + __add = append, ------ -- List order operator. @@ -463,133 +624,6 @@ List = Object { -- @tparam List list2 another list -- @see std.list:compare __le = function (list1, list2) return compare (list1, list2) <= 0 end, - - __index = { - ------ - -- Append an item to a list. - -- @function append - -- @param x item - -- @treturn List new list containing `{self[1], ..., self[#self], x}` - append = append, - - ------ - -- Compare two lists element-by-element, from left-to-right. - -- - -- if a_list:compare (another_list) == 0 then print "same" end - -- @function compare - -- @tparam table l a list - -- @return -1 if `self` is less than `l`, 0 if they are the same, and 1 - -- if `self` is greater than `l` - compare = compare, - - ------ - -- Concatenate arguments into a list. - -- @function concat - -- @param ... tuple of lists - -- @treturn List new list containing - -- `{self[1], ..., self[#self], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` - concat = concat, - - ------ - -- Prepend an item to a list. - -- @function cons - -- @param x item - -- @treturn List new list containing `{x, unpack (self)}` - cons = function (self, x) return M.cons (x, self) end, - - ------ - -- Filter a list according to a predicate. - -- @function filter - -- @func p predicate function, of one argument returning a boolean - -- @treturn List new list containing elements `e` of `self` for which - -- `p (e)` is true - -- @see std.list.filter - filter = function (self, p) return filter (p, self) end, - - ------ - -- Flatten a list. - -- @function flatten - -- @treturn List flattened list - flatten = flatten, - - ------ - -- Map a function over a list. - -- @function map - -- @func fn map function - -- @treturn List new list containing - -- `{fn (self[1]), ..., fn (self[#self])}` - -- @see std.list.map - map = function (self, fn) return map (fn, self) end, - - ------ - -- Project a list of fields from a list of tables. - -- @function project - -- @param f field to project - -- @treturn List list of `f` fields - -- @see std.list.project - project = function (self, f) return project (f, self) end, - - ------ - -- Repeat a list. - -- @function rep - -- @int n number of times to repeat - -- @treturn List `n` copies of `self` appended together - rep = rep, - - ----- - -- Shape a list according to a list of dimensions. - -- @function shape - -- @tparam table s `{d1, ..., dn}` - -- @return reshaped list - -- @see std.list.shape - shape = function (self, s) return shape (s, self) end, - - ------ - -- Return a sub-range of a list. - -- (The equivalent of `string.sub` on strings; negative list indices - -- count from the end of the list.) - -- @function sub - -- @int from start of range (default: 1) - -- @int to end of range (default: `#self`) - -- @treturn List new list containing `{self[from], ..., self[to]}` - sub = sub, - - ------ - -- Return a list with its first element removed. - -- @function tail - -- @treturn List new list containing `{self[2], ..., self[#self]}` - tail = tail, - - ------ - depair = DEPRECATED ("38", "'std.list:depair'", depair), - map_with = DEPRECATED ("38", "'std.list:map_with'", - function (self, fn) return map_with (fn, self) end), - transpose = DEPRECATED ("38", "'std.list:transpose'", transpose), - zip_with = DEPRECATED ("38", "'std.list:zip_with'", zip_with), - - elems = DEPRECATED ("41", "'std.list:elems'", base.ielems), - foldl = DEPRECATED ("41", "'std.list:foldl'", - "use 'std.functional.foldl' instead", - function (self, fn, e) - if e ~= nil then return foldl (fn, e, self) end - return foldl (fn, self) - end), - foldr = DEPRECATED ("41", "'std.list:foldr'", - "use 'std.functional.foldr' instead", - function (self, fn, e) - if e ~= nil then return foldr (fn, e, self) end - return foldr (fn, self) - end), - index_key = DEPRECATED ("41", "'std.list:index_key'", - function (self, fn) return index_key (fn, self) end), - index_value = DEPRECATED ("41", "'std.list:index_value'", - function (self, fn) return index_value (fn, self) end), - relems = DEPRECATED ("41", "'std.list:relems'", relems), - reverse = DEPRECATED ("41", "'std.list:reverse'", reverse), - }, - - - _functions = M, } diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 76dc893..59e5584 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -50,14 +50,14 @@ specify std.list: # List {args} is just syntactic sugar for List:clone {args} - context from List object prototype: - - it constructs a new list: + - it constructs a new List: l = List {} expect (l).not_to_be (List) expect (prototype (l)).to_be "List" - it reuses the List metatable: l, m = List {"l"}, List {"m"} expect (getmetatable (l)).to_be (getmetatable (m)) - - it initialises list with constructor parameters: + - it initialises List with constructor parameters: m = List {"foo", "bar", "baz"} expect (m).to_equal (l) - it serves as a prototype for new instances: @@ -75,33 +75,53 @@ specify std.list: - describe append: - before: - f, badarg = init (M, "append") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 42, false)).to_raise (badarg (3)) - - - context when called as a list object method: - - it returns a list object: - l = l:append ("quux") - expect (prototype (l)).to_be "List" - - it works for an empty list: - l = List {} - expect (l:append ("quux")).to_equal (List {"quux"}) - - it appends an item to a list: - expect (l:append ("quux")). + fname = "append" + + - context as a module function: + - before: + f, badarg = init (M, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "any value")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 42, false)).to_raise (badarg (3)) + + - it returns a List object: + expect (prototype (f (l, "quux"))).to_be "List" + - it works for an empty List: + expect (f (List {}, "quux")).to_equal (List {"quux"}) + - it appends an item to a List: + expect (f (l, "quux")). to_equal (List {"foo", "bar", "baz", "quux"}) - - context when called as a list metamethod: - - it returns a list object: - l = l + "quux" - expect (prototype (l)).to_be "List" + + - context as an object method: + - before: + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + expect (f (l)).to_raise (badarg (1, "any value")) + - it diagnoses wrong argument types: + expect (f ({"foo", "bar"})).to_raise (badarg (0, "List", "table")) + - it diagnoses too many arguments: + expect (f (l, "bar", false)).to_raise (badarg (2)) + + - it returns a List object: + expect (prototype (f (l, "quux"))).to_be "List" + - it works for an empty List: + expect (f (List {}, "quux")).to_equal (List {"quux"}) + - it appends an item to a List: + expect (f (l, "quux")). + to_equal (List {"foo", "bar", "baz", "quux"}) + + - context as a List metamethod: + - it returns a List object: + expect (prototype (l + "quux")).to_be "List" - it works for an empty list: - l = List {} - expect (l + "quux").to_equal (List {"quux"}) + expect (List {} + "quux").to_equal (List {"quux"}) - it appends an item to a list: expect (l + "quux"). to_equal (List {"foo", "bar", "baz", "quux"}) @@ -111,38 +131,74 @@ specify std.list: - before: a, b = List {"foo", "bar"}, List {"foo", "baz"} - f, badarg = init (M, "compare") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (a, false)).to_raise (badarg (2, "List or table", "boolean")) - - it diagnoses too many arguments: - expect (f (a, b, false)).to_raise (badarg (3)) - - - context when called as a list object method: - - it returns -1 when the first list is less than the second: | - expect (a:compare {"foo", "baz"}).to_be (-1) - expect (a:compare (List {"foo", "baz"})).to_be (-1) + fname = "compare" + + - context as a module function: + - before: + f, badarg = init (M, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (a, false)).to_raise (badarg (2, "List or table", "boolean")) + - it diagnoses too many arguments: + expect (f (a, b, false)).to_raise (badarg (3)) + + - it returns -1 when the first list is less than the second: + expect (f (a, {"foo", "baz"})).to_be (-1) + expect (f (a, List {"foo", "baz"})).to_be (-1) + - it returns -1 when the second list has additional elements: + expect (f (List {"foo"}, {"foo", "bar"})).to_be (-1) + expect (f (List {"foo"}, List {"foo", "bar"})).to_be (-1) + - it returns 0 when two lists are the same: + expect (f (a, {"foo", "bar"})).to_be (0) + expect (f (a, List {"foo", "bar"})).to_be (0) + - it returns +1 when the first list is greater than the second: + expect (f (a, {"baz", "quux"})).to_be (1) + expect (f (a, List {"baz", "quux"})).to_be (1) + - it returns +1 when the first list has additional elements: + expect (f (a, {"foo"})).to_be (1) + expect (f (a, List {"foo"})).to_be (1) + - it compares numerically when both arguments can be coerced: + a, b = List {"1", "2", "3"}, List {"1", "2", "10"} + expect (f (a, b)).to_be (-1) + + - context as an object method: + - before: + f, _, badarg = a[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + expect (f (l)).to_raise (badarg (1, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + expect (f (a, false)).to_raise (badarg (1, "List or table", "boolean")) + - it diagnoses too many arguments: + expect (f (a, b, false)).to_raise (badarg (2)) + + - it returns -1 when the first list is less than the second: + expect (f (a, {"foo", "baz"})).to_be (-1) + expect (f (a, List {"foo", "baz"})).to_be (-1) - it returns -1 when the second list has additional elements: | b = List {"foo"} - expect (b:compare {"foo", "bar"}).to_be (-1) + expect (f (b, {"foo", "bar"})).to_be (-1) expect (List {"foo"}:compare (List {"foo", "bar"})).to_be (-1) - - it returns 0 when two lists are the same: | - expect (a:compare {"foo", "bar"}).to_be (0) - expect (a:compare (List {"foo", "bar"})).to_be (0) - - it returns +1 when the first list is greater than the second: | - expect (a:compare {"baz", "quux"}).to_be (1) - expect (a:compare (List {"baz", "quux"})).to_be (1) - - it returns +1 when the first list has additional elements: | - expect (a:compare {"foo"}).to_be (1) - expect (a:compare (List {"foo"})).to_be (1) + - it returns 0 when two lists are the same: + expect (f (a, {"foo", "bar"})).to_be (0) + expect (f (a, List {"foo", "bar"})).to_be (0) + - it returns +1 when the first list is greater than the second: + expect (f (a, {"baz", "quux"})).to_be (1) + expect (f (a, List {"baz", "quux"})).to_be (1) + - it returns +1 when the first list has additional elements: + expect (f (a, {"foo"})).to_be (1) + expect (f (a, List {"foo"})).to_be (1) - it compares numerically when both arguments can be coerced: a, b = List {"1", "2", "3"}, List {"1", "2", "10"} - expect (a:compare (b)).to_be (-1) - - context when called as a '<' list metamethod: + expect (f (a, b)).to_be (-1) + + - context as a '<' List metamethod: - it succeeds when the first list is less than the second: expect (a < b).to_be (true) - it fails when the first list is not less than the second: @@ -151,7 +207,8 @@ specify std.list: - it compares numerically when both arguments can be coerced: a, b = List {"1", "2", "3"}, List {"1", "2", "10"} expect (a < b).to_be (true) - - context when called as a '>' list metamethod: + + - context as a '>' List metamethod: - it succeeds when the first list is greater than the second: expect (b > a).to_be (true) - it fails when the first list is not greater than the second: @@ -160,7 +217,8 @@ specify std.list: - it compares numerically when both arguments can be coerced: a, b = List {"1", "2", "3"}, List {"1", "2", "10"} expect (a > b).to_be (false) - - context when called as a '<=' list metamethod: + + - context as a '<=' List metamethod: - it succeeds when the first list is less than or equal to the second: expect (a <= b).to_be (true) expect (a <= a).to_be (true) @@ -169,7 +227,8 @@ specify std.list: - it compares numerically when both arguments can be coerced: a, b = List {"1", "2", "3"}, List {"1", "2", "10"} expect (a <= b).to_be (true) - - context when called as a '>=' list metamethod: + + - context as a '>=' List metamethod: - it succeeds when the first list is greater than or equal to the second: expect (b >= a).to_be (true) expect (b >= b).to_be (true) @@ -184,39 +243,78 @@ specify std.list: - before: l = List {"foo", "bar"} - f, badarg = init (M, "concat") + fname = "concat" - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "List or table", "boolean")) - expect (f (l, l, false)). - to_raise (badarg (3, "List or table", "boolean")) + - context as a module function: + - before: + f, badarg = init (M, fname) - - context when called as a list object method: - - it returns a list object: - l = l:concat (List {"baz"}) - expect (prototype (l)).to_be "List" - - it works for an empty list: - l = List {} - expect (l:concat (List {"baz"})).to_equal (List {"baz"}) - - it concatenates lists: - expect (l:concat (List {"baz", "quux"})). + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "List or table", "boolean")) + expect (f (l, l, false)). + to_raise (badarg (3, "List or table", "boolean")) + + - it returns a List object: + expect (prototype (f (l, l))).to_be "List" + - it works for an empty List: + expect (f (List {}, {"baz"})).to_equal (List {"baz"}) + expect (f (List {}, List {"baz"})).to_equal (List {"baz"}) + - it concatenates Lists: + expect (f (l, {"baz", "quux"})). to_equal (List {"foo", "bar", "baz", "quux"}) - expect (l:concat (List {"baz"}, List {"quux"})). + expect (f (l, List {"baz", "quux"})). + to_equal (List {"foo", "bar", "baz", "quux"}) + expect (f (l, {"baz"}, {"quux"})). + to_equal (List {"foo", "bar", "baz", "quux"}) + expect (f (l, List {"baz"}, List {"quux"})). + to_equal (List {"foo", "bar", "baz", "quux"}) + + - context as an object method: + - before: + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + expect (f (l)).to_raise (badarg (1, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (1, "List or table", "boolean")) + expect (f (l, l, false)). + to_raise (badarg (2, "List or table", "boolean")) + + - it returns a List object: + expect (prototype (f (l, l))).to_be "List" + - it works for an empty List: + expect (f (List {}, {"baz"})).to_equal (List {"baz"}) + expect (f (List {}, List {"baz"})).to_equal (List {"baz"}) + - it concatenates Lists: + expect (f (l, {"baz", "quux"})). + to_equal (List {"foo", "bar", "baz", "quux"}) + expect (f (l, List {"baz", "quux"})). + to_equal (List {"foo", "bar", "baz", "quux"}) + expect (f (l, {"baz"}, {"quux"})). + to_equal (List {"foo", "bar", "baz", "quux"}) + expect (f (l, List {"baz"}, List {"quux"})). + to_equal (List {"foo", "bar", "baz", "quux"}) + + # Beware that .. operations are right associative + - context as a List metamethod: + - it returns a List object: + expect (prototype (l .. List {"baz"})).to_be "List" + - it works for an empty List: + expect (List {} .. {"baz"}).to_equal (List {"baz"}) + expect (List {} .. List {"baz"}).to_equal (List {"baz"}) + - it concatenates Lists: + expect (l .. {"baz", "quux"}). to_equal (List {"foo", "bar", "baz", "quux"}) - - context when called as a list metamethod: - - it returns a list object: - l = l .. List {"baz"} - expect (prototype (l)).to_be "List" - - it works for an empty list: - l = List {} - expect (l .. List {"baz"}).to_equal (List {"baz"}) - - it concatenates lists: expect (l .. List {"baz", "quux"}). to_equal (List {"foo", "bar", "baz", "quux"}) + expect ({"baz"} .. {"quux"} .. l). + to_equal (List {"baz", "quux", "foo", "bar"}) expect (l .. List {"baz"} .. List {"quux"}). to_equal (List {"foo", "bar", "baz", "quux"}) @@ -236,31 +334,38 @@ specify std.list: - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "any value")) - it diagnoses wrong argument types: - expect (f ("head", false)).to_raise (badarg (2, "List or nil", "boolean")) + expect (f ("x", false)).to_raise (badarg (2, "List or nil", "boolean")) + - it diagnoses too many arguments: + expect (f ("x", List {}, false)).to_raise (badarg (3)) - - it returns a list object: + - it returns a List object: expect (prototype (f ("head", l))).to_be "List" - - it prepends an item to a list: + - it prepends an item to a List: expect (f ("head", l)). to_equal (List {"head", "foo", "bar", "baz"}) - - it works for empty lists: + - it works for empty Lists: l = List {} expect (f ("head", l)).to_equal (List {"head"}) - it supports single argument call: expect (f "head").to_equal (List {"head"}) - - context as a list object method: + - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - it returns a list object: - expect (prototype (f (l, "head"))).to_be "List" - - it prepends an item to a list: - expect (f (l, "head")). - to_equal (List {"head", "foo", "bar", "baz"}) - - it works for empty lists: - l = List {} - expect (f (l, "head")).to_equal (List {"head"}) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, "x", false)).to_raise (badarg (2)) + + - it returns a List object: + expect (prototype (f (l, "x"))).to_be "List" + - it prepends an item to a List: + expect (f (l, "x")).to_equal (List {"x", "foo", "bar", "baz"}) + - it works for empty Lists: + expect (f (List {}, "x")).to_equal (List {"x"}) - describe depair: @@ -289,27 +394,26 @@ specify std.list: - it returns a primitive table: expect (prototype (f (l))).to_be "table" - - it works with an empty list: + - it works with an empty List: l = List {} expect (f (l)).to_equal {} - it is the inverse of enpair: expect (f (l)).to_equal (t) - - context as a list object method: + - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a primitive table: - expect (prototype (l:depair ())).to_be "table" - - it works with an empty list: - l = List {} - expect (l:depair ()).to_equal {} + expect (prototype (f (l))).to_be "table" + - it works with an empty List: + expect (f (List {})).to_equal {} - it is the inverse of enpair: - expect (l:depair ()).to_equal (t) + expect (f (l)).to_equal (t) - describe elems: @@ -318,34 +422,34 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {{}})).to_contain_error "was deprecated" expect (capture (f, {{}})).not_to_contain_error "was deprecated" - - it is an iterator over list members: + - it is an iterator over List members: t = {} for e in f (l) do table.insert (t, e) end expect (t).to_equal {"foo", "bar", "baz"} - - it works for an empty list: + - it works for an empty List: t = {} for e in f (List {}) do table.insert (t, e) end expect (t).to_equal {} - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" expect (capture (f, {l})).not_to_contain_error "was deprecated" - - it is an iterator over list members: + - it is an iterator over List members: t = {} for e in l:elems () do table.insert (t, e) end expect (t).to_equal {"foo", "bar", "baz"} - - it works for an empty list: + - it works for an empty List: t, l = {}, List {} for e in l:elems () do table.insert (t, e) end expect (t).to_equal {} @@ -360,7 +464,6 @@ specify std.list: - context as a module function: - before: f, badarg = init (M, fname) - - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) - it diagnoses wrong argument types: @@ -368,11 +471,11 @@ specify std.list: - it diagnoses too many arguments: expect (f ({}, false)).to_raise (badarg (2)) - - it returns a list object: + - it returns a List object: expect (prototype (f (t))).to_be "List" - it works for an empty table: expect (f {}).to_equal (List {}) - - it turns a table into a list of pairs: + - it turns a table into a List of pairs: expect (f (t)). to_equal (List {List {1, "first"}, List {2, "second"}, List {"third", 4}}) @@ -382,26 +485,47 @@ specify std.list: l = List {"foo", "bar", "baz", "quux"} p = function (e) return (e:match "a" ~= nil) end - f, badarg = init (M, "filter") + fname = "filter" - - it diagnoses missing arguments: | - expect (f ()).to_raise (badarg (1, "function")) - expect (f (p)).to_raise (badarg (2, "List")) - - it diagnoses wrong argument types: | - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (p, false)).to_raise (badarg (2, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (p, l, false)).to_raise (badarg (3)) + - context as a module function: + - before: + f, badarg = init (M, fname) - - context when called as a list object method: - - it returns a list object: - m = l:filter (p) - expect (prototype (m)).to_be "List" - - it works for an empty list: - l = List {} - expect (l:filter (p)).to_equal (List {}) - - it filters a list according to a predicate: - expect (l:filter (p)).to_equal (List {"bar", "baz"}) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "function")) + expect (f (p)).to_raise (badarg (2, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "function", "boolean")) + expect (f (p, false)).to_raise (badarg (2, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (p, l, false)).to_raise (badarg (3)) + + - it returns a List object: + expect (prototype (f (p, l))).to_be "List" + - it works for an empty List: + expect (f (p, List {})).to_equal (List {}) + - it filters a List according to a predicate: + expect (f (p, l)).to_equal (List {"bar", "baz"}) + + - context as an object method: + - before: + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + expect (f (l)).to_raise (badarg (1, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (1, "function", "boolean")) + - it diagnoses too many arguments: + expect (f (l, p, false)).to_raise (badarg (2)) + + - it returns a List object: + expect (prototype (f (l, p))).to_be "List" + - it works for an empty List: + expect (f (List {}, p)).to_equal (List {}) + - it filters a List according to a predicate: + expect (f (l, p)).to_equal (List {"bar", "baz"}) - describe flatten: @@ -412,31 +536,38 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" expect (capture (f, {l})).not_to_contain_error "was deprecated" - - it returns a list object: + - it returns a List object: expect (prototype (f (l))).to_be "List" - - it works for an empty list: + - it works for an empty List: l = List {} expect (f (l)).to_equal (List {}) - - it flattens a list: + - it flattens a List: expect (f (l)). to_equal (List {"one", "two", "three", "four"}) - - context as a List object method: + - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, false)).to_raise (badarg (1)) - - it returns a list object: + - it returns a List object: expect (prototype (f (l))).to_be "List" - - it works for an empty list: + - it works for an empty List: l = List {} expect (f (l)).to_equal (List {}) - - it flattens a list: + - it flattens a List: expect (f (l)). to_equal (List {"one", "two", "three", "four"}) @@ -449,7 +580,7 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {op["+"], 1, {10}})). @@ -474,10 +605,11 @@ specify std.list: - it folds from left to right: expect (f (op["^"], 2, List {3, 4})).to_be ((2 ^ 3) ^ 4) - - context as a List object method: + - context as an object method: - before: l = List {3, 4} - f = l[fname] + + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l, op["+"], 1})). @@ -485,10 +617,10 @@ specify std.list: expect (capture (f, {l, op["+"], 1})). not_to_contain_error "was deprecated" - - it works with an empty list: + - it works with an empty List: l = List {} expect (f (l, op["+"], 2)).to_be (2) - - it folds a binary function through a list: + - it folds a binary function through a List: expect (f (l, op["+"], 2)).to_be (9) - it folds from left to right: expect (f (l, op["^"], 2)).to_be ((2 ^ 3) ^ 4) @@ -502,7 +634,7 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {op["+"], 1, {10}})). @@ -528,10 +660,11 @@ specify std.list: expect (f (op["/"], 10, List {10000, 100})). to_be (10000 / (100 / 10)) - - context as a List object method: + - context as an object method: - before: l = List {10000, 100} - f = l[fname] + + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l, op["+"], 1})). @@ -539,10 +672,10 @@ specify std.list: expect (capture (f, {l, op["+"], 1})). not_to_contain_error "was deprecated" - - it works with an empty list: + - it works with an empty List: l = List {} expect (f (l, op["+"], 10)).to_be (10) - - it folds a binary function through a list: + - it folds a binary function through a List: expect (f (l, op["+"], 10)).to_be (10110) - it folds from right to left: expect (f (l, op["/"], 10)).to_be (10000 / (100 / 10)) @@ -554,7 +687,7 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {1, List {{1}}})). @@ -562,14 +695,14 @@ specify std.list: expect (capture (f, {1, List {{1}}})). not_to_contain_error "was deprecated" - - it makes a map of matched table field values to table list offsets: + - it makes a map of matched table field values to table List offsets: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} t = f ("a", l) expect (t).to_equal {b = 1, x = 3} for k, v in pairs (t) do expect (k).to_equal (l[v]["a"]) end - - it captures only the last matching list offset: + - it captures only the last matching List offset: l = List {{a = "b"}, {a = "x"}, {a = "b"}} t = f ("a", l) expect (t.b).not_to_be (1) @@ -583,20 +716,20 @@ specify std.list: - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l, 1})).to_contain_error "was deprecated" expect (capture (f, {l, 1})).not_to_contain_error "was deprecated" - - it makes a map of matched table field values to table list offsets: + - it makes a map of matched table field values to table List offsets: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} t = l:index_key "a" expect (t).to_equal {b = 1, x = 3} for k, v in pairs (t) do expect (k).to_equal (l[v]["a"]) end - - it captures only the last matching list offset: + - it captures only the last matching List offset: l = List {{a = "b"}, {a = "x"}, {a = "b"}} t = l:index_key "a" expect (t.b).not_to_be (1) @@ -615,7 +748,7 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {1, List {{1}}})). @@ -623,14 +756,14 @@ specify std.list: expect (capture (f, {1, List {{1}}})). not_to_contain_error "was deprecated" - - it makes a table of matched table field values to table list references: + - it makes a table of matched table field values to table List references: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} t = f ("a", l) expect (t).to_equal {b = l[1], x = l[3]} for k, v in pairs (t) do expect (k).to_equal (v["a"]) end - - it captures only the last matching list offset: + - it captures only the last matching List offset: l = List {{a = "b"}, {a = "x"}, {a = "b"}} t = f ("a", l) expect (t.b).not_to_be (l[1]) @@ -645,20 +778,21 @@ specify std.list: - context as an object method: - before: l = List {{1}} - f = l[fname] + + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l, 1})).to_contain_error "was deprecated" expect (capture (f, {l, 1})).not_to_contain_error "was deprecated" - - it makes a table of matched table field values to table list references: + - it makes a table of matched table field values to table List references: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} t = l:index_value "a" expect (t).to_equal {b = l[1], x = l[3]} for k, v in pairs (t) do expect (k).to_equal (v["a"]) end - - it captures only the last matching list offset: + - it captures only the last matching List offset: l = List {{a = "b"}, {a = "x"}, {a = "b"}} t = l:index_value "a" expect (t.b).not_to_be (l[1]) @@ -680,44 +814,50 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {sq, l})).to_contain_error "was deprecated" expect (capture (f, {sq, l})).not_to_contain_error "was deprecated" - - it returns a list object: - m = f (sq, l) - expect (prototype (m)).to_be "List" - - it works for an empty list: - l = List {} - expect (f (sq, l)).to_equal (List {}) - - it creates a new list: + - it returns a List object: + expect (prototype (f (sq, l))).to_be "List" + - it works for an empty List: + expect (f (sq, List {})).to_equal (List {}) + - it creates a new List: o = l m = f (sq, l) expect (l).to_equal (o) expect (m).not_to_equal (o) expect (l).to_equal (List {1, 2, 3, 4, 5}) - - it maps a function over a list: + - it maps a function over a List: expect (f (sq, l)).to_equal (List {1, 4, 9, 16, 25}) - - context as a list object method: + - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + expect (f (l)).to_raise (badarg (1, "function")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (1, "function", "boolean")) + - it diagnoses too many arguments: + expect (f (l, sq, false)).to_raise (badarg (2)) - - it returns a list object: + - it returns a List object: m = f (l, sq) expect (prototype (m)).to_be "List" - - it works for an empty list: - l = List {} - expect (f (l, sq)).to_equal (List {}) - - it creates a new list: + - it works for an empty List: + expect (f (List {}, sq)).to_equal (List {}) + - it creates a new List: o = l m = f (l, sq) expect (l).to_equal (o) expect (m).not_to_equal (o) expect (l).to_equal (List {1, 2, 3, 4, 5}) - - it maps a function over a list: + - it maps a function over a List: expect (f (l, sq)).to_equal (List {1, 4, 9, 16, 25}) @@ -730,47 +870,47 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {fn, l})).to_contain_error "was deprecated" expect (capture (f, {fn, l})).not_to_contain_error "was deprecated" - - it returns a list object: + - it returns a List object: m = f (fn, l) expect (prototype (m)).to_be "List" - - it creates a new list: + - it creates a new List: o = l m = f (fn, l) expect (l).to_equal (o) expect (m).not_to_equal (o) expect (l).to_equal (List {List {1, 2, 3}, List {4, 5}}) - - it maps a function over a list: + - it maps a function over a List: expect (f (fn, l)).to_equal (List {3, 2}) - - it works for an empty list: + - it works for an empty List: l = List {} expect (f (fn, l)).to_equal (List {}) - - context when called as a list object method: + - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l, fn})).to_contain_error "was deprecated" expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - - it returns a list object: + - it returns a List object: m = f (l, fn) expect (prototype (m)).to_be "List" - - it creates a new list: + - it creates a new List: o = l m = f (l, fn) expect (l).to_equal (o) expect (m).not_to_equal (o) expect (l).to_equal (List {List {1, 2, 3}, List {4, 5}}) - - it maps a function over a list: + - it maps a function over a List: expect (f (l, fn)).to_equal (List {3, 2}) - - it works for an empty list: + - it works for an empty List: l = List {} expect (f (l, fn)).to_equal (List {}) @@ -783,33 +923,57 @@ specify std.list: {first = "1st", second = "2nd", third = "3rd"}, } - f, badarg = init (M, "project") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "any value")) - expect (f (f)).to_raise (badarg (2, "List")) - - it diagnoses wrong argument types: - expect (f (f, false)).to_raise (badarg (2, "List", "boolean")) - expect (f (f, List {false})). - to_raise (badarg (2, "List of tables", "boolean at index 1")) - expect (f (f, List {{}, false})). - to_raise (badarg (2, "List of tables", "boolean at index 2")) - - it diagnoses too many arguments: - expect (f (f, l, false)).to_raise (badarg (3)) - - - context when called as a list object method: - - it returns a list object: - p = l:project ("third") - expect (prototype (p)).to_be "List" - - it works with an empty list: - l = List {} - expect (l:project ("third")).to_equal (List {}) - - it projects a list of fields from a list of tables: - expect (l:project ("third")). - to_equal (List {true, 3, "3rd"}) + fname = "project" + + - context as a module function: + - before: + f, badarg = init (M, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "any value")) + expect (f ("x")).to_raise (badarg (2, "List")) + - it diagnoses wrong argument types: + expect (f ("x", false)).to_raise (badarg (2, "List", "boolean")) + expect (f ("x", List {false})). + to_raise (badarg (2, "List of tables", "boolean at index 1")) + expect (f ("x", List {{}, false})). + to_raise (badarg (2, "List of tables", "boolean at index 2")) + - it diagnoses too many arguments: + expect (f ("x", l, false)).to_raise (badarg (3)) + + - it returns a List object: + expect (prototype (f ("third", l))).to_be "List" + - it works with an empty List: + expect (f ("third", List {})).to_equal (List {}) + - it projects a List of fields from a List of tables: + expect (f ("third", l)).to_equal (List {true, 3, "3rd"}) - it projects fields with a falsey value correctly: - expect (l:project ("first")). - to_equal (List {false, 1, "1st"}) + expect (f ("first", l)).to_equal (List {false, 1, "1st"}) + + - context as an object method: + - before: + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + expect (f (l)).to_raise (badarg (1, "any value")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + expect (f (List {false}, "x")). + to_raise (badarg (0, "List of tables", "boolean at index 1")) + expect (f (List {{}, false}, "x")). + to_raise (badarg (0, "List of tables", "boolean at index 2")) + - it diagnoses too many arguments: + expect (f (l, "x", false)).to_raise (badarg (2)) + + - it returns a List object: + expect (prototype (f (l, "third"))).to_be "List" + - it works with an empty List: + expect (f (List {}, "third")).to_equal (List {}) + - it projects a List of fields from a List of tables: + expect (f (l, "third")).to_equal (List {true, 3, "3rd"}) + - it projects fields with a falsey value correctly: + expect (f (l, "first")).to_equal (List {false, 1, "1st"}) - describe relems: @@ -818,63 +982,85 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" expect (capture (f, {l})).not_to_contain_error "was deprecated" - - it is a reverse iterator over list members: + - it is a reverse iterator over List members: t = {} for e in f (l) do table.insert (t, e) end expect (t).to_equal {"baz", "bar", "foo"} - - it works for an empty list: + - it works for an empty List: t = {} for e in f (List {}) do table.insert (t, e) end expect (t).to_equal {} - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" expect (capture (f, {l})).not_to_contain_error "was deprecated" - - it is a reverse iterator over list members: + - it is a reverse iterator over List members: t = {} for e in l:relems () do table.insert (t, e) end expect (t).to_equal {"baz", "bar", "foo"} - - it works for an empty list: + - it works for an empty List: t, l = {}, List {} for e in l:relems () do table.insert (t, e) end expect (t).to_equal {} - - describe rep: - before: l = List {"foo", "bar"} - f, badarg = init (M, "rep") + fname = "rep" - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "int", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 2, false)).to_raise (badarg (3)) + - context as a module function: + - before: + f, badarg = init (M, fname) - - context when called as a list object method: - - it returns a list object: - expect (prototype (l:rep (3))).to_be "List" - - it works for an empty list: - l = List {} - expect (l:rep (99)).to_equal (List {}) - - it repeats the contents of a list: - expect (l:rep (3)). + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "int")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "int", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 2, false)).to_raise (badarg (3)) + + - it returns a List object: + expect (prototype (f (l, 3))).to_be "List" + - it works for an empty List: + expect (f (List {}, 99)).to_equal (List {}) + - it repeats the contents of a List: + expect (f (l, 3)). + to_equal (List {"foo", "bar", "foo", "bar", "foo", "bar"}) + + - context as an object method: + - before: + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + expect (f (l)).to_raise (badarg (1, "int")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (1, "int", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 2, false)).to_raise (badarg (2)) + + - it returns a List object: + expect (prototype (f (l, 3))).to_be "List" + - it works for an empty List: + expect (f (List {}, 99)).to_equal (List {}) + - it repeats the contents of a List: + expect (f (l, 3)). to_equal (List {"foo", "bar", "foo", "bar", "foo", "bar"}) @@ -886,40 +1072,39 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {{}})).to_contain_error "was deprecated" expect (capture (f, {{}})).not_to_contain_error "was deprecated" - - it returns a list object: + - it returns a List object: expect (prototype (f (l))).to_be "List" - - it works for an empty list: + - it works for an empty List: l = List {} expect (f (l)).to_equal (List {}) - - it makes a new reversed list: + - it makes a new reversed List: m = l expect (f (l)). to_equal (List {"quux", "baz", "bar", "foo"}) expect (l).to_equal (List {"foo", "bar", "baz", "quux"}) expect (l).to_be (m) - - context when called as a list object method: + - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" expect (capture (f, {l})).not_to_contain_error "was deprecated" - - it returns a list object: - expect (prototype (l:reverse ())).to_be "List" - - it works for an empty list: - l = List {} - expect (l:reverse ()).to_equal (List {}) - - it makes a new reversed list: + - it returns a List object: + expect (prototype (f (l))).to_be "List" + - it works for an empty List: + expect (f (List {})).to_equal (List {}) + - it makes a new reversed List: m = l - expect (l:reverse ()). + expect (f (l)). to_equal (List {"quux", "baz", "bar", "foo"}) expect (l).to_equal (List {"foo", "bar", "baz", "quux"}) expect (l).to_be (m) @@ -929,139 +1114,235 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6} - f, badarg = init (M, "shape") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - expect (f ({})).to_raise (badarg (2, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_raise (badarg (2, "List", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, l, false)).to_raise (badarg (3)) - - - context when called as a list object method: - - it returns a list object: | - expect (prototype (l:shape {2, 3})).to_be "List" - - it returns the result in a new list object: | - expect (l:shape {2, 3}):not_to_be (l) - - it does not perturb the argument list: | - m = l:shape {2, 3} + fname = "shape" + + - context as a module function: + - before: + f, badarg = init (M, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + expect (f ({})).to_raise (badarg (2, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_raise (badarg (2, "List", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, l, false)).to_raise (badarg (3)) + + - it returns a List object: + expect (prototype (f ({2, 3}, l))).to_be "List" + - it works for an empty List: + expect (f ({0}, List {})).to_equal (List {}) + - it returns the result in a new List object: + expect (f ({2, 3}, l)).not_to_be (l) + - it does not perturb the argument List: + f ({2, 3}, l) expect (l).to_equal (List {1, 2, 3, 4, 5, 6}) - - it reshapes a list according to given dimensions: | - expect (l:shape {2, 3}). + - it reshapes a List according to given dimensions: + expect (f ({2, 3}, l)). to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) - expect (l:shape {3, 2}). + expect (f ({3, 2}, l)). to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - it treats 0-valued dimensions as an indefinite number: - expect (l:shape {2, 0}). + expect (f ({2, 0}, l)). to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) - expect (l:shape {0, 2}). + expect (f ({0, 2}, l)). + to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) + + - context as an object method: + - before: + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + expect (f (l)).to_raise (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + expect (f (List {}, false)).to_raise (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f (l, {}, false)).to_raise (badarg (2)) + + - it returns a List object: + expect (prototype (f (l, {2, 3}))).to_be "List" + - it works for an empty List: + expect (f (List {}, {0})).to_equal (List {}) + - it returns the result in a new List object: + expect (f (l, {2, 3})):not_to_be (l) + - it does not perturb the argument List: + f (l, {2, 3}) + expect (l).to_equal (List {1, 2, 3, 4, 5, 6}) + - it reshapes a List according to given dimensions: + expect (f (l, {2, 3})). + to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) + expect (f (l, {3, 2})). + to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) + - it treats 0-valued dimensions as an indefinite number: + expect (f (l, {2, 0})). + to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) + expect (f (l, {0, 2})). to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - - it works for an empty list: | - l = List {} - expect (l:shape {0}).to_equal (List {}) - describe sub: - before: l = List {1, 2, 3, 4, 5, 6, 7} - f, badarg = init (M, "sub") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "int or nil", "boolean")) - expect (f (l, 1, false)).to_raise (badarg (3, "int or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 1, 2, false)).to_raise (badarg (4)) - - - context when called as a list object method: - - it returns a list object: | - expect (prototype (l:sub (1, 1))).to_be "List" - - it makes a list from a subrange of another list: | - expect (l:sub (2, 5)).to_equal (List {2, 3, 4, 5}) - - it truncates the result if 'to' argument is too large: | - expect (l:sub (5, 10)).to_equal (List {5, 6, 7}) - - it defaults 'to' to the end of the list: | - expect (l:sub (5)).to_equal (List {5, 6, 7}) - - it defaults 'from' to the beginning of the list: | - expect (l:sub ()).to_equal (l) - - it returns an empty list when 'from' is greater than 'to': | - expect (l:sub (2, 1)).to_equal (List {}) - - it counts from the end of the list for a negative 'from' argument: | - expect (l:sub (-3)).to_equal (List {5, 6, 7}) - - it counts from the end of the list for a negative 'to' argument: | - expect (l:sub (-5, -2)).to_equal (List {3, 4, 5, 6}) + fname = "sub" + + - context as a module function: + - before: + f, badarg = init (M, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "int or nil", "boolean")) + expect (f (l, 1, false)).to_raise (badarg (3, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 1, 2, false)).to_raise (badarg (4)) + + - it returns a List object: + expect (prototype (f (l, 1, 1))).to_be "List" + - it makes a List from a subrange of another List: + expect (f (l, 2, 5)).to_equal (List {2, 3, 4, 5}) + - it truncates the result if 'to' argument is too large: + expect (f (l, 5, 10)).to_equal (List {5, 6, 7}) + - it defaults 'to' to the end of the List: + expect (f (l, 5)).to_equal (List {5, 6, 7}) + - it defaults 'from' to the beginning of the List: + expect (f (l)).to_equal (l) + - it returns an empty List when 'from' is greater than 'to': + expect (f (l, 2, 1)).to_equal (List {}) + - it counts from the end of the List for a negative 'from' argument: + expect (f (l, -3)).to_equal (List {5, 6, 7}) + - it counts from the end of the List for a negative 'to' argument: + expect (f (l, -5, -2)).to_equal (List {3, 4, 5, 6}) + + - context as an object method: + - before: + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (1, "int or nil", "boolean")) + expect (f (l, 1, false)).to_raise (badarg (2, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 1, 2, false)).to_raise (badarg (3)) + + - it returns a List object: + expect (prototype (f (l, 1, 1))).to_be "List" + - it makes a List from a subrange of another List: + expect (f (l, 2, 5)).to_equal (List {2, 3, 4, 5}) + - it truncates the result if 'to' argument is too large: + expect (f (l, 5, 10)).to_equal (List {5, 6, 7}) + - it defaults 'to' to the end of the List: + expect (f (l, 5)).to_equal (List {5, 6, 7}) + - it defaults 'from' to the beginning of the List: + expect (f (l)).to_equal (l) + - it returns an empty List when 'from' is greater than 'to': + expect (f (l, 2, 1)).to_equal (List {}) + - it counts from the end of the List for a negative 'from' argument: + expect (f (l, -3)).to_equal (List {5, 6, 7}) + - it counts from the end of the List for a negative 'to' argument: + expect (f (l, -5, -2)).to_equal (List {3, 4, 5, 6}) - describe tail: - before: l = List {1, 2, 3, 4, 5, 6, 7} - f, badarg = init (M, "tail") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, false)).to_raise (badarg (2)) - - - context when called as a list object method: - - it returns a list object: | - expect (prototype (l:tail ())).to_be "List" - - it makes a new list with the first element removed: | - expect (l:tail ()).to_equal (List {2, 3, 4, 5, 6, 7}) - - it works for an empty list: | - l = List {} - expect (l:tail ()).to_equal (List {}) - - it returns an empty list when passed a list with one element: | - l = List {1} - expect (l:tail ()).to_equal (List {}) + fname = "tail" + + - context as a module function: + - before: + f, badarg = init (M, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, false)).to_raise (badarg (2)) + + - it returns a List object: + expect (prototype (f (l))).to_be "List" + - it makes a new List with the first element removed: + expect (f (l)).to_equal (List {2, 3, 4, 5, 6, 7}) + - it works for an empty List: + expect (f (List {})).to_equal (List {}) + - it returns an empty List when passed a List with one element: + expect (f (List {1})).to_equal (List {}) + + - context as an object method: + - before: + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (0, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (0, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, false)).to_raise (badarg (1)) + + - it returns a List object: + expect (prototype (f (l))).to_be "List" + - it makes a new List with the first element removed: + expect (f (l)).to_equal (List {2, 3, 4, 5, 6, 7}) + - it works for an empty List: + expect (f (List {})).to_equal (List {}) + - it returns an empty List when passed a List with one element: + expect (f (List {1})).to_equal (List {}) - describe transpose: - before: l = List {List {1, 2}, List {3, 4}, List {5, 6}} + fname = "transpose" - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" expect (capture (f, {l})).not_to_contain_error "was deprecated" + - it returns a List object: + expect (prototype (f (l))).to_be "List" + - it works for an empty List: + expect (f (List {})).to_equal (List {}) + - it returns the result in a new List object: + expect (f (l)).not_to_be (l) + - it does not perturb the argument List: + m = f (l) + expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - it transposes rows and columns: expect (f (l)).to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) - - it works for an empty list: - expect (f (List {})).to_equal (List {}) - - context as a list object method: + - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" expect (capture (f, {l})).not_to_contain_error "was deprecated" - - it returns a list object: | - expect (prototype (l:transpose ())).to_be "List" - - it returns the result in a new list object: | - expect (l:transpose ()):not_to_be (l) - - it does not perturb the argument list: | - m = l:transpose () + - it returns a List object: + expect (prototype (f (l))).to_be "List" + - it works for an empty List: + expect (f (List {})).to_equal (List {}) + - it returns the result in a new List object: + expect (f (l)).not_to_be (l) + - it does not perturb the argument List: + m = f (l) expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - - it performs a matrix transpose operation: | - expect (l:transpose ()). + - it transposes rows and columns: + expect (f (l)). to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) - - it works for an empty list: | - l = List {} - expect (l:transpose ()).to_equal (List {}) - describe zip_with: @@ -1073,42 +1354,40 @@ specify std.list: - context as a module function: - before: - f = M[fname] + f, badarg = init (M, fname) - it writes a deprecation warning on first call: expect (capture (f, {l, fn})).to_contain_error "was deprecated" expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - - it returns a list object: + - it returns a List object: expect (prototype (f (l, fn))).to_be "List" - - it returns the result in a new list object: + - it works for an empty List: + expect (f (List {}, fn)).to_equal (List {}) + - it returns the result in a new List object: expect (f (l, fn)):not_to_be (l) - - it does not perturb the argument list: + - it does not perturb the argument List: m = f (l, fn) expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) - it combines column entries with a function: expect (f (l, fn)).to_equal (List {135, 24}) - - it works for an empty list: - l = List {} - expect (f (l, fn)).to_equal (List {}) - - context as a list object method: + - context as an object method: - before: - f = l[fname] + f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - it writes a deprecation warning on first call: expect (capture (f, {l, fn})).to_contain_error "was deprecated" expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - - it returns a list object: | + - it returns a List object: expect (prototype (f (l, fn))).to_be "List" - - it returns the result in a new list object: | + - it works for an empty List: + expect (f (List {}, fn)).to_equal (List {}) + - it returns the result in a new List object: expect (f (l, fn)):not_to_be (l) - - it does not perturb the argument list: | + - it does not perturb the argument List: m = f (l, fn) expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) - - it combines column entries with a function: | + - it combines column entries with a function: expect (f (l, fn)).to_equal (List {135, 24}) - - it works for an empty list: | - l = List {} - expect (f (l, fn)).to_equal (List {}) From b6bc3772a59f40860ddaa9f8d2bedc248656d298 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 16:36:46 +0100 Subject: [PATCH 374/703] doc: document list.cons arguments in the correct order. * lib/std/list.lua (cons): Fix LDoc to display parameters in the correct order. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index d2bd5c5..058d9e0 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -112,8 +112,8 @@ end) --- Prepend an item to a list. -- @static -- @function cons --- @tparam List l a list -- @param x item +-- @tparam List l a list -- @treturn List new list containing `{x, unpack (l)}` local cons = function (x, l, ...) if prototype (x) == "List" and prototype (l) ~= "List" then From 95ab403d6e99308a14b6a9936f86fc468ab5ccd4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 17:03:53 +0100 Subject: [PATCH 375/703] list: properly deprecate `list.map` to match NEWS. * specs/list_spec.yaml (map): Deprecated properly. * lib/std/base/functional.lua (map): Move from here... * lib/std/functional.lua (map): ...back to here. * lib/std/list.lua (map): Reinstate deprecated version of this function locally, for simplicity. Signed-off-by: Gary V. Vaughan --- lib/std/base/functional.lua | 24 --------------------- lib/std/functional.lua | 22 ++++++++++++++++++- lib/std/list.lua | 42 ++++++++++++++++++++++++------------- specs/list_spec.yaml | 11 +++------- 4 files changed, 52 insertions(+), 47 deletions(-) diff --git a/lib/std/base/functional.lua b/lib/std/base/functional.lua index 743456d..bbdfb00 100644 --- a/lib/std/base/functional.lua +++ b/lib/std/base/functional.lua @@ -15,29 +15,6 @@ local function callable (x) end -local function map (mapfn, ifn, ...) - local argt = {...} - if not callable (ifn) or not next (argt) then - ifn, argt = pairs, {ifn, ...} - end - - local nextfn, state, k = ifn (unpack (argt)) - local mapargs = {nextfn (state, k)} - - local r = {} - while mapargs[1] ~= nil do - k = mapargs[1] - local d, v = mapfn (unpack (mapargs)) - if v == nil then d, v = #r + 1, d end - if v ~= nil then - r[d] = v - end - mapargs = {nextfn (state, k)} - end - return r -end - - local function reduce (fn, d, ifn, ...) local nextfn, state, k = ifn (...) local t = {nextfn (state, k)} @@ -53,6 +30,5 @@ end return { callable = callable, - map = map, reduce = reduce, } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 4f86c43..0fb5d45 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -407,7 +407,27 @@ end, M.id)) -- @usage -- --> {1, 4, 9, 16} -- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) -export (M, "map (func, [func], any*)", map) +export (M, "map (func, [func], any*)", function (mapfn, ifn, ...) + local argt = {...} + if not callable (ifn) or not next (argt) then + ifn, argt = pairs, {ifn, ...} + end + + local nextfn, state, k = ifn (unpack (argt)) + local mapargs = {nextfn (state, k)} + + local r = {} + while mapargs[1] ~= nil do + k = mapargs[1] + local d, v = mapfn (unpack (mapargs)) + if v == nil then d, v = #r + 1, d end + if v ~= nil then + r[d] = v + end + mapargs = {nextfn (state, k)} + end + return r +end) --- Map a function over a table of argument lists. diff --git a/lib/std/list.lua b/lib/std/list.lua index 058d9e0..56b9571 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -40,7 +40,26 @@ local argscheck, export, ielems, prototype = local M = { "std.list" } -local map = function (...) return List (base.functional.map (...)) end + +--[[ ================= ]]-- +--[[ Helper Functions. ]]-- +--[[ ================= ]]-- + + +-- Support for DEPRECATED apis. -- + +local ielems = base.ielems + +local function map (fn, l) + local r = List {} + for e in ielems (l) do + local v = fn (e) + if v ~= nil then + r[#r + 1] = v + end + end + return r +end --[[ ================= ]]-- @@ -187,7 +206,7 @@ end) -- @treturn List list of `f` fields -- @see std.list:project local project = export (M, "project (any, List of tables)", function (x, l) - return map (function (t) return t[x] end, ielems, l) + return map (function (t) return t[x] end, l) end) @@ -368,16 +387,6 @@ export (m, "filter (func)", export (m, "flatten ()", flatten) ---- Map a function over a list. --- @function map --- @func fn map function --- @treturn List new list containing --- `{fn (self[1]), ..., fn (self[#self])}` --- @see std.list.map -export (m, "map (func)", - function (self, fn) return map (fn, self) end) - - --- Project a list of fields from a list of tables. -- @function project -- @param f field to project @@ -476,7 +485,7 @@ end local function map_with (fn, ls) - return map (function (...) return fn (unpack (...)) end, ielems, ls) + return map (function (...) return fn (unpack (...)) end, ls) end @@ -487,7 +496,7 @@ local function reverse (l) return List (base.ireverse (l)) end local function transpose (ls) - local rs, len, dims = List {}, base.len (ls), map (base.len, ielems, ls) + local rs, len, dims = List {}, base.len (ls), map (base.len, ls) if #dims > 0 then for i = 1, math.max (unpack (dims)) do rs[i] = List {} @@ -556,6 +565,11 @@ m.index_value = DEPRECATED ("41", "'std.list:index_value'", M.map = DEPRECATED ("41", "'std.list.map'", "use 'std.functional.map' instead", map) +m.map = DEPRECATED ("41", "'std.list:map'", + "use 'std.functional.map' instead", + function (self, fn) return map (fn, self) end) + + M.map_with = DEPRECATED ("41", "'std.list.map_with'", "use 'std.functional.map_with' instead", map_with) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 59e5584..fec6f48 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -837,14 +837,9 @@ specify std.list: - before: f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - expect (f (l)).to_raise (badarg (1, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (1, "function", "boolean")) - - it diagnoses too many arguments: - expect (f (l, sq, false)).to_raise (badarg (2)) + - it writes a deprecation warning on first call: + expect (capture (f, {l, sq})).to_contain_error "was deprecated" + expect (capture (f, {l, sq})).not_to_contain_error "was deprecated" - it returns a List object: m = f (l, sq) From 0427b8267439b6f8213fca5e4d5dfb751f33ee85 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 17:22:52 +0100 Subject: [PATCH 376/703] list: deprecate filter in favour of functional.filter. * specs/list_spec.yaml (filter): Specify deprecation messages. * lib/std/list.lua (filter): Deprecated. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 ++++---- lib/std/list.lua | 45 ++++++++++++++++++++------------------------ specs/list_spec.yaml | 22 ++++++---------------- 3 files changed, 30 insertions(+), 45 deletions(-) diff --git a/NEWS b/NEWS index bba098c..087dea1 100644 --- a/NEWS +++ b/NEWS @@ -149,8 +149,8 @@ Stdlib NEWS - User visible changes - `functional.fold` has been renamed to `functional.reduce`, the old name now gives a deprecation warning. - - `list.flatten` has been moved to `functional.flatten`, the old name - now gives a deprecation warning. + - `list.filter` and `list.flatten` has been moved to `functional.filter` + and `functional.flatten`, the old names now give deprecation warnings. - `list.foldl` and `list.foldr` have been replaced by the richer `functional.foldl` and `functional.foldr` respectively. The old @@ -213,8 +213,8 @@ Stdlib NEWS - User visible changes the fixed argument positions in the `bind` invocation. - `functional.collect`, `functional.filter` and `functional.map` still - makes a list from the results from an iterator that returns single - values, but when an iterator returns multiple values it now makes a + 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. diff --git a/lib/std/list.lua b/lib/std/list.lua index 56b9571..2d79e6d 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -185,19 +185,6 @@ export (M, "enpair (table)", function (t) end) ---- Filter a list according to a predicate. --- @static --- @function filter --- @func p predicate function, of one argument returning a boolean --- @tparam List l a list --- @treturn List new list containing elements `e` of `l` for which --- `p (e)` is true --- @see std.list:filter -local filter = export (M, "filter (function, List)", function (p, l) - return List (func.filter (p, ielems, l)) -end) - - --- Project a list of fields from a list of tables. -- @static -- @function project @@ -371,16 +358,6 @@ export (m, "cons (any)", function (self, x) return cons (x, self) end) ---- Filter a list according to a predicate. --- @function filter --- @func p predicate function, of one argument returning a boolean --- @treturn List new list containing elements `e` of `self` for which --- `p (e)` is true --- @see std.list.filter -export (m, "filter (func)", - function (self, p) return filter (p, self) end) - - --- Flatten a list. -- @function flatten -- @treturn List flattened list @@ -439,6 +416,17 @@ export (m, "tail ()", tail) local DEPRECATED = base.DEPRECATED +local function filter (pfn, l) + local r = List {} + for e in ielems (l) do + if pfn (e) then + r[#r + 1] = e + end + end + return r +end + + local function foldl (fn, d, t) if t == nil then local tail = {} @@ -528,6 +516,13 @@ M.elems = DEPRECATED ("41", "'std.list.elems'", m.elems = DEPRECATED ("41", "'std.list:elems'", "use 'std.ielems' instead", base.ielems) +M.filter = DEPRECATED ("41", "'std.list.filter'", + "use 'std.functional.filter' instead", filter) +m.filter = DEPRECATED ("41", "'std.list:filter'", + "use 'std.functional.filter' instead", + function (self, p) return filter (p, self) end) + + M.flatten = DEPRECATED ("41", "'std.list.flatten'", "use 'std.functional.flatten' instead", flatten) @@ -550,14 +545,14 @@ m.foldr = DEPRECATED ("41", "'std.list:foldr'", end) M.index_key = DEPRECATED ("41", "'std.list.index_key'", - "compose 'std.list.filter' and 'std.table.invert' instead", + "compose 'std.functional.filter' and 'std.table.invert' instead", index_key) m.index_key = DEPRECATED ("41", "'std.list:index_key'", function (self, fn) return index_key (fn, self) end) M.index_value = DEPRECATED ("41", "'std.list.index_value'", - "compose 'std.list.filter' and 'std.table.invert' instead", + "compose 'std.functional.filter' and 'std.table.invert' instead", index_value) m.index_value = DEPRECATED ("41", "'std.list:index_value'", function (self, fn) return index_value (fn, self) end) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index fec6f48..306ad59 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -491,14 +491,9 @@ specify std.list: - before: f, badarg = init (M, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (p)).to_raise (badarg (2, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (p, false)).to_raise (badarg (2, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (p, l, false)).to_raise (badarg (3)) + - it writes a deprecation warning on first call: + expect (capture (f, {p, l})).to_contain_error "was deprecated" + expect (capture (f, {p, l})).not_to_contain_error "was deprecated" - it returns a List object: expect (prototype (f (p, l))).to_be "List" @@ -511,14 +506,9 @@ specify std.list: - before: f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - expect (f (l)).to_raise (badarg (1, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (1, "function", "boolean")) - - it diagnoses too many arguments: - expect (f (l, p, false)).to_raise (badarg (2)) + - it writes a deprecation warning on first call: + expect (capture (f, {l, p})).to_contain_error "was deprecated" + expect (capture (f, {l, p})).not_to_contain_error "was deprecated" - it returns a List object: expect (prototype (f (l, p))).to_be "List" From 1bd8e1618ae8ca09acf044e1cc0fccbbed30ec29 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 17:30:33 +0100 Subject: [PATCH 377/703] refactor: move list.flatten to table.flatten. * specs/list_spec.yaml (flatten): Specify deprecation warnings. * specs/functional_spec.yaml (flatten): Move from here... * specs/table_spec.yaml (flatten): ...to here. * lib/std/list.lua (flatten): Move from here... * lib/std/table.lua (fatten): ...to here. * lib/std/functional.lua (collect): Move core from here... * lib/std/base/functional.lua (collect): ...to here. * specs/list_spec.yaml (flatten): Deprecated properly. * lib/std/list.lua (flatten): Wrap object method in deprecation warning. Signed-off-by: Gary V. Vaughan --- lib/std/base/functional.lua | 16 ++++++++++++++++ lib/std/functional.lua | 24 +----------------------- lib/std/list.lua | 9 +++------ lib/std/table.lua | 11 +++++++++++ specs/functional_spec.yaml | 24 ++---------------------- specs/list_spec.yaml | 9 +++------ specs/table_spec.yaml | 23 ++++++++++++++++++++++- 7 files changed, 58 insertions(+), 58 deletions(-) diff --git a/lib/std/base/functional.lua b/lib/std/base/functional.lua index bbdfb00..2a4ec46 100644 --- a/lib/std/base/functional.lua +++ b/lib/std/base/functional.lua @@ -15,6 +15,21 @@ local function callable (x) end +local function collect (ifn, ...) + local argt = {...} + if not callable (ifn) then + ifn, argt = ipairs, {ifn, ...} + end + + local r = {} + for k, v in ifn (unpack (argt)) do + if v == nil then k, v = #r + 1, k end + r[k] = v + end + return r +end + + local function reduce (fn, d, ifn, ...) local nextfn, state, k = ifn (...) local t = {nextfn (state, k)} @@ -30,5 +45,6 @@ end return { callable = callable, + collect = collect, reduce = reduce, } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 0fb5d45..70e9020 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -15,7 +15,6 @@ local export, ielems, ipairs, ireverse, len, pairs = base.export, base.ielems, base.ipairs, base.ireverse, base.len, base.pairs local callable, map, reduce = base.functional.callable, base.functional.map, base.functional.reduce -local leaves = base.tree.leaves local M = { "std.functional" } @@ -135,19 +134,7 @@ end) -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} -local collect = export (M, "collect ([func], any*)", function (ifn, ...) - local argt = {...} - if not callable (ifn) then - ifn, argt = ipairs, {ifn, ...} - end - - local r = {} - for k, v in ifn (unpack (argt)) do - if v == nil then k, v = #r + 1, k end - r[k] = v - end - return r -end) +export (M, "collect ([func], any*)", base.functional.collect) --- Compose functions. @@ -270,15 +257,6 @@ export (M, "filter (func, [func], any*)", function (pfn, ifn, ...) end) ---- Flatten a nested table into a list. --- @function flatten --- @tparam table t a table --- @treturn table a list of all non-table elements of *t* -export (M, "flatten (table)", function (t) - return collect (leaves, ipairs, t) -end) - - --- Fold a binary function left associatively. -- If parameter *d* is omitted, the first element of *t* is used, -- and *t* treated as if it had been passed without that element. diff --git a/lib/std/list.lua b/lib/std/list.lua index 2d79e6d..04c428c 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -358,12 +358,6 @@ export (m, "cons (any)", function (self, x) return cons (x, self) end) ---- Flatten a list. --- @function flatten --- @treturn List flattened list -export (m, "flatten ()", flatten) - - --- Project a list of fields from a list of tables. -- @function project -- @param f field to project @@ -525,6 +519,9 @@ m.filter = DEPRECATED ("41", "'std.list:filter'", M.flatten = DEPRECATED ("41", "'std.list.flatten'", "use 'std.functional.flatten' instead", flatten) +m.flatten = DEPRECATED ("41", "'std.list:flatten'", + "use 'std.functional.flatten' instead", flatten) + M.foldl = DEPRECATED ("41", "'std.list.foldl'", "use 'std.functional.foldl' instead", foldl) diff --git a/lib/std/table.lua b/lib/std/table.lua index 44b0ba0..426b28c 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -15,6 +15,8 @@ local base = require "std.base" local export, getmetamethod, ipairs, pairs = base.export, base.getmetamethod, base.ipairs, base.pairs +local collect = base.functional.collect +local leaves = base.tree.leaves local M = { "std.table" } @@ -121,6 +123,15 @@ export (M, "empty (table)", function (t) end) +--- Flatten a nested table into a list. +-- @function flatten +-- @tparam table t a table +-- @treturn table a list of all non-table elements of *t* +export (M, "flatten (table)", function (t) + return collect (leaves, ipairs, t) +end) + + --- Invert a table. -- @function invert -- @tparam table t a table with `{k=v, ...}` diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index f91ee9f..76b5644 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -5,8 +5,8 @@ before: global_table = "_G" exported_apis = { 1, "bind", "callable", "case", "collect", "compose", - "cond", "curry", "eval", "filter", "flatten", "fold", - "foldl", "foldr", "id", "lambda", "map", "map_with", + "cond", "curry", "eval", "filter", "fold", "foldl", + "foldr", "id", "lambda", "map", "map_with", "memoize", "nop", "op", "reduce", "zip", "zip_with" } M = require (this_module) @@ -263,26 +263,6 @@ specify std.functional: to_equal {"first", last="three"} -- describe flatten: - - before: - t = {{{"one"}}, "two", {{"three"}, "four"}} - - f, badarg = init (M, "flatten") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f (t, false)).to_raise (badarg (2)) - - - it returns a table: - expect (type (f (t))).to_be "table" - - it works for an empty table: - expect (f {}).to_equal {} - - it flattens a nested table: - expect (f (t)).to_equal {"one", "two", "three", "four"} - - describe fold: - before: f = M.fold diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 306ad59..1d1638a 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -545,12 +545,9 @@ specify std.list: - before: f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, false)).to_raise (badarg (1)) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: expect (prototype (f (l))).to_be "List" diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 76ba6bd..7ca187b 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -4,7 +4,7 @@ before: | global_table = "_G" extend_base = { "clone", "clone_rename", "clone_select", "empty", - "invert", "keys", "merge", "merge_select", + "flatten", "invert", "keys", "merge", "merge_select", "metamethod", "monkey_patch", "new", "pack", "ripairs", "size", "sort", "totable", "values" } @@ -170,6 +170,27 @@ specify std.table: expect (f {false}).to_be (false) +- describe flatten: + - before: + t = {{{"one"}}, "two", {{"three"}, "four"}} + + f, badarg = init (M, "flatten") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f (t, false)).to_raise (badarg (2)) + + - it returns a table: + expect (type (f (t))).to_be "table" + - it works for an empty table: + expect (f {}).to_equal {} + - it flattens a nested table: + expect (f (t)).to_equal {"one", "two", "three", "four"} + + - describe invert: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } From 60408e55b91bf7033ae00c35185165f57b6cc5c4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 20:52:01 +0100 Subject: [PATCH 378/703] refactor: move `list.shape` to `table.shape`. * specs/table_spec.yaml (shape): Specify full behaviours. * specs/list_spec.yaml (shape): Specify deprecation warnings. * lib/std/list.lua (shape): Move from here... * lib/std/table.lua (shape): ...to here. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 16 ++++-- lib/std/list.lua | 127 +++++++++++++++++------------------------- lib/std/table.lua | 58 ++++++++++++++++++- specs/list_spec.yaml | 22 ++------ specs/table_spec.yaml | 39 ++++++++++++- 5 files changed, 162 insertions(+), 100 deletions(-) diff --git a/NEWS b/NEWS index 087dea1..589dd15 100644 --- a/NEWS +++ b/NEWS @@ -149,8 +149,11 @@ Stdlib NEWS - User visible changes - `functional.fold` has been renamed to `functional.reduce`, the old name now gives a deprecation warning. - - `list.filter` and `list.flatten` has been moved to `functional.filter` - and `functional.flatten`, 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 @@ -160,10 +163,8 @@ Stdlib NEWS - User visible changes - `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 will continue to work - for another release or two, issuing a deprecation warning on first - use. After that, in some future release, they will be removed - entirely. + 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 @@ -175,6 +176,9 @@ Stdlib NEWS - User visible changes - `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. diff --git a/lib/std/list.lua b/lib/std/list.lua index 04c428c..b1a6482 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -212,73 +212,6 @@ local rep = export (M, "rep (List, int)", function (l, n) end) ---- Shape a list according to a list of dimensions. --- --- Dimensions are given outermost first and items from the original --- list are distributed breadth first; there may be one 0 indicating --- an indefinite number. Hence, `{0}` is a flat list, --- `{1}` is a singleton, `{2, 0}` is a list of --- two lists, and `{0, 2}` is a list of pairs. --- --- Algorithm: turn shape into all positive numbers, calculating --- the zero if necessary and making sure there is at most one; --- recursively walk the shape, adding empty tables until the bottom --- level is reached at which point add table items instead, using a --- counter to walk the flattened original list. --- --- @todo Use ileaves instead of flatten (needs a while instead of a --- for in fill function) --- @static --- @function shape --- @tparam table s `{d1, ..., dn}` --- @tparam List l a list --- @return reshaped list --- @see std.list:shape - -local function flatten (l) - local r = List {} - for v in base.tree.leaves (ipairs, l) do - r[#r + 1] = v - end - return r -end - -local shape = export (M, "shape (table, List)", function (s, l) - l = flatten (l) - -- Check the shape and calculate the size of the zero, if any - local size = 1 - local zero - for i, v in ipairs (s) do - if v == 0 then - if zero then -- bad shape: two zeros - return nil - else - zero = i - end - else - size = size * v - end - end - if zero then - s[zero] = math.ceil (#l / size) - end - local function fill (i, d) - if d > #s then - return l[i], i + 1 - else - local r = List {} - for j = 1, s[d] do - local e - e, i = fill (i, d + 1) - r[#r + 1] = e - end - return r, i - end - end - return (fill (1, 1)) -end) - - --- Return a sub-range of a list. -- (The equivalent of `string.sub` on strings; negative list indices -- count from the end of the list.) @@ -376,15 +309,6 @@ end) export (m, "rep (int)", rep) ---- Shape a list according to a list of dimensions. --- @function shape --- @tparam table s `{d1, ..., dn}` --- @return reshaped list --- @see std.list.shape -export (m, "shape (table)", - function (self, s) return shape (s, self) end) - - --- Return a sub-range of a list. -- (The equivalent of `string.sub` on strings; negative list indices -- count from the end of the list.) @@ -421,6 +345,15 @@ local function filter (pfn, l) end +local function flatten (l) + local r = List {} + for v in base.tree.leaves (ipairs, l) do + r[#r + 1] = v + end + return r +end + + local function foldl (fn, d, t) if t == nil then local tail = {} @@ -477,6 +410,42 @@ local function relems (l) return base.ielems (base.ireverse (l)) end local function reverse (l) return List (base.ireverse (l)) end +local function shape (s, l) + l = flatten (l) + -- Check the shape and calculate the size of the zero, if any + local size = 1 + local zero + for i, v in ipairs (s) do + if v == 0 then + if zero then -- bad shape: two zeros + return nil + else + zero = i + end + else + size = size * v + end + end + if zero then + s[zero] = math.ceil (#l / size) + end + local function fill (i, d) + if d > #s then + return l[i], i + 1 + else + local r = List {} + for j = 1, s[d] do + local e + e, i = fill (i, d + 1) + r[#r + 1] = e + end + return r, i + end + end + return (fill (1, 1)) +end + + local function transpose (ls) local rs, len, dims = List {}, base.len (ls), map (base.len, ls) if #dims > 0 then @@ -575,6 +544,12 @@ M.reverse = DEPRECATED ("41", "'std.list.reverse'", m.reverse = DEPRECATED ("41", "'std.list:reverse'", "use 'std.ireverse' instead", reverse) +M.shape = DEPRECATED ("41", "'std.list.shape'", + "use 'std.functional.shape' instead", shape) +m.shape = DEPRECATED ("41", "'std.list:shape'", + "use 'std.functional.shape' instead", + function (t, l) return shape (l, t) end) + M.transpose = DEPRECATED ("41", "'std.list.transpose'", "use 'std.functional.zip' instead", transpose) diff --git a/lib/std/table.lua b/lib/std/table.lua index 426b28c..1b0d5bd 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -127,7 +127,7 @@ end) -- @function flatten -- @tparam table t a table -- @treturn table a list of all non-table elements of *t* -export (M, "flatten (table)", function (t) +local flatten = export (M, "flatten (table)", function (t) return collect (leaves, ipairs, t) end) @@ -213,6 +213,62 @@ function M.pack (...) end +--- Shape a table according to a list of dimensions. +-- +-- Dimensions are given outermost first and items from the original +-- list are distributed breadth first; there may be one 0 indicating +-- an indefinite number. Hence, `{0}` is a flat list, +-- `{1}` is a singleton, `{2, 0}` is a list of +-- two lists, and `{0, 2}` is a list of pairs. +-- +-- Algorithm: turn shape into all positive numbers, calculating +-- the zero if necessary and making sure there is at most one; +-- recursively walk the shape, adding empty tables until the bottom +-- level is reached at which point add table items instead, using a +-- counter to walk the flattened original list. +-- +-- @todo Use ileaves instead of flatten (needs a while instead of a +-- for in fill function) +-- @function shape +-- @tparam table dims table of dimensions `{d1, ..., dn}` +-- @tparam table t a table of elements +-- @return reshaped list +export (M, "shape (table, table)", function (dims, t) + t = flatten (t) + -- Check the shape and calculate the size of the zero, if any + local size = 1 + local zero + for i, v in ipairs (dims) do + if v == 0 then + if zero then -- bad shape: two zeros + return nil + else + zero = i + end + else + size = size * v + end + end + if zero then + dims[zero] = math.ceil (#t / size) + end + local function fill (i, d) + if d > #dims then + return t[i], i + 1 + else + local r = {} + for j = 1, dims[d] do + local e + e, i = fill (i, d + 1) + r[#r + 1] = e + end + return r, i + end + end + return (fill (1, 1)) +end) + + --- Find the number of elements in a table. -- @function size -- @tparam table t any table diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 1d1638a..b82dbd1 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1102,14 +1102,9 @@ specify std.list: - before: f, badarg = init (M, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - expect (f ({})).to_raise (badarg (2, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_raise (badarg (2, "List", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, l, false)).to_raise (badarg (3)) + - it writes a deprecation warning on first call: + expect (capture (f, {{0}, l})).to_contain_error "was deprecated" + expect (capture (f, {{0}, l})).not_to_contain_error "was deprecated" - it returns a List object: expect (prototype (f ({2, 3}, l))).to_be "List" @@ -1135,14 +1130,9 @@ specify std.list: - before: f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - expect (f (l)).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - expect (f (List {}, false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f (l, {}, false)).to_raise (badarg (2)) + - it writes a deprecation warning on first call: + expect (capture (f, {l, {0}})).to_contain_error "was deprecated" + expect (capture (f, {l, {0}})).not_to_contain_error "was deprecated" - it returns a List object: expect (prototype (f (l, {2, 3}))).to_be "List" diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 7ca187b..851f866 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -6,7 +6,8 @@ before: | extend_base = { "clone", "clone_rename", "clone_select", "empty", "flatten", "invert", "keys", "merge", "merge_select", "metamethod", "monkey_patch", "new", "pack", - "ripairs", "size", "sort", "totable", "values" } + "ripairs", "shape", "size", "sort", "totable", + "values" } M = require "std.table" @@ -485,6 +486,42 @@ specify std.table: expect (u).to_equal {"five", "two", "one"} +- describe shape: + - before: + l = {1, 2, 3, 4, 5, 6} + + f, badarg = init (M, "shape") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + expect (f ({})).to_raise (badarg (2, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + expect (f ({}, false)).to_raise (badarg (2, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, l, false)).to_raise (badarg (3)) + + - it returns a table: + expect (prototype (f ({2, 3}, l))).to_be "table" + - it works for an empty table: + expect (f ({0}, {})).to_equal ({}) + - it returns the result in a new table: + expect (f ({2, 3}, l)).not_to_be (l) + - it does not perturb the argument table: + f ({2, 3}, l) + expect (l).to_equal {1, 2, 3, 4, 5, 6} + - it reshapes a table according to given dimensions: + expect (f ({2, 3}, l)). + to_equal ({{1, 2, 3}, {4, 5, 6}}) + expect (f ({3, 2}, l)). + to_equal ({{1, 2}, {3, 4}, {5, 6}}) + - it treats 0-valued dimensions as an indefinite number: + expect (f ({2, 0}, l)). + to_equal ({{1, 2, 3}, {4, 5, 6}}) + expect (f ({0, 2}, l)). + to_equal ({{1, 2}, {3, 4}, {5, 6}}) + + - describe size: - before: | -- - 1 - --------- 2 ---------- -- 3 -- From 9ef89c8b850bec3cb7cf720131fdf9c4f2ac6d85 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 21:23:33 +0100 Subject: [PATCH 379/703] refactor: move `list.project` to `table.project`. * specs/table_spec.yaml (project): Specify behaviours of project. * specs/list_spec.yaml (project): Specify deprecation warnings. * lib/std/list.lua (project): Move from here... * lib/std/table.lua (project): ...to here. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/list.lua | 38 +++++++++++++------------------------- lib/std/table.lua | 14 ++++++++++++++ specs/list_spec.yaml | 28 ++++++---------------------- specs/table_spec.yaml | 36 ++++++++++++++++++++++++++++++++++-- 5 files changed, 70 insertions(+), 49 deletions(-) diff --git a/NEWS b/NEWS index 589dd15..c81b4b9 100644 --- a/NEWS +++ b/NEWS @@ -170,6 +170,9 @@ Stdlib NEWS - User visible changes 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)`. diff --git a/lib/std/list.lua b/lib/std/list.lua index b1a6482..14c50eb 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -185,18 +185,6 @@ export (M, "enpair (table)", function (t) end) ---- Project a list of fields from a list of tables. --- @static --- @function project --- @param f field to project --- @tparam List l a list of tables --- @treturn List list of `f` fields --- @see std.list:project -local project = export (M, "project (any, List of tables)", function (x, l) - return map (function (t) return t[x] end, l) -end) - - --- Repeat a list. -- @static -- @function rep @@ -291,17 +279,6 @@ export (m, "cons (any)", function (self, x) return cons (x, self) end) ---- Project a list of fields from a list of tables. --- @function project --- @param f field to project --- @treturn List list of `f` fields --- @see std.list.project -export (m, "project (any)", function (self, x) - base.argcheck ("std.list:project", 0, "List of tables", self) - return project (x, self) -end) - - --- Repeat a list. -- @function rep -- @int n number of times to repeat @@ -404,6 +381,11 @@ local function map_with (fn, ls) end +local function project (x, l) + return map (function (t) return t[x] end, l) +end + + local function relems (l) return base.ielems (base.ireverse (l)) end @@ -535,6 +517,12 @@ m.map = DEPRECATED ("41", "'std.list:map'", M.map_with = DEPRECATED ("41", "'std.list.map_with'", "use 'std.functional.map_with' instead", map_with) +M.project = DEPRECATED ("41", "'std.list.project'", + "use 'std.table.project' instead", project) +m.project = DEPRECATED ("41", "'std.list:project'", + "use 'std.table.project' instead", + function (self, x) return project (x, self) end) + M.relems = DEPRECATED ("41", "'std.list.relems'", "compose 'std.ielems' and 'std.ireverse' instead", relems) m.relems = DEPRECATED ("41", "'std.list:relems'", relems) @@ -545,9 +533,9 @@ m.reverse = DEPRECATED ("41", "'std.list:reverse'", "use 'std.ireverse' instead", reverse) M.shape = DEPRECATED ("41", "'std.list.shape'", - "use 'std.functional.shape' instead", shape) + "use 'std.table.shape' instead", shape) m.shape = DEPRECATED ("41", "'std.list:shape'", - "use 'std.functional.shape' instead", + "use 'std.table.shape' instead", function (t, l) return shape (l, t) end) M.transpose = DEPRECATED ("41", "'std.list.transpose'", diff --git a/lib/std/table.lua b/lib/std/table.lua index 1b0d5bd..885aa59 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -213,6 +213,20 @@ function M.pack (...) end +--- 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* +export (M, "project (any, list of tables)", function (fkey, tt) + local r = {} + for _, t in ipairs (tt) do + r[#r + 1] = t[fkey] + end + return r +end) + + --- Shape a table according to a list of dimensions. -- -- Dimensions are given outermost first and items from the original diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index b82dbd1..0d87f36 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -911,17 +911,9 @@ specify std.list: - before: f, badarg = init (M, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "any value")) - expect (f ("x")).to_raise (badarg (2, "List")) - - it diagnoses wrong argument types: - expect (f ("x", false)).to_raise (badarg (2, "List", "boolean")) - expect (f ("x", List {false})). - to_raise (badarg (2, "List of tables", "boolean at index 1")) - expect (f ("x", List {{}, false})). - to_raise (badarg (2, "List of tables", "boolean at index 2")) - - it diagnoses too many arguments: - expect (f ("x", l, false)).to_raise (badarg (3)) + - it writes a deprecation warning on first call: + expect (capture (f, {"third", l})).to_contain_error "was deprecated" + expect (capture (f, {"third", l})).not_to_contain_error "was deprecated" - it returns a List object: expect (prototype (f ("third", l))).to_be "List" @@ -936,17 +928,9 @@ specify std.list: - before: f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - expect (f (l)).to_raise (badarg (1, "any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - expect (f (List {false}, "x")). - to_raise (badarg (0, "List of tables", "boolean at index 1")) - expect (f (List {{}, false}, "x")). - to_raise (badarg (0, "List of tables", "boolean at index 2")) - - it diagnoses too many arguments: - expect (f (l, "x", false)).to_raise (badarg (2)) + - it writes a deprecation warning on first call: + expect (capture (f, {l, "third"})).to_contain_error "was deprecated" + expect (capture (f, {l, "third"})).not_to_contain_error "was deprecated" - it returns a List object: expect (prototype (f (l, "third"))).to_be "List" diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 851f866..57e8511 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -6,8 +6,8 @@ before: | extend_base = { "clone", "clone_rename", "clone_select", "empty", "flatten", "invert", "keys", "merge", "merge_select", "metamethod", "monkey_patch", "new", "pack", - "ripairs", "shape", "size", "sort", "totable", - "values" } + "project", "ripairs", "shape", "size", "sort", + "totable", "values" } M = require "std.table" @@ -465,6 +465,38 @@ specify std.table: expect (f (unpack (t))).to_equal (t) +- describe project: + - before: + l = { + {first = false, second = true, third = true}, + {first = 1, second = 2, third = 3}, + {first = "1st", second = "2nd", third = "3rd"}, + } + + f, badarg = init (M, "project") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "any value")) + expect (f ("x")).to_raise (badarg (2, "list")) + - it diagnoses wrong argument types: + expect (f ("x", false)).to_raise (badarg (2, "list", "boolean")) + expect (f ("x", {false})). + to_raise (badarg (2, "list of tables", "boolean at index 1")) + expect (f ("x", {{}, false})). + to_raise (badarg (2, "list of tables", "boolean at index 2")) + - it diagnoses too many arguments: + expect (f ("x", l, false)).to_raise (badarg (3)) + + - it returns a table: + expect (prototype (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 ripairs: - before: f = M.ripairs From 5b230b3870deba3ec8b155ac40a0acb37a931caf Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 22:01:13 +0100 Subject: [PATCH 380/703] travis: use unreleased specl rockspec. * .travis.yml (script): Use specl-git-1.rockspec from github. Signed-off-by: Gary V. Vaughan --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f90af29..6ea8d87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,7 +54,9 @@ script: - export PATH=`pwd`/luarocks/bin:$PATH # Install extra rocks into $LUAROCKS_CONFIG rocks tree. - - $LUAROCKS install lyaml; $LUAROCKS install ldoc; $LUAROCKS install specl; + - $LUAROCKS install lyaml; $LUAROCKS install ldoc + # Temporarily install git specl for required unreleased fixes + - $LUAROCKS install https://raw.githubusercontent.com/gvvaughan/specl/release/specl-git-1.rockspec # Make git rockspec for this stdlib - make rockspecs LUAROCKS="$LUAROCKS" V=1 From 59092edfd96d1afd009f48fb1236775111fa8542 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 22:20:58 +0100 Subject: [PATCH 381/703] maint: disable specl rock version check by bootstrap. Specl 13 is not released yet, but we're relying on some fixes it has. * bootstrap.conf (buildreq): Disable specl temporarily. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap.conf b/bootstrap.conf index e20b124..deeb49d 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -1,4 +1,4 @@ -# bootstrap.conf (Stdlib) version 2014-01-04 +# bootstrap.conf (Stdlib) version 2014-08-18 # # Copyright (C) 2013-2014 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 @@ -37,8 +37,8 @@ buildreq=' git - http://git-scm.com ldoc 1.4.0 http://luarocks.org/repositories/rocks/ldoc-1.4.2-1.rockspec - specl 11 http://luarocks.org/repositories/rocks/specl-11-1.rockspec ' +### specl 13 http://luarocks.org/repositories/rocks/specl-13-1.rockspec # List of slingshot files to link into stdlib tree before autotooling. slingshot_files=' From 30114f3b4af1101555ea5fceac65af70452bdeda Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 22:20:31 +0100 Subject: [PATCH 382/703] Revert "list: exchange parameter order for list.cons." This reverts commit 1b290ee40f638b03a4dd4b3c4f5b8faa6cdd2479. Signed-off-by: Gary V. Vaughan Conflicts: lib/std/list.lua specs/list_spec.yaml --- NEWS | 9 --------- lib/std/list.lua | 29 +++++++---------------------- specs/list_spec.yaml | 21 +++++++-------------- 3 files changed, 14 insertions(+), 45 deletions(-) diff --git a/NEWS b/NEWS index c81b4b9..6a9faf0 100644 --- a/NEWS +++ b/NEWS @@ -236,15 +236,6 @@ Stdlib NEWS - User visible changes - `io.catdir` now raises an error when called with no arguments, for consistency with `io.catfile`. - - `list.cons` now expects arguments in lisp-like order: - - newlist = list.cons (newhead, oldlist) - - If legacy ordered arguments are detected, a deprecation warning is - raised in the first instance, and arguments are reordered before - continuing. List object method `list:cons (newhead)` calls remain - as before. - - `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, diff --git a/lib/std/list.lua b/lib/std/list.lua index 14c50eb..e807412 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -35,8 +35,8 @@ local Object = object {} local List -- forward declaration local ipairs, pairs = base.ipairs, base.pairs -local argscheck, export, ielems, prototype = - base.argscheck, base.export, base.ielems, base.prototype +local export, ielems, prototype = + base.export, base.ielems, base.prototype local M = { "std.list" } @@ -131,26 +131,12 @@ end) --- Prepend an item to a list. -- @static -- @function cons --- @param x item -- @tparam List l a list +-- @param x item -- @treturn List new list containing `{x, unpack (l)}` -local cons = function (x, l, ...) - if prototype (x) == "List" and prototype (l) ~= "List" then - if not base.getcompat (M.cons) then - io.stderr:write (base.DEPRECATIONMSG ("41", - "'std.list.cons' with List argument first", 2)) - base.setcompat (M.cons) - end - x, l = l, x - end - argscheck ("std.list.cons", {"any", "List?"}, {x, l}) - if next {...} then - error (string.format (base.toomanyarg_fmt, "std.list.cons", 2, 2 + base.len {...}), 2) - end - - return List {x, unpack (l or {})} -end -M.cons = cons +local cons = export (M, "cons (List, any)", function (l, x) + return List {x, unpack (l)} +end) --- Turn a list of pairs into a table. @@ -275,8 +261,7 @@ export (m, "concat (List|table*)", concat) -- @function cons -- @param x item -- @treturn List new list containing `{x, unpack (self)}` -export (m, "cons (any)", - function (self, x) return cons (x, self) end) +export (m, "cons (any)", cons) --- Repeat a list. diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 0d87f36..ba9f3f7 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -327,27 +327,20 @@ specify std.list: - before: f, badarg = init (M, fname) - - it writes a argument order deprecation warning on first call: - expect (capture (f, {l, "x"})).to_contain_error "was deprecated" - expect (capture (f, {l, "x"})).not_to_contain_error "was deprecated" - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "any value")) + expect (f ()).to_raise (badarg (1, "List")) + expect (f (List {})).to_raise (badarg (2, "any value")) - it diagnoses wrong argument types: - expect (f ("x", false)).to_raise (badarg (2, "List or nil", "boolean")) + expect (f (false)).to_raise (badarg (1, "List", "boolean")) - it diagnoses too many arguments: - expect (f ("x", List {}, false)).to_raise (badarg (3)) + expect (f (List {}, "x", false)).to_raise (badarg (3)) - it returns a List object: - expect (prototype (f ("head", l))).to_be "List" + expect (prototype (f (l, "x"))).to_be "List" - it prepends an item to a List: - expect (f ("head", l)). - to_equal (List {"head", "foo", "bar", "baz"}) + expect (f (l, "x")).to_equal (List {"x", "foo", "bar", "baz"}) - it works for empty Lists: - l = List {} - expect (f ("head", l)).to_equal (List {"head"}) - - it supports single argument call: - expect (f "head").to_equal (List {"head"}) + expect (f (List {}, "x")).to_equal (List {"x"}) - context as an object method: - before: From f78812fb36c89921e966750eb93ac598268e7776 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 22:36:53 +0100 Subject: [PATCH 383/703] refactor: move list.depair and list.enpair into std.table. * specs/table_spec.yaml (depair, enpair): Specify full behaviours. * specs/list_spec.yaml (depair, enpair): Specify deprecation warnings. * lib/std/list.lua (depair, enpair): Move from here... * lib/std/table.lua (depair, enpair): ...to here. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/list.lua | 55 +++++++++++++++++---------------------- lib/std/table.lua | 34 ++++++++++++++++++++++-- specs/list_spec.yaml | 27 ++++++------------- specs/table_spec.yaml | 60 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 121 insertions(+), 58 deletions(-) diff --git a/NEWS b/NEWS index 6a9faf0..65d6c4e 100644 --- a/NEWS +++ b/NEWS @@ -149,6 +149,9 @@ Stdlib NEWS - User visible changes - `functional.fold` has been renamed to `functional.reduce`, the old name now gives a deprecation warning. + - `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. diff --git a/lib/std/list.lua b/lib/std/list.lua index e807412..82c7f15 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -139,38 +139,6 @@ local cons = export (M, "cons (List, any)", function (l, x) end) ---- Turn a list of pairs into a table. --- @todo Find a better name. --- @static --- @function depair --- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` --- @treturn table a new list containing table `{i1=v1, ..., in=vn}` --- @see enpair -local depair = export (M, "depair (List of Lists)", function (ls) - local t = {} - for v in ielems (ls) do - t[v[1]] = v[2] - end - return t -end) - - ---- Turn a table into a list of pairs. --- @todo Find a better name. --- @static --- @function enpair --- @tparam table t a table `{i1=v1, ..., in=vn}` --- @treturn List a new list containing `{{i1, v1}, ..., {in, vn}}` --- @see depair -export (M, "enpair (table)", function (t) - local ls = List {} - for i, v in pairs (t) do - ls[#ls + 1] = List {i, v} - end - return ls -end) - - --- Repeat a list. -- @static -- @function rep @@ -296,6 +264,24 @@ export (m, "tail ()", tail) local DEPRECATED = base.DEPRECATED +local function depair (ls) + local t = {} + for v in ielems (ls) do + t[v[1]] = v[2] + end + return t +end + + +local function enpair (t) + local ls = List {} + for i, v in pairs (t) do + ls[#ls + 1] = List {i, v} + end + return ls +end + + local function filter (pfn, l) local r = List {} for e in ielems (l) do @@ -441,6 +427,11 @@ m.transpose = DEPRECATED ("38", "'std.list:transpose'", transpose) m.zip_with = DEPRECATED ("38", "'std.list:zip_with'", zip_with) +M.depair = DEPRECATED ("41", "'std.list.depair'", depair) + +M.enpair = DEPRECATED ("41", "'std.list.enpair'", enpair) +m.enpair = DEPRECATED ("41", "'std.list:enpair'", enpair) + M.elems = DEPRECATED ("41", "'std.list.elems'", "use 'std.ielems' instead", base.ielems) m.elems = DEPRECATED ("41", "'std.list:elems'", diff --git a/lib/std/table.lua b/lib/std/table.lua index 885aa59..df7b373 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -13,8 +13,8 @@ local base = require "std.base" -local export, getmetamethod, ipairs, pairs = - base.export, base.getmetamethod, base.ipairs, base.pairs +local export, getmetamethod, ielems, ipairs, pairs = + base.export, base.getmetamethod, base.ielems, base.ipairs, base.pairs local collect = base.functional.collect local leaves = base.tree.leaves @@ -113,6 +113,36 @@ export (M, "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 `{{i1, v1}, ..., {in, vn}}` +-- @treturn table a new list containing table `{i1=v1, ..., in=vn}` +-- @see enpair +export (M, "depair (list of lists)", function (ls) + local t = {} + for v in ielems (ls) do + t[v[1]] = v[2] + end + return t +end) + + +--- 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 +export (M, "enpair (table)", function (t) + local tt = {} + for i, v in pairs (t) do + tt[#tt + 1] = {i, v} + end + return tt +end) + + --- Return whether table is empty. -- @function empty -- @tparam table t any table diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index ba9f3f7..d7b2c10 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -363,8 +363,8 @@ specify std.list: - describe depair: - before: + l = List {List {1, "first"}, List {2, "second"}, List {"third", 4}} t = {"first", "second", third = 4} - l = M.enpair (t) fname = "depair" @@ -372,18 +372,9 @@ specify std.list: - before: f, badarg = init (M, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (List {0})). - to_raise (badarg (1, "List of Lists", "number at index 1")) - expect (f (List {{}})). - to_raise (badarg (1, "List of Lists", "empty table at index 1")) - expect (f (List { List {"a", "b"}, ""})). - to_raise (badarg (1, "List of Lists", "string at index 2")) - - it diagnoses too many arguments: - expect (f (List { List {"a", "b"} }, false)).to_raise (badarg (2)) + - it writes a deprecation warning on first call: + expect (capture (f, {l})).to_contain_error "was deprecated" + expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a primitive table: expect (prototype (f (l))).to_be "table" @@ -457,12 +448,10 @@ specify std.list: - context as a module function: - before: f, badarg = init (M, fname) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + + - it writes a deprecation warning on first call: + expect (capture (f, {t})).to_contain_error "was deprecated" + expect (capture (f, {t})).not_to_contain_error "was deprecated" - it returns a List object: expect (prototype (f (t))).to_be "List" diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 57e8511..cd3a012 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -3,11 +3,11 @@ before: | this_module = "std.table" global_table = "_G" - extend_base = { "clone", "clone_rename", "clone_select", "empty", - "flatten", "invert", "keys", "merge", "merge_select", - "metamethod", "monkey_patch", "new", "pack", - "project", "ripairs", "shape", "size", "sort", - "totable", "values" } + extend_base = { "clone", "clone_rename", "clone_select", "depair", + "empty", "enpair", "flatten", "invert", "keys", + "merge", "merge_select", "metamethod", + "monkey_patch", "new", "pack", "project", "ripairs", + "shape", "size", "sort", "totable", "values" } M = require "std.table" @@ -151,6 +151,32 @@ specify std.table: expect (getmetatable (f (withmt, {"k1"}, ":nometa"))).to_be (nil) +- describe depair: + - before: + t = {"first", "second", third = 4} + l = M.enpair (t) + + f, badarg = init (M, "depair") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "list")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "list", "boolean")) + expect (f {0}). + to_raise (badarg (1, "list of lists", "number at index 1")) + expect (f {{"a", "b"}, ""}). + to_raise (badarg (1, "list of lists", "string at index 2")) + - it diagnoses too many arguments: + expect (f ({{"a", "b"}}, false)).to_raise (badarg (2)) + + - it returns a primitive table: + expect (prototype (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, badarg = init (M, "empty") @@ -171,6 +197,30 @@ specify std.table: expect (f {false}).to_be (false) +- describe enpair: + - before: + t = {"first", "second", third = 4} + l = M.enpair (t) + + f, badarg = init (M, "enpair") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_raise (badarg (2)) + + - it returns a table: + expect (prototype (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 flatten: - before: t = {{{"one"}}, "two", {{"three"}, "four"}} From a51c032ce50df62e52889de5781b12beb7c92cb7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 Aug 2014 22:47:09 +0100 Subject: [PATCH 384/703] maint: prepare lib/std/list.lua for deprecation-apocalypse! Almost half of lib/std/list.lua is only there to take care of warning about deprecated usage. * lib/std/list.lua: Group all deprecation support code in one huge block ready for quick and easy annihilation in due course. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 129 +++++++++++++---------------------------------- 1 file changed, 36 insertions(+), 93 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 82c7f15..8b47e01 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -9,16 +9,15 @@ local list = require "std.list" -- module table local List = list {} -- prototype object local l = List {"foo", "bar"} - for e in ielems (l:append ("baz")) do print (e) end + for e in ielems (l:cons ("baz")) do print (e) end => foo => bar => baz - ... some can also be called as module functions with an explicit list - argument in the first or last parameter, check the documentation for - details: + ...they can also be called as module functions with an explicit list + argument in the first parameter: - for e in ielems (list.append (l, "quux")) do print (e) end + for e in ielems (list.cons (l, "quux")) do print (e) end => foo => bar => quux @@ -37,29 +36,9 @@ local List -- forward declaration local ipairs, pairs = base.ipairs, base.pairs local export, ielems, prototype = base.export, base.ielems, base.prototype -local M = { "std.list" } - - - ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- +local M = { "std.list" } --- Support for DEPRECATED apis. -- - -local ielems = base.ielems - -local function map (fn, l) - local r = List {} - for e in ielems (l) do - local v = fn (e) - if v ~= nil then - r[#r + 1] = v - end - end - return r -end --[[ ================= ]]-- @@ -192,74 +171,14 @@ end) ---[[ =============== ]]-- ---[[ Object Methods. ]]-- ---[[ =============== ]]-- - - -local m = { "std.list", "List" } - - ---- Append an item to a list. --- @function append --- @param x item --- @treturn List new list containing `{self[1], ..., self[#self], x}` -export (m, "append (any)", append) - - ---- Compare two lists element-by-element, from left-to-right. --- --- if a_list:compare (another_list) == 0 then print "same" end --- @function compare --- @tparam table l a list --- @return -1 if `self` is less than `l`, 0 if they are the same, and 1 --- if `self` is greater than `l` -export (m, "compare (List|table)", compare) - - ---- Concatenate arguments into a list. --- @function concat --- @param ... tuple of lists --- @treturn List new list containing --- `{self[1], ..., self[#self], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` -export (m, "concat (List|table*)", concat) - - ---- Prepend an item to a list. --- @function cons --- @param x item --- @treturn List new list containing `{x, unpack (self)}` -export (m, "cons (any)", cons) - - ---- Repeat a list. --- @function rep --- @int n number of times to repeat --- @treturn List `n` copies of `self` appended together -export (m, "rep (int)", rep) - - ---- Return a sub-range of a list. --- (The equivalent of `string.sub` on strings; negative list indices --- count from the end of the list.) --- @function sub --- @int from start of range (default: 1) --- @int to end of range (default: `#self`) --- @treturn List new list containing `{self[from], ..., self[to]}` -export (m, "sub (int?, int?)", sub) - - ---- Return a list with its first element removed. --- @function tail --- @treturn List new list containing `{self[2], ..., self[#self]}` -export (m, "tail ()", tail) - - - --[[ ============= ]]-- --[[ Deprecations. ]]-- --[[ ============= ]]-- +-- This entire section can be deleted in due course, with just one +-- additional small correction noted in FIXME comments in the List +-- object constructor at the end of this file. + local DEPRECATED = base.DEPRECATED @@ -284,7 +203,7 @@ end local function filter (pfn, l) local r = List {} - for e in ielems (l) do + for e in base.ielems (l) do if pfn (e) then r[#r + 1] = e end @@ -347,6 +266,18 @@ local function index_value (f, l) end +local function map (fn, l) + local r = List {} + for e in base.ielems (l) do + local v = fn (e) + if v ~= nil then + r[#r + 1] = v + end + end + return r +end + + local function map_with (fn, ls) return map (function (...) return fn (unpack (...)) end, ls) end @@ -420,6 +351,18 @@ local function zip_with (ls, fn) end +local m = { "std.list", "List" } + + +export (m, "append (any)", append) +export (m, "compare (List|table)", compare) +export (m, "concat (List|table*)", concat) +export (m, "cons (any)", cons) +export (m, "rep (int)", rep) +export (m, "sub (int?, int?)", sub) +export (m, "tail ()", tail) + + m.depair = DEPRECATED ("38", "'std.list:depair'", depair) m.map_with = DEPRECATED ("38", "'std.list:map_with'", function (self, fn) return map_with (fn, self) end) @@ -533,8 +476,8 @@ M.zip_with = DEPRECATED ("41", "'std.list.zip_with'", List = Object { -- Derived object type. _type = "List", - _functions = M, - __index = m, + _functions = M, -- FIXME: remove this when DEPRECATIONS have gone + __index = m, -- FIXME: `__index = M` when DEPRECATIONS have gone ------ -- Concatenate lists. From c93a15d1626a7e83fa26a63fba2526a81b710170 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 21 Aug 2014 13:08:34 +0100 Subject: [PATCH 385/703] specs: break dependency on M[1], M[2] in badarg. That is, polluting the exported module tables with the module name (M = {"std.base"}) and object name (M = {"std.list", "List"}) for argument error message text generation is ugly. * specs/spec_helper.lua (badarg): Require an explicit module name arg, and use it instead of relying on M[1]. * specs/base_spec.yaml, specs/debug_spec.yaml, specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/math_spec.yaml, specs/package_spec.yaml, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Pass the explicit module name arg to badarg calls. * specs/list_spec.yaml: The goal is not to output different error messages when called with ':' syntax than with '.' syntax. Move module function argument check specifications into higher scope; remove object method argument check specifications entirely. Signed-off-by: Gary V. Vaughan --- specs/base_spec.yaml | 38 +--- specs/debug_spec.yaml | 8 +- specs/functional_spec.yaml | 30 ++-- specs/io_spec.yaml | 22 +-- specs/list_spec.yaml | 347 ++++++++++++------------------------- specs/math_spec.yaml | 6 +- specs/package_spec.yaml | 10 +- specs/spec_helper.lua | 17 +- specs/std_spec.yaml | 26 +-- specs/string_spec.yaml | 36 ++-- specs/table_spec.yaml | 36 ++-- 11 files changed, 215 insertions(+), 361 deletions(-) diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index 1fc9c85..c9d42a5 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -17,7 +17,7 @@ specify std.base: ) end - f, badarg = init (M, "DEPRECATED") + f, badarg = init (M, this_module, "DEPRECATED") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -107,7 +107,7 @@ specify std.base: mkmagic = function () return "MAGIC" end - f, badarg = init (M, "export") + f, badarg = init (M, this_module, "export") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -149,7 +149,7 @@ specify std.base: - context when checking zero argument function: - before: M = { "base_spec.yaml" } - _, badarg = init (M, "chk_function") + _, badarg = init (M, "base_spec.yaml", "chk_function") f (M, "chk_function ()", mkmagic) chk = M.chk_function - it diagnoses too many arguments: @@ -160,7 +160,7 @@ specify std.base: - context when checking single argument function: - before: M = { "base_spec.yaml" } - _, badarg = init (M, "chk_function") + _, badarg = init (M, "base_spec.yaml", "chk_function") f (M, "chk_function (#table)", mkmagic) chk = M.chk_function - it diagnoses missing arguments: @@ -175,7 +175,7 @@ specify std.base: - context when checking multi-argument function: - before: M = { "base_spec.yaml" } - _, badarg = init (M, "chk_function") + _, badarg = init (M, "base_spec.yaml", "chk_function") f (M, "chk_function (table, function)", mkmagic) chk = M.chk_function - it diagnoses missing arguments: @@ -192,7 +192,7 @@ specify std.base: - context when checking optional argument function: - before: M = { "base_spec.yaml" } - _, badarg = init (M, "chk_function") + _, badarg = init (M, "base_spec.yaml", "chk_function") f (M, "chk_function ([int])", mkmagic) chk = M.chk_function - it diagnoses wrong argument types: @@ -206,7 +206,7 @@ specify std.base: - context when checking optional multi-argument function: - before: M = { "base_spec.yaml" } - _, badarg = init (M, "chk_function") + _, badarg = init (M, "base_spec.yaml", "chk_function") f (M, "chk_function ([int], string)", mkmagic) chk = M.chk_function - it diagnoses missing arguments: @@ -219,27 +219,3 @@ specify std.base: - it accepts correct argument types: expect (chk ("two")).to_be "MAGIC" expect (chk (1, "two")).to_be "MAGIC" - - - context when checking self on an object method: - - before: - M = { "base_spec.yaml", "Object" } - _, badarg = init (M, "chk_method") - f (M, "chk_method (string)", function (self, x) return x end) - Object = require "std.object" {} - Bad = Object { _type = "Bad" } - obj = Object { chk = M.chk_method } - - it diagnoses missing arguments: - expect (obj.chk ()).to_raise (badarg (0, "Object")) - expect (obj:chk ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (obj.chk ({})).to_raise (badarg (0, "Object", "empty table")) - expect (obj.chk (Bad)).to_raise (badarg (0, "Object", "Bad")) - expect (obj:chk ({})).to_raise (badarg (1, "string", "empty table")) - expect (obj:chk (obj)).to_raise (badarg (1, "string", "Object")) - - it diagnoses too many arguments: - expect (obj.chk (obj, "str", false)).to_raise (badarg (2)) - expect (obj:chk ("str", false)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (obj.chk (obj, "str")).to_be "str" - expect (obj.chk (Object, "str")).to_be "str" - expect (obj:chk ("str")).to_be "str" diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 3bb5b0c..f12ef92 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -46,7 +46,7 @@ specify std.debug: ]], tostring (level)) end - f, badarg = init (M, "argerror") + f, badarg = init (M, this_module, "argerror") - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" @@ -100,7 +100,7 @@ specify std.debug: ]], tostring (debugp), tostring (level)) end - f, badarg = init (M, "argcheck") + f, badarg = init (M, this_module, "argcheck") - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" @@ -440,7 +440,7 @@ specify std.debug: ]], tostring (debugp)) end - f, badarg = init (M, "argscheck") + f, badarg = init (M, this_module, "argscheck") - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" @@ -647,7 +647,7 @@ specify std.debug: - describe trace: - before: - f = init (M, "trace") + f = init (M, this_module, "trace") - it does nothing when _DEBUG is disabled: expect (luaproc [[ diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 76b5644..d8df396 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -31,7 +31,7 @@ specify std.functional: - describe bind: - before: fname = "bind" - f, badarg = init (M, fname) + f, badarg = init (M, this_module, fname) - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -85,7 +85,7 @@ specify std.functional: default = function (s) return s end branches = { yes = yes, no = no, default } - f, badarg = init (M, "case") + f, badarg = init (M, this_module, "case") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (2, "non-empty table")) @@ -125,7 +125,7 @@ specify std.functional: - describe collect: - before: - f, badarg = init (M, "collect") + f, badarg = init (M, this_module, "collect") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function or any value")) @@ -141,7 +141,7 @@ specify std.functional: - describe compose: - before: - f, badarg = init (M, "compose") + f, badarg = init (M, this_module, "compose") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -186,7 +186,7 @@ specify std.functional: - describe curry: - before: - f, badarg = init (M, "curry") + f, badarg = init (M, this_module, "curry") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -231,7 +231,7 @@ specify std.functional: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) - f, badarg = init (M, "filter") + f, badarg = init (M, this_module, "filter") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -289,7 +289,7 @@ specify std.functional: - describe foldl: - before: - f, badarg = init (M, "foldl") + f, badarg = init (M, this_module, "foldl") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -313,7 +313,7 @@ specify std.functional: - describe foldr: - before: - f, badarg = init (M, "foldr") + f, badarg = init (M, this_module, "foldr") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -347,7 +347,7 @@ specify std.functional: - describe lambda: - before: - f, badarg = init (M, "lambda") + f, badarg = init (M, this_module, "lambda") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -386,7 +386,7 @@ specify std.functional: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) - f, badarg = init (M, "map") + f, badarg = init (M, this_module, "map") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -428,7 +428,7 @@ specify std.functional: t = {{1, 2, 3}, {4, 5}} fn = function (...) return select ("#", ...) end - f, badarg = init (M, "map_with") + f, badarg = init (M, this_module, "map_with") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -462,7 +462,7 @@ specify std.functional: - describe memoize: - before: - f, badarg = init (M, "memoize") + f, badarg = init (M, this_module, "memoize") memfn = f (function (x) if x then return {x} else return nil, "bzzt" end @@ -514,7 +514,7 @@ specify std.functional: - describe reduce: - before: - f, badarg = init (M, "reduce") + f, badarg = init (M, this_module, "reduce") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -542,7 +542,7 @@ specify std.functional: - before: tt = {{1, 2}, {3, 4}, {5, 6}} - f, badarg = init (M, "zip") + f, badarg = init (M, this_module, "zip") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -566,7 +566,7 @@ specify std.functional: tt = {{1, 2}, {3, 4}, {5}} fn = function (...) return tonumber (table.concat {...}) end - f, badarg = init (M, "zip_with") + f, badarg = init (M, this_module, "zip_with") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 173bd7b..8365edc 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -35,7 +35,7 @@ specify std.io: - describe catdir: - before: | - f, badarg = init (M, "catdir") + f, badarg = init (M, this_module, "catdir") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -58,7 +58,7 @@ specify std.io: - describe catfile: - before: - f, badarg = init (M, "catfile") + f, badarg = init (M, this_module, "catfile") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -83,7 +83,7 @@ specify std.io: - before: | script = [[require "std.io".die "By 'eck!"]] - f, badarg = init (M, "die") + f, badarg = init (M, this_module, "die") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -152,7 +152,7 @@ specify std.io: }, } - f, badarg = init (M, "monkey_patch") + f, badarg = init (M, this_module, "monkey_patch") - it diagnoses wrong argument types: expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) @@ -184,7 +184,7 @@ specify std.io: require "std.io".process_files (function () io.write (io.input ():read "*a") end) ]] - f, badarg = init (M, "process_files") + f, badarg = init (M, this_module, "process_files") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "function")) @@ -226,7 +226,7 @@ specify std.io: defaultin = io.input () - f, badarg = init (M, "readlines") + f, badarg = init (M, this_module, "readlines") - after: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end @@ -257,7 +257,7 @@ specify std.io: - describe shell: - before: - f, badarg = init (M, "shell") + f, badarg = init (M, this_module, "shell") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -278,7 +278,7 @@ specify std.io: h:close () defaultin = io.input () - f, badarg = init (M, "slurp") + f, badarg = init (M, this_module, "slurp") - after: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end @@ -309,7 +309,7 @@ specify std.io: - describe splitdir: - before: - f, badarg = init (M, "splitdir") + f, badarg = init (M, this_module, "splitdir") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -333,7 +333,7 @@ specify std.io: - describe warn: - before: script = [[require "std.io".warn "Ayup!"]] - f, badarg = init (M, "warn") + f, badarg = init (M, this_module, "warn") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -395,7 +395,7 @@ specify std.io: lines = M.readlines (io.open "Makefile") defaultout = io.output () - f, badarg = init (M, "writelines") + f, badarg = init (M, this_module, "writelines") - after: if io.type (defaultout) ~= "closed file" then io.output (defaultout) end h:close () diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index d7b2c10..d9fd194 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -75,20 +75,17 @@ specify std.list: - describe append: - before: - fname = "append" + f, badarg = init (M, this_module, "append") - - context as a module function: - - before: - f, badarg = init (M, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 42, false)).to_raise (badarg (3)) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "any value")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 42, false)).to_raise (badarg (3)) + - context as a module function: - it returns a List object: expect (prototype (f (l, "quux"))).to_be "List" - it works for an empty List: @@ -99,15 +96,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - expect (f (l)).to_raise (badarg (1, "any value")) - - it diagnoses wrong argument types: - expect (f ({"foo", "bar"})).to_raise (badarg (0, "List", "table")) - - it diagnoses too many arguments: - expect (f (l, "bar", false)).to_raise (badarg (2)) + f = l.append - it returns a List object: expect (prototype (f (l, "quux"))).to_be "List" @@ -131,21 +120,18 @@ specify std.list: - before: a, b = List {"foo", "bar"}, List {"foo", "baz"} - fname = "compare" - - - context as a module function: - - before: - f, badarg = init (M, fname) + f, badarg = init (M, this_module, "compare") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (a, false)).to_raise (badarg (2, "List or table", "boolean")) - - it diagnoses too many arguments: - expect (f (a, b, false)).to_raise (badarg (3)) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (a, false)).to_raise (badarg (2, "List or table", "boolean")) + - it diagnoses too many arguments: + expect (f (a, b, false)).to_raise (badarg (3)) + - context as a module function: - it returns -1 when the first list is less than the second: expect (f (a, {"foo", "baz"})).to_be (-1) expect (f (a, List {"foo", "baz"})).to_be (-1) @@ -167,16 +153,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = a[fname], init ({M[1], M[2]}, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - expect (f (l)).to_raise (badarg (1, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - expect (f (a, false)).to_raise (badarg (1, "List or table", "boolean")) - - it diagnoses too many arguments: - expect (f (a, b, false)).to_raise (badarg (2)) + f = a.compare - it returns -1 when the first list is less than the second: expect (f (a, {"foo", "baz"})).to_be (-1) @@ -243,21 +220,18 @@ specify std.list: - before: l = List {"foo", "bar"} - fname = "concat" - - - context as a module function: - - before: - f, badarg = init (M, fname) + f, badarg = init (M, this_module, "concat") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "List or table", "boolean")) - expect (f (l, l, false)). - to_raise (badarg (3, "List or table", "boolean")) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "List or table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "List or table", "boolean")) + expect (f (l, l, false)). + to_raise (badarg (3, "List or table", "boolean")) + - context as a module function: - it returns a List object: expect (prototype (f (l, l))).to_be "List" - it works for an empty List: @@ -275,16 +249,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - expect (f (l)).to_raise (badarg (1, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (1, "List or table", "boolean")) - expect (f (l, l, false)). - to_raise (badarg (2, "List or table", "boolean")) + f = l.concat - it returns a List object: expect (prototype (f (l, l))).to_be "List" @@ -321,20 +286,17 @@ specify std.list: - describe cons: - before: - fname = "cons" - - - context as a module function: - - before: - f, badarg = init (M, fname) + f, badarg = init (M, this_module, "cons") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (List {})).to_raise (badarg (2, "any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (List {}, "x", false)).to_raise (badarg (3)) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (List {})).to_raise (badarg (2, "any value")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (List {}, "x", false)).to_raise (badarg (3)) + - context as a module function: - it returns a List object: expect (prototype (f (l, "x"))).to_be "List" - it prepends an item to a List: @@ -344,14 +306,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, "x", false)).to_raise (badarg (2)) + f = l.cons - it returns a List object: expect (prototype (f (l, "x"))).to_be "List" @@ -366,11 +321,9 @@ specify std.list: l = List {List {1, "first"}, List {2, "second"}, List {"third", 4}} t = {"first", "second", third = 4} - fname = "depair" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.depair - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -386,7 +339,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.depair - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -401,12 +354,9 @@ specify std.list: - describe elems: - - before: - fname = "elems" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.elems - it writes a deprecation warning on first call: expect (capture (f, {{}})).to_contain_error "was deprecated" @@ -423,7 +373,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.elems - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -442,17 +392,13 @@ specify std.list: - describe enpair: - before: t = {"first", "second", third = 4} + f = M.enpair - fname = "enpair" + - it writes a deprecation warning on first call: + expect (capture (f, {t})).to_contain_error "was deprecated" + expect (capture (f, {t})).not_to_contain_error "was deprecated" - context as a module function: - - before: - f, badarg = init (M, fname) - - - it writes a deprecation warning on first call: - expect (capture (f, {t})).to_contain_error "was deprecated" - expect (capture (f, {t})).not_to_contain_error "was deprecated" - - it returns a List object: expect (prototype (f (t))).to_be "List" - it works for an empty table: @@ -467,11 +413,9 @@ specify std.list: l = List {"foo", "bar", "baz", "quux"} p = function (e) return (e:match "a" ~= nil) end - fname = "filter" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.filter - it writes a deprecation warning on first call: expect (capture (f, {p, l})).to_contain_error "was deprecated" @@ -486,7 +430,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.filter - it writes a deprecation warning on first call: expect (capture (f, {l, p})).to_contain_error "was deprecated" @@ -504,11 +448,9 @@ specify std.list: - before: l = List {List {List {"one"}}, "two", List {List {"three"}, "four"}} - fname = "flatten" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.flatten - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -525,7 +467,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.flatten - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -544,17 +486,16 @@ specify std.list: - describe foldl: - before: op = require "std.operator" - - fname = "foldl" + l = List {3, 4} - context as a module function: - before: - f, badarg = init (M, fname) + f = M.foldl - it writes a deprecation warning on first call: - expect (capture (f, {op["+"], 1, {10}})). + expect (capture (f, {op["+"], 1, l})). to_contain_error "was deprecated" - expect (capture (f, {op["+"], 1, {10}})). + expect (capture (f, {op["+"], 1, l})). not_to_contain_error "was deprecated" - context with a table: @@ -576,9 +517,7 @@ specify std.list: - context as an object method: - before: - l = List {3, 4} - - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.foldl - it writes a deprecation warning on first call: expect (capture (f, {l, op["+"], 1})). @@ -598,12 +537,11 @@ specify std.list: - describe foldr: - before: op = require "std.operator" - - fname = "foldr" + l = List {10000, 100} - context as a module function: - before: - f, badarg = init (M, fname) + f = M.foldr - it writes a deprecation warning on first call: expect (capture (f, {op["+"], 1, {10}})). @@ -631,9 +569,7 @@ specify std.list: - context as an object method: - before: - l = List {10000, 100} - - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.foldr - it writes a deprecation warning on first call: expect (capture (f, {l, op["+"], 1})). @@ -651,12 +587,9 @@ specify std.list: - describe index_key: - - before: - fname = "index_key" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.index_key - it writes a deprecation warning on first call: expect (capture (f, {1, List {{1}}})). @@ -685,7 +618,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.index_key - it writes a deprecation warning on first call: expect (capture (f, {l, 1})).to_contain_error "was deprecated" @@ -712,12 +645,9 @@ specify std.list: - describe index_value: - - before: - fname = "index_value" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.index_value - it writes a deprecation warning on first call: expect (capture (f, {1, List {{1}}})). @@ -748,7 +678,7 @@ specify std.list: - before: l = List {{1}} - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.index_value - it writes a deprecation warning on first call: expect (capture (f, {l, 1})).to_contain_error "was deprecated" @@ -779,11 +709,9 @@ specify std.list: l = List {1, 2, 3, 4, 5} sq = function (n) return n * n end - fname = "map" - - context as a module function: - before: - f, badarg = init (M, fname) + f, badarg = init (M, this_module, "map") - it writes a deprecation warning on first call: expect (capture (f, {sq, l})).to_contain_error "was deprecated" @@ -804,7 +732,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.map - it writes a deprecation warning on first call: expect (capture (f, {l, sq})).to_contain_error "was deprecated" @@ -830,11 +758,9 @@ specify std.list: l = List {List {1, 2, 3}, List {4, 5}} fn = function (...) return select ("#", ...) end - fname = "map_with" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.map_with - it writes a deprecation warning on first call: expect (capture (f, {fn, l})).to_contain_error "was deprecated" @@ -857,7 +783,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.map_with - it writes a deprecation warning on first call: expect (capture (f, {l, fn})).to_contain_error "was deprecated" @@ -887,11 +813,9 @@ specify std.list: {first = "1st", second = "2nd", third = "3rd"}, } - fname = "project" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.project - it writes a deprecation warning on first call: expect (capture (f, {"third", l})).to_contain_error "was deprecated" @@ -908,7 +832,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.project - it writes a deprecation warning on first call: expect (capture (f, {l, "third"})).to_contain_error "was deprecated" @@ -925,12 +849,9 @@ specify std.list: - describe relems: - - before: - fname = "relems" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.relems - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -947,7 +868,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.relems - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -967,21 +888,18 @@ specify std.list: - before: l = List {"foo", "bar"} - fname = "rep" + f, badarg = init (M, this_module, "rep") - - context as a module function: - - before: - f, badarg = init (M, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "int", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 2, false)).to_raise (badarg (3)) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + expect (f (l)).to_raise (badarg (2, "int")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "int", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 2, false)).to_raise (badarg (3)) + - context as a module function: - it returns a List object: expect (prototype (f (l, 3))).to_be "List" - it works for an empty List: @@ -992,16 +910,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - expect (f (l)).to_raise (badarg (1, "int")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (1, "int", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 2, false)).to_raise (badarg (2)) + f = l.rep - it returns a List object: expect (prototype (f (l, 3))).to_be "List" @@ -1016,11 +925,9 @@ specify std.list: - before: l = List {"foo", "bar", "baz", "quux"} - fname = "reverse" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.reverse - it writes a deprecation warning on first call: expect (capture (f, {{}})).to_contain_error "was deprecated" @@ -1040,7 +947,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.reverse - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -1062,11 +969,9 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6} - fname = "shape" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.shape - it writes a deprecation warning on first call: expect (capture (f, {{0}, l})).to_contain_error "was deprecated" @@ -1094,7 +999,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.shape - it writes a deprecation warning on first call: expect (capture (f, {l, {0}})).to_contain_error "was deprecated" @@ -1125,21 +1030,18 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6, 7} - fname = "sub" - - - context as a module function: - - before: - f, badarg = init (M, fname) + f, badarg = init (M, this_module, "sub") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "int or nil", "boolean")) - expect (f (l, 1, false)).to_raise (badarg (3, "int or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 1, 2, false)).to_raise (badarg (4)) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + expect (f (l, false)).to_raise (badarg (2, "int or nil", "boolean")) + expect (f (l, 1, false)).to_raise (badarg (3, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (f (l, 1, 2, false)).to_raise (badarg (4)) + - context as a module function: - it returns a List object: expect (prototype (f (l, 1, 1))).to_be "List" - it makes a List from a subrange of another List: @@ -1159,16 +1061,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (1, "int or nil", "boolean")) - expect (f (l, 1, false)).to_raise (badarg (2, "int or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 1, 2, false)).to_raise (badarg (3)) + f = l.sub - it returns a List object: expect (prototype (f (l, 1, 1))).to_be "List" @@ -1192,19 +1085,16 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6, 7} - fname = "tail" + f, badarg = init (M, this_module, "tail") - - context as a module function: - - before: - f, badarg = init (M, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, false)).to_raise (badarg (2)) + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "List")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "List", "boolean")) + - it diagnoses too many arguments: + expect (f (l, false)).to_raise (badarg (2)) + - context as a module function: - it returns a List object: expect (prototype (f (l))).to_be "List" - it makes a new List with the first element removed: @@ -1216,14 +1106,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (0, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (0, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, false)).to_raise (badarg (1)) + f = l.tail - it returns a List object: expect (prototype (f (l))).to_be "List" @@ -1239,11 +1122,9 @@ specify std.list: - before: l = List {List {1, 2}, List {3, 4}, List {5, 6}} - fname = "transpose" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.transpose - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -1263,7 +1144,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.transpose - it writes a deprecation warning on first call: expect (capture (f, {l})).to_contain_error "was deprecated" @@ -1288,11 +1169,9 @@ specify std.list: l = List {List {1, 2}, List {3, 4}, List {5}} fn = function (...) return tonumber (table.concat {...}) end - fname = "zip_with" - - context as a module function: - before: - f, badarg = init (M, fname) + f = M.zip_with - it writes a deprecation warning on first call: expect (capture (f, {l, fn})).to_contain_error "was deprecated" @@ -1312,7 +1191,7 @@ specify std.list: - context as an object method: - before: - f, _, badarg = l[fname], init ({M[1], M[2]}, fname) + f = l.zip_with - it writes a deprecation warning on first call: expect (capture (f, {l, fn})).to_contain_error "was deprecated" diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index f254bde..46d5ac8 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -31,7 +31,7 @@ specify std.math: - describe floor: - before: - f, badarg = init (M, "floor") + f, badarg = init (M, this_module, "floor") - it diagnoses missing arguments: | expect (f ()).to_raise (badarg (1, "number")) @@ -62,7 +62,7 @@ specify std.math: math = {}, } - f, badarg = init (M, "monkey_patch") + f, badarg = init (M, this_module, "monkey_patch") - it diagnoses wrong argument types: | expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) @@ -76,7 +76,7 @@ specify std.math: - describe round: - before: - f, badarg = init (M, "round") + f, badarg = init (M, this_module, "round") - it diagnoses missing arguments: | expect (f ()).to_raise (badarg (1, "number")) diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index fd9b418..ad537d3 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -41,7 +41,7 @@ specify std.package: - before: | path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) - f, badarg = init (M, "find") + f, badarg = init (M, this_module, "find") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -73,7 +73,7 @@ specify std.package: - describe insert: - before: - f, badarg = init (M, "insert") + f, badarg = init (M, this_module, "insert") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -108,7 +108,7 @@ specify std.package: - before: | expected = require "std.string".split (path, M.pathsep) - f, badarg = init (M, "mappath") + f, badarg = init (M, this_module, "mappath") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -133,7 +133,7 @@ specify std.package: - describe normalize: - before: - f, badarg = init (M, "normalize") + f, badarg = init (M, this_module, "normalize") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -185,7 +185,7 @@ specify std.package: - describe remove: - before: - f, badarg = init (M, "remove") + f, badarg = init (M, this_module, "remove") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 649f9b6..343a823 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -98,17 +98,16 @@ end -- @string want expected argument type -- @string[opt="no value"] got actual argument type -- @usage --- expect (f ()).to_error (badarg (M, name, 1, "function")) -local function badarg (M, fname, i, want, got) - fname = tostring (M[1]) .. (rawget (M, 2) and ":" or ".") .. fname +-- expect (f ()).to_error (badarg (fname, mname, 1, "function")) +local function badarg (mname, fname, i, want, got) if want == nil then i, want = i - 1, i end if got == nil and type (want) == "number" then - return string.format ("too many arguments to '%s' (no more than %d expected, got %d)", - fname, i, want) + local s = "too many arguments to '%s.%s' (no more than %d expected, got %d)" + return string.format (s, mname, fname, i, want) end - return string.format ("bad argument #%d to '%s' (%s expected, got %s)", - i, fname, want, got or "no value") + return string.format ("bad argument #%d to '%s.%s' (%s expected, got %s)", + i, mname, fname, want, got or "no value") end @@ -119,8 +118,8 @@ end -- @treturn function `M[fname]` if any, otherwise `nil` -- @treturn function badarg with *M* and *fname* prebound -- @treturn function toomanyarg with *M* and *fname* prebound -function init (M, fname) - return M[fname], bind (badarg, {M, fname}) +function init (M, mname, fname) + return M[fname], bind (badarg, {mname, fname}) end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 24987f1..e8cb8f8 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -51,7 +51,7 @@ specify std: - describe assert: - before: - f, badarg = init (M, "assert") + f, badarg = init (M, this_module, "assert") - it diagnoses wrong argument types: expect (f (false, false)).to_raise (badarg (2, "string or nil", "boolean")) @@ -92,7 +92,7 @@ specify std: table = {}, } - f, badarg = init (M, "barrel") + f, badarg = init (M, this_module, "barrel") f (t) @@ -165,7 +165,7 @@ specify std: - describe elems: - before: - f, badarg = init (M, "elems") + f, badarg = init (M, this_module, "elems") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -193,7 +193,7 @@ specify std: - describe eval: - before: - f, badarg = init (M, "eval") + f, badarg = init (M, this_module, "eval") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -211,7 +211,7 @@ specify std: - describe getmetamethod: - before: - f, badarg = init (M, "getmetamethod") + f, badarg = init (M, this_module, "getmetamethod") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "object or table")) @@ -251,7 +251,7 @@ specify std: - describe ielems: - before: - f, badarg = init (M, "ielems") + f, badarg = init (M, this_module, "ielems") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -284,7 +284,7 @@ specify std: - describe ipairs: - before: - f, badarg = init (M, "ipairs") + f, badarg = init (M, this_module, "ipairs") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -317,7 +317,7 @@ specify std: - describe ireverse: - before: - f, badarg = init (M, "ireverse") + f, badarg = init (M, this_module, "ireverse") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -352,7 +352,7 @@ specify std: table = {}, } - f, badarg = init (M, "monkey_patch") + f, badarg = init (M, this_module, "monkey_patch") f (t) @@ -371,7 +371,7 @@ specify std: - describe pairs: - before: - f, badarg = init (M, "pairs") + f, badarg = init (M, this_module, "pairs") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -399,7 +399,7 @@ specify std: - describe require: - before: - f, badarg = init (M, "require") + f, badarg = init (M, this_module, "require") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -479,7 +479,7 @@ specify std: - describe ripairs: - before: - f, badarg = init (M, "ripairs") + f, badarg = init (M, this_module, "ripairs") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -514,7 +514,7 @@ specify std: - describe tostring: - before: - f, badarg = init (M, "tostring") + f, badarg = init (M, this_module, "tostring") - it diagnoses too many arguments: expect (f (true, false)).to_raise (badarg (2)) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index e7b0c32..deafe62 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -90,7 +90,7 @@ specify std.string: - describe caps: - before: - f, badarg = init (M, "caps") + f, badarg = init (M, this_module, "caps") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -116,7 +116,7 @@ specify std.string: - before: target = "a string \n" - f, badarg = init (M, "chomp") + f, badarg = init (M, this_module, "chomp") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -146,7 +146,7 @@ specify std.string: magic[meta:sub (i, i)] = true end - f, badarg = init (M, "escape_pattern") + f, badarg = init (M, this_module, "escape_pattern") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -176,7 +176,7 @@ specify std.string: - describe escape_shell: - before: - f, badarg = init (M, "escape_shell") + f, badarg = init (M, this_module, "escape_shell") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -211,7 +211,7 @@ specify std.string: - before: subject = "abcd" - f, badarg = init (M, "finds") + f, badarg = init (M, this_module, "finds") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -253,7 +253,7 @@ specify std.string: - before: subject = "string=%s, number=%d" - f, badarg = init (M, "format") + f, badarg = init (M, this_module, "format") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -274,7 +274,7 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - f, badarg = init (M, "ltrim") + f, badarg = init (M, this_module, "ltrim") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -302,7 +302,7 @@ specify std.string: - describe monkey_patch: - before: - f, badarg = init (M, "monkey_patch") + f, badarg = init (M, this_module, "monkey_patch") t = {} f (t) @@ -324,7 +324,7 @@ specify std.string: - describe numbertosi: - before: - f, badarg = init (M, "numbertosi") + f, badarg = init (M, this_module, "numbertosi") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "number or string")) @@ -348,7 +348,7 @@ specify std.string: - describe ordinal_suffix: - before: - f, badarg = init (M, "ordinal_suffix") + f, badarg = init (M, this_module, "ordinal_suffix") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "int or string")) @@ -378,7 +378,7 @@ specify std.string: - before: width = 20 - f, badarg = init (M, "pad") + f, badarg = init (M, this_module, "pad") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -454,7 +454,7 @@ specify std.string: - describe prettytostring: - before: - f, badarg = init (M, "prettytostring") + f, badarg = init (M, this_module, "prettytostring") - it diagnoses wrong argument types: expect (f (true, false)).to_raise (badarg (2, "string or nil", "boolean")) @@ -507,7 +507,7 @@ specify std.string: t = {1, {{2, 3}, 4, {5}}} a = require "std.vector" {"a", "b", {{"c"},"d"},"e"} - f, badarg = init (M, "render") + f, badarg = init (M, this_module, "render") - it diagnoses missing arguments: expect (f (true)).to_raise (badarg (2, "function")) @@ -604,7 +604,7 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - f, badarg = init (M, "rtrim") + f, badarg = init (M, this_module, "rtrim") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -634,7 +634,7 @@ specify std.string: target = { "first", "the second one", "final entry" } subject = table.concat (target, ", ") - f, badarg = init (M, "split") + f, badarg = init (M, this_module, "split") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -683,7 +683,7 @@ specify std.string: - before: subject = "abc" - f, badarg = init (M, "tfind") + f, badarg = init (M, this_module, "tfind") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -753,7 +753,7 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - f, badarg = init (M, "trim") + f, badarg = init (M, this_module, "trim") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) @@ -787,7 +787,7 @@ specify std.string: "er the MIT license (the same license as Lua itself). There" .. " is no warranty." - f, badarg = init (M, "wrap") + f, badarg = init (M, this_module, "wrap") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "string")) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index cd3a012..e485b22 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -36,7 +36,7 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) - f, badarg = init (M, "clone") + f, badarg = init (M, this_module, "clone") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -113,7 +113,7 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) - f, badarg = init (M, "clone_select") + f, badarg = init (M, this_module, "clone_select") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -156,7 +156,7 @@ specify std.table: t = {"first", "second", third = 4} l = M.enpair (t) - f, badarg = init (M, "depair") + f, badarg = init (M, this_module, "depair") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "list")) @@ -179,7 +179,7 @@ specify std.table: - describe empty: - before: - f, badarg = init (M, "empty") + f, badarg = init (M, this_module, "empty") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -202,7 +202,7 @@ specify std.table: t = {"first", "second", third = 4} l = M.enpair (t) - f, badarg = init (M, "enpair") + f, badarg = init (M, this_module, "enpair") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -225,7 +225,7 @@ specify std.table: - before: t = {{{"one"}}, "two", {{"three"}, "four"}} - f, badarg = init (M, "flatten") + f, badarg = init (M, this_module, "flatten") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -246,7 +246,7 @@ specify std.table: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } - f, badarg = init (M, "invert") + f, badarg = init (M, this_module, "invert") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -270,7 +270,7 @@ specify std.table: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } - f, badarg = init (M, "keys") + f, badarg = init (M, this_module, "keys") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -302,7 +302,7 @@ specify std.table: 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, "merge") + f, badarg = init (M, this_module, "merge") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -369,7 +369,7 @@ specify std.table: 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, "merge_select") + f, badarg = init (M, this_module, "merge_select") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -446,7 +446,7 @@ specify std.table: - describe monkey_patch: - before: - f, badarg = init (M, "monkey_patch") + f, badarg = init (M, this_module, "monkey_patch") t = { table = {}, @@ -464,7 +464,7 @@ specify std.table: - describe new: - before: - f, badarg = init (M, "new") + f, badarg = init (M, this_module, "new") - it diagnoses wrong argument types: expect (f (nil, false)).to_raise (badarg (2, "table or nil", "boolean")) @@ -523,7 +523,7 @@ specify std.table: {first = "1st", second = "2nd", third = "3rd"}, } - f, badarg = init (M, "project") + f, badarg = init (M, this_module, "project") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "any value")) @@ -572,7 +572,7 @@ specify std.table: - before: l = {1, 2, 3, 4, 5, 6} - f, badarg = init (M, "shape") + f, badarg = init (M, this_module, "shape") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -609,7 +609,7 @@ specify std.table: -- - 1 - --------- 2 ---------- -- 3 -- subject = { "one", { { "two" }, "three" }, four = 5 } - f, badarg = init (M, "size") + f, badarg = init (M, this_module, "size") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -630,7 +630,7 @@ specify std.table: target = { 0, 1, 2, 3, 4, 5 } cmp = function (a, b) return a < b end - f, badarg = init (M, "sort") + f, badarg = init (M, this_module, "sort") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) @@ -653,7 +653,7 @@ specify std.table: mt = { _type = "MockObject", __totable = function (self) return self.content end } - f, badarg = init (M, "totable") + f, badarg = init (M, this_module, "totable") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "object, table or string")) @@ -675,7 +675,7 @@ specify std.table: - before: subject = { k1 = {1}, k2 = {2}, k3 = {3} } - f, badarg = init (M, "values") + f, badarg = init (M, this_module, "values") - it diagnoses missing arguments: expect (f ()).to_raise (badarg (1, "table")) From 1db24eedaf65eb5baec7686cefafb52c13370a57 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 21 Aug 2014 19:40:37 +0100 Subject: [PATCH 386/703] functional: have callable return the function rather than a bool. * specs/functional_spec.yaml (callable): Adjust to check that return values are the actual function. * lib/std/base/functional.lua (callable): Return the actual function. Signed-off-by: Gary V. Vaughan --- lib/std/base/functional.lua | 10 ++++++++-- specs/functional_spec.yaml | 11 ++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/std/base/functional.lua b/lib/std/base/functional.lua index 2a4ec46..8d1e5a3 100644 --- a/lib/std/base/functional.lua +++ b/lib/std/base/functional.lua @@ -10,8 +10,14 @@ local function callable (x) - if type (x) == "function" then return true end - return type ((getmetatable (x) or {}).__call) == "function" + if type (x) == "function" then + return x + else + x = (getmetatable (x) or {}).__call + if type (x) == "function" then + return x + end + end end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index d8df396..261dc79 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -64,18 +64,19 @@ specify std.functional: - describe callable: - before: f = M.callable - - it identifies whether objects are callable: + - it returns the function associated with a callable: + Container = require "std.container" { __call = M.nop } for _, v in ipairs { true, 42, "str", io.stderr, {}, - function () end, - setmetatable ({}, {__call = function () end}), - require "std.container", + M.nop, + setmetatable ({}, {__call = M.nop}), + Container, } do - expect (f (v)).to_be (pcall (v, {})) + expect (f (v)).to_be (pcall (v, {}) and M.nop or nil) end - describe case: From 58ce29b6b4de683e9932c6b3ff30f709778ccc16 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 23 Aug 2014 22:22:39 +0100 Subject: [PATCH 387/703] refactor: simplify use of export by looking up local functions. Prior to this changeset, export worked by creating an argument checking wrapper function when called with an inner function, a destination table containing metadata and the argument spec string. The metadata leaked out into the library interface, as well as other assorted clunkiness. Clean up and simplify the whole thing. * specs/spec_helper.lua (badarg): Adjust to produce consolidated error messages. * specs/functional_spec.yaml, specs/list_spec.yaml, specs/std_spec.yaml: Remove element `1` from expected entries in export table, now that metadata isn't leaked. * specs/base_spec.yaml (DEPRECATED, export): Remove argument checking specifications. * lib/std/base.lua (getinfo): New functions. Use `debug.getinfo` to lookup a local function in the given scope given only its name. (whatpath): Cross reference the package.path and function source file from `debug.getinfo` to reverse engineer the module path passed to require that was used to load this function. (export): Remove all vestiges of hard-coded module metadata from the destination table, and passing of an inner function; instead look everything up using introspection and the new functions above. Don't stash the result in a table parameter, return it. Adjust all callers. (copy, render, split): Move from here... * lib/std/base/string.lua (copy, render, split): New file. ...to here, so that base.whatpath reports the correct module path. * local.mk (dist_luastdbase_DATA): Add lib/std/base/string.lua. * lib/std/container.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua (M): Remove metadata, rebuild after exported functions' local declarations. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 2 +- lib/std.lua.in | 56 ++++++---- lib/std/base.lua | 207 ++++++++++++++---------------------- lib/std/base/string.lua | 71 +++++++++++++ lib/std/container.lua | 17 +-- lib/std/debug.lua | 26 +++-- lib/std/functional.lua | 210 +++++++++++++++++++------------------ lib/std/io.lua | 72 ++++++++----- lib/std/list.lua | 81 +++++++------- lib/std/math.lua | 34 +++--- lib/std/package.lua | 85 ++++++++------- lib/std/string.lua | 113 ++++++++++++-------- lib/std/table.lua | 123 ++++++++++++---------- local.mk | 1 + specs/base_spec.yaml | 136 ++++++++---------------- specs/functional_spec.yaml | 2 +- specs/list_spec.yaml | 2 +- specs/spec_helper.lua | 13 ++- specs/std_spec.yaml | 2 +- 19 files changed, 670 insertions(+), 583 deletions(-) create mode 100644 lib/std/base/string.lua diff --git a/NEWS b/NEWS index 65d6c4e..ea37e8d 100644 --- a/NEWS +++ b/NEWS @@ -48,7 +48,7 @@ Stdlib NEWS - User visible changes optional; when omitted these functions automatically start with the left- or right-most element of the table argument resp. - - New `functional.callable` function for detecting objects or + - 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: diff --git a/lib/std.lua.in b/lib/std.lua.in index 975eb98..4f250be 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -25,11 +25,9 @@ local base = require "std.base" -local export = base.export +local M -local M = { "std" } - --- Enhance core `assert` to also allow formatted arguments. -- @function assert @@ -40,7 +38,7 @@ local M = { "std" } -- @usage -- std.assert (expected ~= nil, "100% unexpected!") -- std.assert (expected ~= nil, "%s unexpected!", expected) -export (M, "assert (any?, string?, any?*)", base.assert) +local assert = base.assert --- An iterator over all elements of a sequence. @@ -54,7 +52,7 @@ export (M, "assert (any?, string?, any?*)", base.assert) -- @see pairs -- @usage -- for value in std.elems {a = 1, b = 2, c = 5} do process (value) end -export (M, "elems (table)", base.elems) +local elems = base.elems --- Evaluate a string as Lua code. @@ -62,7 +60,7 @@ export (M, "elems (table)", base.elems) -- @string s string of Lua code -- @return result of evaluating `s` -- @usage std.eval "math.pow (2, 10)" -export (M, "eval (string)", base.eval) +local eval = base.eval --- An iterator over the integer keyed elements of a sequence. @@ -76,7 +74,7 @@ export (M, "eval (string)", base.eval) -- @see ipairs -- @usage -- for v in std.ielems {"a", "b", "c"} do process (v) end -export (M, "ielems (table)", base.ielems) +local ielems = base.ielems --- An iterator over elements `1..#t`, respecting `__len` metamethod. @@ -96,7 +94,7 @@ export (M, "ielems (table)", base.ielems) -- args = {"first", "second", nil, "last"} -- setmetatable (args, { __len = std.functional.lambda "=4" }) -- for i, v in std.ipairs (args) do process (i, v) end -export (M, "ipairs (table)", base.ipairs) +local ipairs = base.ipairs --- Return a new table with element order reversed. @@ -111,7 +109,7 @@ export (M, "ipairs (table)", base.ipairs) -- @usage -- local rielems = std.functional.compose (std.ireverse, std.ielems) -- for e in rielems (l) do process (e) end -export (M, "ireverse (table)", base.ireverse) +local ireverse = base.ireverse --- Return named metamethod, if any, otherwis `nil`. @@ -120,7 +118,7 @@ export (M, "ireverse (table)", base.ireverse) -- @string n name of metamethod to get -- @treturn function|nil metamethod function, or `nil` if no metamethod -- @usage lookup = std.getmetamethod (require "std.object", "__index") -export (M, "getmetamethod (object|table, string)", base.getmetamethod) +local getmetamethod = base.getmetamethod --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. @@ -133,7 +131,7 @@ export (M, "getmetamethod (object|table, string)", base.getmetamethod) -- @see ipairs -- @usage -- for k, v in pairs {"a", b = "c", foo = 42} do process (k, v) end -export (M, "pairs (table)", base.pairs) +local pairs = base.pairs --- Enhance core `require` to assert version number compatibility. @@ -148,7 +146,7 @@ export (M, "pairs (table)", base.pairs) -- @usage -- -- posix.version == "posix library for Lua 5.2 / 32" -- posix = require ("posix", "29") -export (M, "require (string, string?, string?, string?)", base.require) +local require = base.require --- An iterator like ipairs, but in reverse. @@ -160,7 +158,7 @@ export (M, "require (string, string?, string?, string?)", base.require) -- @treturn table *t* -- @treturn number `#t + 1` -- @usage for i, v = ripairs (t) do ... end -export (M, "ripairs (table)", base.ripairs) +local ripairs = base.ripairs --- Enhance core `tostring` to render table contents as a string. @@ -170,7 +168,7 @@ export (M, "ripairs (table)", base.ripairs) -- @usage -- -- {1=baz,foo=bar} -- print (std.tostring {foo="bar","baz"}) -export (M, "tostring (any?)", base.tostring) +local tostring = base.tostring --- Overwrite core methods and metamethods with `std` enhanced versions. @@ -181,7 +179,7 @@ export (M, "tostring (any?)", base.tostring) -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local std = require "std".monkey_patch () -local monkey_patch = export (M, "monkey_patch (table?)", function (namespace) +local function monkey_patch (namespace) namespace = namespace or _G for n, fn in pairs (M) do @@ -191,7 +189,7 @@ local monkey_patch = export (M, "monkey_patch (table?)", function (namespace) end return M -end) +end --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). @@ -203,7 +201,7 @@ end) -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table module table -- @usage local std = require "std".barrel () -export (M, "barrel (table?)", function (namespace) +local function barrel (namespace) namespace = namespace or _G -- Older releases installed the following into _G by default. @@ -234,7 +232,8 @@ export (M, "barrel (table?)", function (namespace) require "std.table".monkey_patch (namespace) return monkey_patch (namespace) -end) +end + --- Module table. @@ -244,7 +243,26 @@ end) -- demand. -- @table std -- @field version release version string -M.version = "General Lua libraries / @VERSION@" + +local export = base.debug.export + +M = { + assert = export "assert (any?, string?, any?*)", + barrel = export "barrel (table?)", + elems = export "elems (table)", + eval = export "eval (string)", + ielems = export "ielems (table)", + ipairs = export "ipairs (table)", + ireverse = export "ireverse (table)", + getmetamethod = export "getmetamethod (object|table, string)", + monkey_patch = export "monkey_patch (table?)", + pairs = export "pairs (table)", + require = export "require (string, string?, string?, string?)", + ripairs = export "ripairs (table)", + tostring = export "tostring (any?)", + + version = "General Lua libraries / @VERSION@", +} --- Metamethods diff --git a/lib/std/base.lua b/lib/std/base.lua index 8609bea..c282527 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,6 +23,12 @@ ]] +local base = require "std.base.string" +local callable = require "std.base.functional".callable +local copy, render, split = base.copy, base.render, base.split + + + local function len (t) -- Lua < 5.2 doesn't call `__len` automatically! local m = (getmetatable (t) or {}).__len @@ -77,22 +83,6 @@ end ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - ---- Make a shallow copy of a table. --- @tparam table t source table --- @treturn table shallow copy of *t* -local function copy (t) - local new = {} - for k, v in pairs (t) do new[k] = v end - return new -end - - - --[[ ======================== ]]-- --[[ Documented in table.lua. ]]-- --[[ ======================== ]]-- @@ -111,54 +101,6 @@ end ---[[ ========================= ]]-- ---[[ Documented in string.lua. ]]-- ---[[ ========================= ]]-- - - -local function render (x, open, close, elem, pair, sep, roots) - local function stop_roots (x) - return roots[x] or render (x, open, close, elem, pair, sep, copy (roots)) - end - roots = roots or {} - if type (x) ~= "table" or getmetamethod (x, "__tostring") then - return elem (x) - else - local s = {} - s[#s + 1] = open (x) - roots[x] = elem (x) - - -- create a sorted list of keys - local ord = {} - for k, _ in pairs (x) do ord[#ord + 1] = k end - table.sort (ord, function (a, b) return tostring (a) < tostring (b) end) - - -- render x elements in order - local i, v = nil, nil - for _, j in ipairs (ord) do - local w = x[j] - s[#s + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) - i, v = j, w - end - s[#s + 1] = sep (x, i, v, nil, nil) .. close (x) - return table.concat (s) - end -end - - -local function split (s, sep) - sep = sep or "%s+" - local b, len, t, patt = 0, #s, {}, "(.-)" .. sep - if sep == "" then patt = "(.)"; t[#t + 1] = "" end - while b <= len do - local e, n, m = string.find (s, patt, b + 1) - t[#t + 1] = m or s:sub (b + 1, len) - b = n or len + 1 - end - return t -end - - --[[ ====================== ]]-- --[[ Documented in std.lua. ]]-- --[[ ====================== ]]-- @@ -666,6 +608,49 @@ local getfenv = getfenv or function(f) end +local dirsep, pathsep, path_mark = package.config:match "^(%S+)\n(%S+)\n(%S+)\n" +local pathpatt, markpatt = "[^" .. pathsep .. "]+", path_mark:gsub ("%p", "%%%0") + +local function whatpath (name, src) + local r + package.path:gsub (pathpatt, function (s) + local substituted = s:gsub (markpatt, (name:gsub ("%.", dirsep))) + if substituted == src then r = name end + end) + return r +end + + +local function getinfo (what, level) + local fqfname, s, fn + + for i = 1, math.huge do + s, fn = debug.getlocal (level + 1, i) + + if s == nil then + break + + elseif s == what or fn == what then + local t, src = {}, debug.getinfo (callable (fn), "S").source:gsub ("^@(.*)$", "%1") + src:gsub ("/([^/]+)", function (m) t[#t + 1] = m:gsub ("%.lua", "") end) + + local tryme + for i = #t, 1, -1 do + tryme = tryme and (t[i] .. "." .. tryme) or t[i] + if whatpath (tryme, src) then + fqfname = (tryme .. "." .. s):gsub ("^(std%.)base%.", "%1") + break + end + end + break + + end + end + + return fqfname, fn +end + + --- Export a function definition, optionally with argument type checking. -- In addition to checking that each argument type matches the corresponding -- element in the *types* table with `argcheck`, if the final element of @@ -677,41 +662,22 @@ end -- @func fn value to store at *name* in *M* -- @usage -- export (M, "round (number, int?)", std.math.round) -local function export (M, decl, fn, ...) - local inner = fn - +local function export (decl, ...) -- Parse "fname (argtype, argtype, argtype...)". - local name, types - if decl then - name, types = decl:match "([%w_][%d%w_]*)%s+%((.*)%)" + local name, types = decl:match "([%w_][%d%w_]*)%s+%((.*)%)" + if types == "" then + types = {} + elseif types then + types = split (types, ",%s+") + else + name = decl:match "([%w_][%d%w_]*)" end + local fqfname, inner = getinfo (name, 2) + + local fn = inner -- When argument checking is enabled, wrap in type checking function. if _ARGCHECK then - local fname = "std.base.export" - local args = {M, decl, fn, ...} - argscheck (fname, {"table", "string", "function"}, args) - - -- Check for other argument errors. - if types == "" then - types = {} - elseif types then - types = split (types, ",%s+") - else - name = decl:match "([%w_][%d%w_]*)" - end - if arglen (args) > 3 then - error (string.format (toomanyarg_fmt, fname, 3, arglen (args)), 2) - elseif type (M[1]) ~= "string" then - argerror (fname, 1, formaterror ("module name at index 1", M[1]), 2) - elseif name == nil then - argerror (fname, 2, formaterror ("function name", name), 2) - elseif types == nil then - argerror (fname, 2, formaterror ("argument type specification", types), 2) - end - - local name = M[1] .. (M[2] and ":" or ".") .. name - -- If the final element of types ends with "*", then set max to a -- sentinel value to denote type-checking of *all* remaining -- unchecked arguments against that type-spec is required. @@ -730,13 +696,6 @@ local function export (M, decl, fn, ...) local args = {...} local argc, bestmismatch, at = arglen (args), 0, 0 - -- For object methods, report type mismatch on self as argument 0. - if M[2] then - argcheck (name, 0, M[2], args[1], 2) - table.remove (args, 1) - argc = argc - 1 - end - for i, types in ipairs (type_specs) do local mismatch = match (types, args, max == math.huge) if mismatch == nil then @@ -769,18 +728,18 @@ local function export (M, decl, fn, ...) if contents and type (args[i]) == "table" then for k, v in pairs (args[i]) do if not checktype (contents, v) then - argerror (name, i, formaterror (expected, v, k), 2) + argerror (fqfname or name, i, formaterror (expected, v, k), 2) end end end end -- Otherwise the argument type itself was mismatched. - argerror (name, i, formaterror (expected, args[i]), 2) + argerror (fqfname or name, i, formaterror (expected, args[i]), 2) end if argc > max then - error (string.format (toomanyarg_fmt, name, max, argc), 2) + error (string.format (toomanyarg_fmt, fqfname or name, max, argc), 2) end -- Propagate outer environment to inner function. @@ -790,16 +749,10 @@ local function export (M, decl, fn, ...) end end - M[name] = fn - - return inner + return fn end --- Required for exported function argument check failures: -local M = { "std.base" } - - -- Whether to show a deprecation warning the next time a give key is set. local compat = {} @@ -808,56 +761,51 @@ local compat = {} -- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; -- if `_DEBUG.compat` is false, warn every time *fn* is called; -- otherwise don't write any warnings, and run *fn* normally. --- @function setcompat -- @param key unique identifier for a deprecated API. -local setcompat = export (M, "setcompat (any)", function (key) +local function setcompat (key) compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat == nil) or _DEBUG == true -end) +end --- Get the deprecation warning status for *key*. --- @function getcompat +-- @param key unique identifier for a deprecated API. -- @treturn boolean whether to show a deprecation warning. -local getcompat = export (M, "getcompat (any)", function (key) +local function getcompat (key) if compat[key] == nil then -- Whether to warn on first access. compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat) or _DEBUG == false end return compat[key] -end) +end --- Format a deprecation warning message. --- @function DEPRECATIONMSG -- @string version first deprecation release version -- @string name function name for automatic warning message -- @string[opt] extramsg additional warning text -- @int level call stack level to blame for the error -- @treturn string deprecation warning message -local DEPRECATIONMSG = export (M, "DEPRECATIONMSG (string, string, [string], int)", -function (version, name, extramsg, level) +local function DEPRECATIONMSG (version, name, extramsg, level) if level == nil then level, extramsg = extramsg, nil end extramsg = extramsg or "and will be removed entirely in a future release" local _, where = pcall (function () error ("", level + 3) end) return (where .. string.format ("%s was deprecated in release %s, %s.\n", name, version, extramsg)) -end) +end --- Write a deprecation warning to stderr. -- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; -- if `_DEBUG.compat` is false, warn every time *fn* is called; -- otherwise don't write any warnings, and run *fn* normally. --- @function DEPRECATED -- @string version first deprecation release version -- @string name function name for automatic warning message -- @string[opt] extramsg additional warning text -- @func fn deprecated function -- @return a function to show the warning on first call, and hand off to *fn* -- @usage funcname = deprecate (function (...) ... end, "funcname") -export (M, "DEPRECATED (string, string, [string], func)", -function (version, name, extramsg, fn) +local function DEPRECATED (version, name, extramsg, fn) if fn == nil then fn, extramsg = extramsg, nil end return function (...) @@ -867,7 +815,7 @@ function (version, name, extramsg, fn) end return fn (...) end -end) +end @@ -907,12 +855,13 @@ return setmetatable ({ argscheck = argscheck, -- Maintenance -- - DEPRECATED = M.DEPRECATED, - DEPRECATIONMSG = M.DEPRECATIONMSG, - export = export, + DEPRECATED = DEPRECATED, + DEPRECATIONMSG = DEPRECATIONMSG, getcompat = getcompat, - len = len, setcompat = setcompat, + + export = export, + len = len, toomanyarg_fmt = toomanyarg_fmt, }, { diff --git a/lib/std/base/string.lua b/lib/std/base/string.lua new file mode 100644 index 0000000..f758688 --- /dev/null +++ b/lib/std/base/string.lua @@ -0,0 +1,71 @@ +--[[-- + Base implementations of functions exported by `std.string`. + + These functions are required by implementations of exported functions + in other stdlib modules. We keep them here to ensure argument checking + error messages report the correct module ('std.string') after "%.base" + has been stripped from `debug.getinfo (fn, "S").short_src`. + + @module std.base.string +]] + + +--- Make a shallow copy of a table. +-- @tparam table t source table +-- @treturn table shallow copy of *t* +local function copy (t) + local new = {} + for k, v in pairs (t) do new[k] = v end + return new +end + + + +local function render (x, open, close, elem, pair, sep, roots) + local function stop_roots (x) + return roots[x] or render (x, open, close, elem, pair, sep, copy (roots)) + end + roots = roots or {} + if type (x) ~= "table" or type ((getmetatable (x) or {}).__tostring) == "function" then + return elem (x) + else + local s = {} + s[#s + 1] = open (x) + roots[x] = elem (x) + + -- create a sorted list of keys + local ord = {} + for k, _ in pairs (x) do ord[#ord + 1] = k end + table.sort (ord, function (a, b) return tostring (a) < tostring (b) end) + + -- render x elements in order + local i, v = nil, nil + for _, j in ipairs (ord) do + local w = x[j] + s[#s + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) + i, v = j, w + end + s[#s + 1] = sep (x, i, v, nil, nil) .. close (x) + return table.concat (s) + end +end + + +local function split (s, sep) + sep = sep or "%s+" + local b, len, t, patt = 0, #s, {}, "(.-)" .. sep + if sep == "" then patt = "(.)"; t[#t + 1] = "" end + while b <= len do + local e, n, m = string.find (s, patt, b + 1) + t[#t + 1] = m or s:sub (b + 1, len) + b = n or len + 1 + end + return t +end + + +return { + copy = copy, + render = render, + split = split, +} diff --git a/lib/std/container.lua b/lib/std/container.lua index dab4711..b45ba48 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -69,10 +69,9 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" -local argcheck, export, ipairs, pairs, prototype = - base.argcheck, base.export, base.ipairs, base.pairs, base.prototype - -local M = { "std.container" } +local ipairs, pairs = base.ipairs, base.pairs +local prototype = base.prototype +local argcheck, export = base.argcheck, base.export @@ -145,8 +144,7 @@ end -- a metatable with private fields (if any) merged, both sets of keys -- renamed according to *map* -- @see std.object.mapfields -local mapfields = export (M, "mapfields (table, table|object, table?)", -function (obj, src, map) +local function mapfields (obj, src, map) local mt = getmetatable (obj) or {} -- Map key pairs. @@ -182,7 +180,7 @@ function (obj, src, map) setmetatable (obj, mt) end return obj -end) +end --- Return a clone of this container. @@ -232,6 +230,11 @@ local function __call (self, x, ...) end +local M = { + mapfields = export "mapfields (table, table|object, table?)", +} + + if _ARGCHECK then local arglen, toomanyarg_fmt = base.arglen, base.toomanyarg_fmt diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 0b6447d..d40e9de 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -35,9 +35,9 @@ local base = require "std.base" local functional = require "std.functional" local string = require "std.string" -local export, ielems = base.export, base.ielems +local ielems = base.ielems -local M = { "std.debug" } +local M @@ -88,7 +88,7 @@ local tabify = functional.compose ( -- local _DEBUG = require "std.debug_init"._DEBUG -- _DEBUG.level = 3 -- say (2, "_DEBUG table contents:", _DEBUG) -function M.say (n, ...) +local function say (n, ...) local level = 1 local arg = {n, ...} if type (arg[1]) == "number" then @@ -115,7 +115,7 @@ local level = 0 -- @usage -- _DEBUG = { call = true } -- local debug = require "std.debug" -function M.trace (event) +local function trace (event) local t = debug.getinfo (3) local s = " >>> " .. string.rep (" ", level) if t ~= nil and t.currentline >= 0 then @@ -144,7 +144,7 @@ end -- Set hooks according to _DEBUG if type (_DEBUG) == "table" and _DEBUG.call then - debug.sethook (M.trace, "cr") + debug.sethook (trace, "cr") end @@ -162,7 +162,7 @@ end -- local h, err = input_handle (file) -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end -- ... -M.argerror = base.argerror +local argerror = base.argerror --[[ Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, @@ -220,7 +220,7 @@ export (M, "argerror (string, int, string?, int?)", base.argerror) -- local function case (with, branches) -- argcheck ("std.functional.case", 2, "#table", branches) -- ... -M.argcheck = base.argcheck +local argcheck = base.argcheck --[[ Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, @@ -240,7 +240,7 @@ export (M, "argcheck (string, int, string, any?, int?)", base.argcheck) -- local function curry (f, n) -- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) -- ... -M.argscheck = base.argscheck +local argscheck = base.argscheck --[[ Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, @@ -252,6 +252,16 @@ export (M, "argscheck (string, #list, table)", base.argscheck) ]] +--- @export +M = { + argcheck = argcheck, + argerror = argerror, + argscheck = argscheck, + say = say, + trace = trace, +} + + for k, v in pairs (debug) do M[k] = M[k] or v end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 70e9020..736e66a 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -11,45 +11,9 @@ local base = require "std.base" local operator = require "std.operator" -local export, ielems, ipairs, ireverse, len, pairs = - base.export, base.ielems, base.ipairs, base.ireverse, base.len, base.pairs -local callable, map, reduce = - base.functional.callable, base.functional.map, base.functional.reduce +local ipairs, ireverse, len, pairs = + base.ipairs, base.ireverse, base.len, base.pairs -local M = { "std.functional" } - - - ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - -local function memoize (fn, normalize) - if normalize == nil then - -- Call require here, to avoid pulling in all of 'std.string' - -- even when memoize is never called. - normalize = function (...) return require "std.base".tostring {...} end - end - - return setmetatable ({}, { - __call = function (self, ...) - local k = normalize (...) - local t = self[k] - if t == nil then - t = {fn (...)} - self[k] = t - end - return unpack (t) - end - }) -end - - - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- --- Partially apply a function. @@ -59,8 +23,7 @@ end -- @return function with *argt* arguments already bound -- @usage -- cube = bind (lambda "^", {[2] = 3}) -local bind -bind = export (M, "bind (func, any?*)", function (fn, ...) +local function bind (fn, ...) local argt = {...} if type (argt[1]) == "table" and argt[2] == nil then argt = argt[1] @@ -85,7 +48,7 @@ bind = export (M, "bind (func, any?*)", function (fn, ...) end return fn (unpack (arg)) end -end) +end --- Identify callable types. @@ -94,7 +57,7 @@ end) -- @return `true` if *x* can be called, otherwise `false` -- @usage -- if callable (functable) then functable (args) end -M.callable = callable +local callable = base.functional.callable --- A rudimentary case statement. @@ -115,13 +78,13 @@ M.callable = callable -- string = function () return "string" end, -- function (s) error ("unhandled type: " .. s) end, -- }) -export (M, "case (any?, #table)", function (with, branches) +local function case (with, branches) local match = branches[with] or branches[1] if callable (match) then return match (with) end return match -end) +end --- Collect the results of an iterator. @@ -134,7 +97,7 @@ end) -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} -export (M, "collect ([func], any*)", base.functional.collect) +local collect = base.functional.collect --- Compose functions. @@ -150,7 +113,7 @@ export (M, "collect ([func], any*)", base.functional.collect) -- @usage -- vpairs = compose (table.invert, ipairs) -- for v, i in vpairs {"a", "b", "c"} do process (v, i) end -local compose = export (M, "compose (func*)", function (...) +local function compose (...) local arg = {...} local fns, n = arg, #arg for i = 1, n do @@ -164,7 +127,7 @@ local compose = export (M, "compose (func*)", function (...) end return unpack (arg) end -end) +end --- A rudimentary condition-case statement. @@ -187,7 +150,7 @@ end) -- n == 1, 1, -- function () return n + triangle (n - 1) end) -- end -M.cond = function (expr, branch, ...) +local function cond (expr, branch, ...) if branch == nil and select ("#", ...) == 0 then expr, branch = true, expr end @@ -197,7 +160,7 @@ M.cond = function (expr, branch, ...) end return branch end - return M.cond (...) + return cond (...) end @@ -209,8 +172,7 @@ end -- @usage -- add = curry (function (x, y) return x + y end, 2) -- incr, decr = add (1), add (-1) -local curry -curry = export (M, "curry (func, int)", function (fn, n) +local function curry (fn, n) if n <= 1 then return fn else @@ -218,7 +180,7 @@ curry = export (M, "curry (func, int)", function (fn, n) return curry (bind (fn, x), n - 1) end end -end) +end --- Filter an iterator with a predicate. @@ -232,7 +194,7 @@ end) -- @usage -- --> {2, 4} -- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) -export (M, "filter (func, [func], any*)", function (pfn, ifn, ...) +local function filter (pfn, ifn, ...) local argt = {...} if not callable (ifn) then ifn, argt = pairs, {ifn, ...} @@ -254,7 +216,22 @@ export (M, "filter (func, [func], any*)", function (pfn, ifn, ...) t = {nextfn (state, k)} -- maintain loop invariant end return r -end) +end + + +--- Fold a binary function into an iterator. +-- @function reduce +-- @func fn reduce function +-- @param d initial first argument +-- @func ifn iterator function +-- @param ... iterator arguments +-- @return result +-- @see foldl +-- @see foldr +-- @usage +-- --> 2 ^ 3 ^ 4 ==> 4096 +-- reduce (lambda '^', 2, std.ipairs, {3, 4}) +local reduce = base.functional.reduce --- Fold a binary function left associatively. @@ -269,14 +246,14 @@ end) -- @see reduce -- @usage -- foldl (lambda "/", {10000, 100, 10}) == (10000 / 100) / 10 -export (M, "foldl (function, [any], table)", function (fn, d, t) +local function foldl (fn, d, t) if t == nil then local tail = {} for i = 2, len (d) do tail[#tail + 1] = d[i] end d, t = d[1], tail end return reduce (fn, d, ipairs, t) -end) +end --- Fold a binary function right associatively. @@ -291,25 +268,58 @@ end) -- @see reduce -- @usage -- foldr (lambda "/", {10000, 100, 10}) == 10000 / (100 / 10) -export (M, "foldr (function, [any], table)", function (fn, d, t) +local function foldr (fn, d, t) if t == nil then local u, last = {}, len (d) for i = 1, last - 1 do u[#u + 1] = d[i] end d, t = d[last], u end return reduce (function (x, y) return fn (y, x) end, d, ipairs, ireverse (t)) -end) +end --- Identity function. -- @function id -- @param ... arguments -- @return *arguments* -function M.id (...) +local function id (...) return ... end +--- Memoize a function, by wrapping it in a functable. +-- +-- To ensure that memoize always returns the same results for the same +-- arguments, it passes arguments to *fn*. You can specify a more +-- sophisticated function if memoize should handle complicated argument +-- equivalencies. +-- @function memoize +-- @func fn pure function: a function with no side effects +-- @tparam[opt=std.tostring] normalize normfn function to normalize arguments +-- @treturn functable memoized function +-- @usage +-- local fast = memoize (function (...) --[[ slow code ]] end) +local function memoize (fn, normalize) + if normalize == nil then + -- Call require here, to avoid pulling in all of 'std.string' + -- even when memoize is never called. + normalize = function (...) return require "std.base".tostring {...} end + end + + return setmetatable ({}, { + __call = function (self, ...) + local k = normalize (...) + local t = self[k] + if t == nil then + t = {fn (...)} + self[k] = t + end + return unpack (t) + end + }) +end + + --- Compile a lambda string into a Lua function. -- -- A valid lambda string takes one of the following forms: @@ -332,7 +342,7 @@ end -- lambda '<' -- lambda '= _1 < _2' -- lambda '|a,b| a {1, 4, 9, 16} -- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) -export (M, "map (func, [func], any*)", function (mapfn, ifn, ...) +local function map (mapfn, ifn, ...) local argt = {...} if not callable (ifn) or not next (argt) then ifn, argt = pairs, {ifn, ...} @@ -405,7 +415,7 @@ export (M, "map (func, [func], any*)", function (mapfn, ifn, ...) mapargs = {nextfn (state, k)} end return r -end) +end --- Map a function over a table of argument lists. @@ -419,28 +429,13 @@ end) -- --> {"123", "45"}, {a="123", b="45"} -- conc = bind (map_with, {lambda '|...|table.concat {...}'}) -- conc {{1, 2, 3}, {4, 5}}, conc {a={1, 2, 3, x="y"}, b={4, 5, z=6}} -local map_with = export (M, "map_with (function, table of tables)", function (mapfn, tt) +local function map_with (mapfn, tt) local r = {} for k, v in pairs (tt) do r[k] = mapfn (unpack (v)) end return r -end) - - ---- Memoize a function, by wrapping it in a functable. --- --- To ensure that memoize always returns the same results for the same --- arguments, it passes arguments to *fn*. You can specify a more --- sophisticated function if memoize should handle complicated argument --- equivalencies. --- @function memoize --- @func fn pure function: a function with no side effects --- @tparam[opt=std.tostring] normalize normfn function to normalize arguments --- @treturn functable memoized function --- @usage --- local fast = memoize (function (...) --[[ slow code ]] end) -export (M, "memoize (func, func?)", memoize) +end --- No operation. @@ -448,22 +443,7 @@ export (M, "memoize (func, func?)", memoize) -- @function nop -- @usage -- if unsupported then vtable["memrmem"] = nop end -M.nop = function () end - - ---- Fold a binary function into an iterator. --- @function reduce --- @func fn reduce function --- @param d initial first argument --- @func ifn iterator function --- @param ... iterator arguments --- @return result --- @see foldl --- @see foldr --- @usage --- --> 2 ^ 3 ^ 4 ==> 4096 --- reduce (lambda '^', 2, std.ipairs, {3, 4}) -export (M, "reduce (func, any, func, any*)", reduce) +local function nop () end --- Zip a table of tables. @@ -478,7 +458,7 @@ export (M, "reduce (func, any, func, any*)", reduce) -- @usage -- --> {{1, 3, 5}, {2, 4}}, {a={x=1, y=3, z=5}, b={x=2, y=4}} -- zip {{1, 2}, {3, 4}, {5}}, zip {x={a=1, b=2}, y={a=3, b=4}, z={a=5}} -local zip = export (M, "zip (table of tables)", function (tt) +local function zip (tt) local r = {} for outerk, inner in pairs (tt) do for k, v in pairs (inner) do @@ -487,7 +467,7 @@ local zip = export (M, "zip (table of tables)", function (tt) end end return r -end) +end --- Zip a list of tables together with a function. @@ -504,14 +484,38 @@ end) -- --> {"135", "24"}, {a="1", b="25"} -- conc = bind (zip_with, {lambda '|...|table.concat {...}'}) -- conc {{1, 2}, {3, 4}, {5}}, conc {{a=1, b=2}, x={a=3, b=4}, {b=5}} -export (M, "zip_with (function, table of tables)", function (fn, tt) +local function zip_with (fn, tt) return map_with (fn, zip (tt)) -end) - +end --- For backwards compatibility. -M.op = operator +local export = base.export + +--- @export +local M = { + bind = export "bind (func, any?*)", + callable = callable, + case = export "case (any?, #table)", + collect = export "collect ([func], any*)", + compose = export "compose (func*)", + cond = cond, + curry = export "curry (func, int)", + filter = export "filter (func, [func], any*)", + foldl = export "foldl (function, [any], table)", + foldr = export "foldr (function, [any], table)", + id = id, + lambda = export "lambda (string)", + map = export "map (func, [func], any*)", + map_with = export "map_with (function, table of tables)", + memoize = export "memoize (func, func?)", + nop = nop, + reduce = export "reduce (func, any, func, any*)", + zip = export "zip (table of tables)", + zip_with = export "zip_with (function, table of tables)", +} + + +M.op = operator -- for backwards compatibility --[[ ============= ]]-- diff --git a/lib/std/io.lua b/lib/std/io.lua index f0ef8de..d426763 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -18,11 +18,12 @@ local package = { } local ipairs, pairs = base.ipairs, base.pairs -local argerror, export, leaves, split = - base.argerror, base.export, base.tree.leaves, base.split +local argerror = base.argerror +local leaves = base.tree.leaves +local split = base.split -local M = { "std.io" } +local M @@ -57,7 +58,7 @@ end -- @return contents of file or handle, or nil if error -- @see process_files -- @usage contents = slurp (filename) -local slurp = export (M, "slurp (file|string|nil)", function (file) +local function slurp (file) local h, err = input_handle (file) if h == nil then argerror ("std.io.slurp", 1, err, 2) end @@ -66,7 +67,7 @@ local slurp = export (M, "slurp (file|string|nil)", function (file) h:close () return s end -end) +end --- Read a file or file handle into a list of lines. @@ -76,7 +77,7 @@ end) -- if file is a file handle, that file is closed after reading -- @treturn list lines -- @usage list = readlines "/etc/passwd" -export (M, "readlines (file|string|nil)", function (file) +local function readlines (file) local h, err = input_handle (file) if h == nil then argerror ("std.io.readlines", 1, err, 2) end @@ -86,7 +87,7 @@ export (M, "readlines (file|string|nil)", function (file) end h:close () return l -end) +end --- Write values adding a newline after each. @@ -95,9 +96,7 @@ end) -- the file is **not** closed after writing -- @tparam string|number ... values to write (as for write) -- @usage writelines (io.stdout, "first line", "next line") -local writelines = export (M, -"writelines (file|string|number?, string|number?*)", -function (h, ...) +local function writelines (h, ...) if io.type (h) ~= "file" then io.write (h, "\n") h = io.output () @@ -105,7 +104,7 @@ function (h, ...) for v in leaves (ipairs, {...}) do h:write (v, "\n") end -end) +end --- Overwrite core methods and metamethods with `std` enhanced versions. @@ -115,7 +114,7 @@ end) -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the `std.io` module table -- @usage local io = require "std.io".monkey_patch () -export (M, "monkey_patch (table?)", function (namespace) +local function monkey_patch (namespace) namespace = namespace or _G local file_metatable = getmetatable (namespace.io.stdin) @@ -123,7 +122,7 @@ export (M, "monkey_patch (table?)", function (namespace) file_metatable.writelines = M.writelines return M -end) +end --- Split a directory path into components. @@ -133,9 +132,9 @@ end) -- @return list of path components -- @see catdir -- @usage dir_components = splitdir (filepath) -export (M, "splitdir (string)", function (path) +local function splitdir (path) return split (path, package.dirsep) -end) +end --- Concatenate one or more directories and a filename into a path. @@ -145,9 +144,9 @@ end) -- @see catdir -- @see splitdir -- @usage filepath = catfile ("relative", "path", "filename") -export (M, "catfile (string*)", function (...) +local function catfile (...) return table.concat ({...}, package.dirsep) -end) +end --- Concatenate directory names into a path. @@ -156,9 +155,9 @@ end) -- @return path without trailing separator -- @see catfile -- @usage dirpath = catdir ("", "absolute", "directory") -export (M, "catdir (string*)", function (...) +local function catdir (...) return table.concat ({...}, package.dirsep):gsub("^$", package.dirsep) -end) +end --- Perform a shell command and return its output. @@ -167,9 +166,9 @@ end) -- @treturn string output, or nil if error -- @see os.execute -- @usage users = shell [[cat /etc/passwd | awk -F: '{print $1;}']] -export (M, "shell (string)", function (c) +local function shell (c) return slurp (io.popen (c)) -end) +end --- Process files specified on the command-line. @@ -185,7 +184,7 @@ end) -- -- minimal cat command -- local io = require "std.io" -- io.process_files (function () io.write (io.slurp ()) end) -export (M, "process_files (function)", function (fn) +local function process_files (fn) -- N.B. "arg" below refers to the global array of command-line args if #arg == 0 then arg[#arg + 1] = "-" @@ -198,7 +197,7 @@ export (M, "process_files (function)", function (fn) end fn (v, i) end -end) +end --- Give warning with the name of program and file (if any). @@ -220,7 +219,7 @@ end) -- if not _G.opts.keep_going then -- require "std.io".warn "oh noes!" -- end -local warn = export (M, "warn (string, any?*)", function (msg, ...) +local function warn (msg, ...) local prefix = "" if (prog or {}).name then prefix = prog.name .. ":" @@ -240,7 +239,7 @@ local warn = export (M, "warn (string, any?*)", function (msg, ...) end if #prefix > 0 then prefix = prefix .. " " end writelines (io.stderr, prefix .. string.format (msg, ...)) -end) +end --- Die with error. @@ -251,10 +250,29 @@ end) -- @param ... additional arguments to plug format string specifiers -- @see warn -- @usage die ("oh noes! (%s)", tostring (obj)) -export (M, "die (string, any?*)", function (...) +local function die (...) warn (...) error () -end) +end + + +local export = base.export + +--- @export +M = { + catdir = export "catdir (string*)", + catfile = export "catfile (string*)", + die = export "die (string, any?*)", + monkey_patch = export "monkey_patch (table?)", + process_files = export "process_files (function)", + readlines = export "readlines (file|string|nil)", + shell = export "shell (string)", + slurp = export "slurp (file|string|nil)", + splitdir = export "splitdir (string)", + warn = export "warn (string, any?*)", + writelines = export "writelines (file|string|number?, string|number?*)", +} + for k, v in pairs (io) do diff --git a/lib/std/list.lua b/lib/std/list.lua index 8b47e01..5228b6c 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -34,10 +34,10 @@ local Object = object {} local List -- forward declaration local ipairs, pairs = base.ipairs, base.pairs -local export, ielems, prototype = - base.export, base.ielems, base.prototype +local ielems = base.ielems +local prototype = base.prototype -local M = { "std.list" } +local M = {} @@ -47,28 +47,24 @@ local M = { "std.list" } --- Append an item to a list. --- @static --- @function append -- @tparam List l a list -- @param x item -- @treturn List new list containing `{l[1], ..., l[#l], x}` -local append = export (M, "append (List, any)", function (l, x) +local function append (l, x) local r = l {} r[#r + 1] = x return r -end) +end --- Compare two lists element-by-element, from left-to-right. -- -- if a_list:compare (another_list) == 0 then print "same" end --- @static --- @function compare -- @tparam List l a list -- @tparam table m another list -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 -- if `l` is greater than `m` -local compare = export (M, "compare (List, List|table)", function (l, m) +local function compare (l, m) for i = 1, math.min (#l, #m) do local li, mi = tonumber (l[i]), tonumber (m[i]) if li == nil or mi == nil then @@ -86,17 +82,15 @@ local compare = export (M, "compare (List, List|table)", function (l, m) return 1 end return 0 -end) +end --- Concatenate arguments into a list. --- @static --- @function concat -- @tparam List l a list -- @param ... tuple of lists -- @treturn List new list containing -- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` -local concat = export (M, "concat (List, List|table*)", function (l, ...) +local function concat (l, ...) local r = List {} for e in ielems {l, ...} do for v in ielems (e) do @@ -104,45 +98,39 @@ local concat = export (M, "concat (List, List|table*)", function (l, ...) end end return r -end) +end --- Prepend an item to a list. --- @static --- @function cons -- @tparam List l a list -- @param x item -- @treturn List new list containing `{x, unpack (l)}` -local cons = export (M, "cons (List, any)", function (l, x) +local function cons (l, x) return List {x, unpack (l)} -end) +end --- Repeat a list. --- @static --- @function rep -- @tparam List l a list -- @int n number of times to repeat -- @treturn List `n` copies of `l` appended together -local rep = export (M, "rep (List, int)", function (l, n) +local function rep (l, n) local r = List {} for i = 1, n do r = concat (r, l) end return r -end) +end --- Return a sub-range of a list. -- (The equivalent of `string.sub` on strings; negative list indices -- count from the end of the list.) --- @static --- @function sub -- @tparam List l a list -- @int from start of range (default: 1) -- @int to end of range (default: `#l`) -- @treturn List new list containing `{l[from], ..., l[to]}` -local sub = export (M, "sub (List, int?, int?)", function (l, from, to) +local function sub (l, from, to) local r = List {} local len = #l from = from or 1 @@ -157,17 +145,29 @@ local sub = export (M, "sub (List, int?, int?)", function (l, from, to) r[#r + 1] = l[i] end return r -end) +end --- Return a list with its first element removed. --- @static --- @function tail -- @tparam List l a list -- @treturn List new list containing `{l[2], ..., l[#l]}` -local tail = export (M, "tail (List)", function (l) +local function tail (l) return sub (l, 2) -end) +end + + +local export = base.export + +--- @export +local M = { + append = export "append (List, any)", + compare = export "compare (List, List|table)", + concat = export "concat (List, List|table*)", + cons = export "cons (List, any)", + rep = export "rep (List, int)", + sub = export "sub (List, int?, int?)", + tail = export "tail (List)", +} @@ -351,16 +351,15 @@ local function zip_with (ls, fn) end -local m = { "std.list", "List" } - - -export (m, "append (any)", append) -export (m, "compare (List|table)", compare) -export (m, "concat (List|table*)", concat) -export (m, "cons (any)", cons) -export (m, "rep (int)", rep) -export (m, "sub (int?, int?)", sub) -export (m, "tail ()", tail) +local m = { + append = M.append, + compare = M.compare, + concat = M.concat, + cons = M.cons, + rep = M.rep, + sub = M.sub, + tail = M.tail, +} m.depair = DEPRECATED ("38", "'std.list:depair'", depair) diff --git a/lib/std/math.lua b/lib/std/math.lua index 9c31bff..85b6168 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,10 +11,7 @@ ]] -local export = require "std.base".export -local floor = math.floor - -local M = { "std.math" } +local M --- Extend `math.floor` to take the number of decimal places. @@ -24,15 +21,16 @@ local M = { "std.math" } -- @treturn number `n` truncated to `p` decimal places -- @usage tenths = floor (magnitude, 1) +local _floor = math.floor -export (M, "floor (number, int?)", function (n, p) +local function floor (n, p) if p and p ~= 0 then local e = 10 ^ p - return floor (n * e) / e + return _floor (n * e) / e else - return floor (n) + return _floor (n) end -end) +end --- Overwrite core methods with `std` enhanced versions. @@ -42,11 +40,11 @@ end) -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage require "std.math".monkey_patch () -export (M, "monkey_patch (table?)", function (namespace) +local function monkey_patch (namespace) namespace = namespace or _G namespace.math.floor = M.floor return M -end) +end --- Round a number to a given number of decimal places @@ -55,10 +53,20 @@ end) -- @int[opt=0] p number of decimal places to round to -- @treturn number `n` rounded to `p` decimal places -- @usage roughly = round (exactly, 2) -export (M, "round (number, int?)", function (n, p) +local function round (n, p) local e = 10 ^ (p or 0) - return floor (n * e + 0.5) / e -end) + return _floor (n * e + 0.5) / e +end + + +local export = require "std.base".export + +--- @export +M = { + floor = export "floor (number, int?)", + monkey_patch = export "monkey_patch (table?)", + round = export "round (number, int?)", +} for k, v in pairs (math) do diff --git a/lib/std/package.lua b/lib/std/package.lua index 38d1fb0..39baef1 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -16,11 +16,9 @@ local catfile = require "std.io".catfile local invert = require "std.table".invert local escape_pattern = require "std.string".escape_pattern -local export, ipairs, pairs, split = - base.export, base.ipairs, base.pairs, base.split +local ipairs, pairs, split = base.ipairs, base.pairs, base.split - -local M = { "std.package" } +local M @@ -54,6 +52,18 @@ end --[[ ================= ]]-- +--- 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 path_mark 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 dirsep, pathsep, path_mark, execdir, igmark = + string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") + + --- Look for a path segment match of `patt` in `pathstrings`. -- @function find -- @string pathstrings `pathsep` delimited path elements @@ -66,8 +76,7 @@ end -- @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 .. "/]") -export (M, "find (string, string, int?, boolean|:plain?)", -function (pathstrings, patt, init, plain) +local function find (pathstrings, patt, init, plain) local paths = split (pathstrings, M.pathsep) if plain then patt = escape_pattern (patt) end init = init or 1 @@ -75,7 +84,7 @@ function (pathstrings, patt, init, plain) for i = init, #paths do if paths[i]:find (patt) then return i, paths[i] end end -end) +end --- Normalize a path list. @@ -88,14 +97,14 @@ end) -- @param ... path elements -- @treturn string a single normalized `pathsep` delimited paths string -- @usage package.path = normalize (user_paths, sys_paths, package.path) -local normalize = export (M, "normalize (string*)", function (...) - local i, paths, pathstrings = 1, {}, table.concat ({...}, M.pathsep) - for _, path in ipairs (split (pathstrings, M.pathsep)) do +local function normalize (...) + local i, paths, pathstrings = 1, {}, table.concat ({...}, pathsep) + for _, path in ipairs (split (pathstrings, pathsep)) do path = pathsub (path): gsub (catfile ("^[^", "]"), catfile (".", "%0")): - gsub (catfile ("", "%.", ""), M.dirsep): + gsub (catfile ("", "%.", ""), dirsep): gsub (catfile ("", "%.$"), ""): - gsub (catfile ("", "[^", "]+", "%.%.", ""), M.dirsep): + gsub (catfile ("", "[^", "]+", "%.%.", ""), dirsep): gsub (catfile ("", "[^", "]+", "%.%.$"), ""): gsub (catfile ("%.", "%..", ""), catfile ("..", "")): gsub (catfile ("", "$"), "") @@ -106,8 +115,8 @@ local normalize = export (M, "normalize (string*)", function (...) paths[path], i = i, i + 1 end end - return table.concat (invert (paths), M.pathsep) -end) + return table.concat (invert (paths), pathsep) +end ------ @@ -123,11 +132,11 @@ end) local unpack = unpack or table.unpack -export (M, "insert (string, [int], string)", function (pathstrings, ...) - local paths = split (pathstrings, M.pathsep) +local function insert (pathstrings, ...) + local paths = split (pathstrings, pathsep) table.insert (paths, ...) return normalize (unpack (paths)) -end) +end ------ @@ -146,13 +155,12 @@ end) -- @param ... additional arguments passed to `callback` -- @return nil, or first non-nil returned by `callback` -- @usage mappath (package.path, searcherfn, transformfn) -export (M, "mappath (string, function, any?*)", -function (pathstrings, callback, ...) - for _, path in ipairs (split (pathstrings, M.pathsep)) do +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) +end --- Remove any element from a `package.path` like string of paths. @@ -162,23 +170,30 @@ end) -- is the number of elements prior to removal -- @treturn string a new string with given element removed -- @usage package.path = remove (package.path) -export (M, "remove (string, int?)", function (pathstrings, pos) - local paths = split (pathstrings, M.pathsep) +local function remove (pathstrings, pos) + local paths = split (pathstrings, pathsep) table.remove (paths, pos) - return table.concat (paths, M.pathsep) -end) + return table.concat (paths, pathsep) +end ---- 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 path_mark 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. -M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark = - string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") +local export = base.export + +--- @export +M = { + find = export "find (string, string, int?, boolean|:plain?)", + insert = export "insert (string, [int], string)", + mappath = export "mappath (string, function, any?*)", + normalize = export "normalize (string*)", + remove = export "remove (string, int?)", +} + + +M.dirsep = dirsep +M.execdir = execdir +M.igmark = igmark +M.path_mark = path_mark +M.pathsep = pathsep for k, v in pairs (package) do diff --git a/lib/std/string.lua b/lib/std/string.lua index 67126ae..b5fb86a 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -16,14 +16,14 @@ local table = require "std.table" local StrBuf = strbuf {} -local export, getmetamethod, pairs, split = - base.export, base.getmetamethod, base.pairs, base.split -local totable = table.totable +local getmetamethod = base.getmetamethod +local pairs = base.pairs +local totable = table.totable local _format = string.format local _tostring = base.tostring -local M = { "std.string" } +local M @@ -58,7 +58,7 @@ end -- @usage -- local string = require "std.string".monkey_patch () -- concatenated = "foo" .. {"bar"} -function M.__concat (s, o) +local function __concat (s, o) return _tostring (s) .. _tostring (o) end @@ -71,7 +71,7 @@ end -- @usage -- getmetatable ("").__index = require "std.string".__index -- third = ("12345")[3] -function M.__index (s, i) +local function __index (s, i) if type (i) == "number" then return s:sub (i, i) else @@ -94,9 +94,9 @@ end -- @param[opt] ... arguments to format -- @return formatted string -- @usage print (format "100% stdlib!") -local format = export (M, "format (string, any?*)", function (f, arg1, ...) +local function format (f, arg1, ...) return (arg1 ~= nil) and _format (f, arg1, ...) or f -end) +end --- Do `string.find`, returning a table of captures. @@ -110,9 +110,9 @@ end) -- @treturn table list of captured strings -- @see std.string.finds -- @usage b, e, captures = tfind ("the target string", "%s", 10) -local tfind = export (M, "tfind (string, string, int?, boolean|:plain?)", function (s, ...) +local function tfind (s, ...) return tpack (s:find (...)) -end) +end --- Repeatedly `string.find` until target string is exhausted. @@ -127,7 +127,7 @@ end) -- for t in std.elems (finds ("the target string", "%S+")) do -- print (tostring (t.capt)) -- end -export (M, "finds (string, string, int?, boolean|:plain?)", function (s, p, i, ...) +local function finds (s, p, i, ...) i = i or 1 local l = {} local from, to, r @@ -139,7 +139,7 @@ export (M, "finds (string, string, int?, boolean|:plain?)", function (s, p, i, . end until not from return l -end) +end --- Split a string at a given separator. @@ -150,7 +150,7 @@ end) -- @string[opt="%s+"] sep separator pattern -- @return list of strings -- @usage words = split "a very short sentence" -export (M, "split (string, string?)", split) +local split = base.split --- Overwrite core methods and metamethods with `std` enhanced versions. @@ -163,13 +163,13 @@ export (M, "split (string, string?)", split) -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local string = require "std.string".monkey_patch () -export (M, "monkey_patch (table?)", function (namespace) +local function monkey_patch (namespace) local string_metatable = getmetatable "" string_metatable.__concat = M.__concat string_metatable.__index = M.__index return M -end) +end --- Capitalise each word in a string. @@ -177,9 +177,9 @@ end) -- @string s any string -- @treturn string *s* with each word capitalized -- @usage userfullname = caps (input_string) -export (M, "caps (string)", function (s) +local function caps (s) return s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end) -end) +end --- Remove any final newline from a string. @@ -187,9 +187,9 @@ end) -- @string s any string -- @treturn string *s* with any single trailing newline removed -- @usage line = chomp (line) -export (M, "chomp (string)", function (s) +local function chomp (s) return s:gsub ("\n$", "") -end) +end --- Escape a string to be used as a pattern. @@ -197,9 +197,9 @@ end) -- @string s any string -- @treturn string *s* with active pattern characters escaped -- @usage substr = inputstr:match (escape_pattern (literal)) -export (M, "escape_pattern (string)", function (s) +local function escape_pattern (s) return s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") -end) +end --- Escape a string to be used as a shell token. @@ -209,9 +209,9 @@ end) -- @string s any string -- @treturn string *s* with active shell characters escaped -- @usage os.execute ("echo " .. escape_shell (outputstr)) -export (M, "escape_shell (string)", function (s) +local function escape_shell (s) return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) -end) +end --- Return the English suffix for an ordinal. @@ -221,7 +221,7 @@ end) -- @usage -- local now = os.date "*t" -- print ("%d%s day of the week", now.day, ordinal_suffix (now.day)) -export (M, "ordinal_suffix (int|string)", function (n) +local function ordinal_suffix (n) n = math.abs (n) % 100 local d = n % 10 if d == 1 and n ~= 11 then @@ -233,7 +233,7 @@ export (M, "ordinal_suffix (int|string)", function (n) else return "th" end -end) +end --- Justify a string. @@ -246,13 +246,13 @@ end) -- @string[opt=" "] p string to pad with -- @treturn string *s* justified to *w* characters wide -- @usage print (pad (trim (outputstr, 78)) .. "\n") -export (M, "pad (string, int, string?)", function (s, w, p) +local function pad (s, w, p) p = string.rep (p or " ", math.abs (w)) if w < 0 then return string.sub (p .. s, w) end return string.sub (s .. p, 1, w) -end) +end --- Wrap a string into a paragraph. @@ -264,7 +264,7 @@ end) -- @treturn string *s* wrapped to *w* columns -- @usage -- print (wrap (copyright, 72, 4)) -export (M, "wrap (string, int?, int?, int?)", function (s, w, ind, ind1) +local function wrap (s, w, ind, ind1) w = w or 78 ind = ind or 0 ind1 = ind1 or ind @@ -289,7 +289,7 @@ export (M, "wrap (string, int?, int?, int?)", function (s, w, ind, ind1) end end return r:tostring () -end) +end --- Write a number using SI suffixes. @@ -298,7 +298,7 @@ end) -- @tparam number|string n any numeric value -- @treturn string *n* simplifed using largest available SI suffix. -- @usage print (numbertosi (bitspersecond) .. "bps") -export (M, "numbertosi (number|string)", function (n) +local function numbertosi (n) local SIprefix = { [-8] = "y", [-7] = "z", [-6] = "a", [-5] = "f", [-4] = "p", [-3] = "n", [-2] = "mu", [-1] = "m", @@ -314,7 +314,7 @@ export (M, "numbertosi (number|string)", function (n) local s = SIprefix[siexp] or "e" .. tostring (siexp) man = man * (10 ^ shift) return tostring (man) .. s -end) +end --- Remove leading matter from a string. @@ -323,10 +323,10 @@ end) -- @string[opt="%s+"] r leading pattern -- @treturn string *s* with leading *r* stripped -- @usage print ("got: " .. ltrim (userinput)) -export (M, "ltrim (string, string?)", function (s, r) +local function ltrim (s, r) r = r or "%s+" return s:gsub ("^" .. r, "") -end) +end --- Remove trailing matter from a string. @@ -335,10 +335,10 @@ end) -- @string[opt="%s+"] r trailing pattern -- @treturn string *s* with trailing *r* stripped -- @usage print ("got: " .. rtrim (userinput)) -export (M, "rtrim (string, string?)", function (s, r) +local function rtrim (s, r) r = r or "%s+" return s:gsub (r .. "$", "") -end) +end --- Remove leading and trailing matter from a string. @@ -347,10 +347,10 @@ end) -- @string[opt="%s+"] r trailing pattern -- @treturn string *s* with leading and trailing *r* stripped -- @usage print ("got: " .. trim (userinput)) -export (M, "trim (string, string?)", function (s, r) +local function trim (s, r) r = r or "%s+" return s:gsub ("^" .. r, ""):gsub (r .. "$", "") -end) +end -- Write pretty-printing based on: @@ -396,8 +396,7 @@ end) -- lambda '=_4.."=".._5', lambda '= _4 and "," or ""', -- lambda '=","') -- end -local render = export (M, - "render (any?, func, func, func, func, func, table?)", base.render) +local render = base.render --- Pretty-print a table, or other object. @@ -407,8 +406,7 @@ local render = export (M, -- @string[opt=""] spacing space before every line -- @treturn string pretty string rendering of *x* -- @usage print (prettytostring (std, " ")) -export (M, "prettytostring (any?, string?, string?)", -function (x, indent, spacing) +local function prettytostring (x, indent, spacing) indent = indent or "\t" spacing = spacing or "" return render (x, @@ -459,7 +457,7 @@ function (x, indent, spacing) end return s end) -end) +end --- Convert a value to a string. @@ -470,7 +468,7 @@ end) -- @see std.eval -- @usage -- function slow_identity (x) return functional.eval (pickle (x)) end -function M.pickle (x) +local function pickle (x) if type (x) == "string" then return format ("%q", x) elseif type (x) == "number" or type (x) == "boolean" or @@ -493,6 +491,34 @@ function M.pickle (x) end +local export = base.export + +--- @export +M = { + __concat = __concat, + __index = __index, + caps = export "caps (string)", + chomp = export "chomp (string)", + escape_pattern = export "escape_pattern (string)", + escape_shell = export "escape_shell (string)", + finds = export "finds (string, string, int?, boolean|:plain?)", + format = export "format (string, any?*)", + ltrim = export "ltrim (string, string?)", + monkey_patch = export "monkey_patch (table?)", + numbertosi = export "numbertosi (number|string)", + ordinal_suffix = export "ordinal_suffix (int|string)", + pad = export "pad (string, int, string?)", + pickle = pickle, + prettytostring = export "prettytostring (any?, string?, string?)", + render = export "render (any?, func, func, func, func, func, table?)", + rtrim = export "rtrim (string, string?)", + split = export "split (string, string?)", + tfind = export "tfind (string, string, int?, boolean|:plain?)", + trim = export "trim (string, string?)", + wrap = export "wrap (string, int?, int?, int?)", +} + + --[[ ============= ]]-- --[[ Deprecations. ]]-- @@ -515,7 +541,6 @@ M.tostring = DEPRECATED ("41", "'std.string.tostring'", - for k, v in pairs (string) do M[k] = M[k] or v end diff --git a/lib/std/table.lua b/lib/std/table.lua index df7b373..c1fcc17 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -13,13 +13,12 @@ local base = require "std.base" -local export, getmetamethod, ielems, ipairs, pairs = - base.export, base.getmetamethod, base.ielems, base.ipairs, base.pairs -local collect = base.functional.collect -local leaves = base.tree.leaves +local collect = base.functional.collect +local leaves = base.tree.leaves +local ielems, ipairs, pairs = base.ielems, base.ipairs, base.pairs -local M = { "std.table" } +local M @@ -82,7 +81,6 @@ end --- Make a shallow copy of a table, including any metatable. -- -- To make deep copies, use @{tree.clone}. --- @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 @@ -92,14 +90,12 @@ end -- @see clone_select -- @usage -- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") -local clone = export (M, "clone (table, [table], boolean|:nometa?)", - function (...) return merge_allfields ({}, ...) end) +local function clone (...) 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 @@ -109,90 +105,82 @@ local clone = export (M, "clone (table, [table], boolean|:nometa?)", -- @see merge_select -- @usage -- partialcopy = clone_select (original, {"this", "and_this"}, true) -export (M, "clone_select (table, [table], boolean|:nometa?)", - function (...) return merge_namedfields ({}, ...) end) +local function clone_select (...) 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 `{{i1, v1}, ..., {in, vn}}` -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` -- @see enpair -export (M, "depair (list of lists)", function (ls) +local function depair (ls) local t = {} for v in ielems (ls) do t[v[1]] = v[2] end return t -end) +end --- 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 -export (M, "enpair (table)", function (t) +local function enpair (t) local tt = {} for i, v in pairs (t) do tt[#tt + 1] = {i, v} end return tt -end) +end --- 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 -export (M, "empty (table)", function (t) +local function empty (t) return not next (t) -end) +end --- Flatten a nested table into a list. --- @function flatten -- @tparam table t a table -- @treturn table a list of all non-table elements of *t* -local flatten = export (M, "flatten (table)", function (t) +local function flatten (t) return collect (leaves, ipairs, t) -end) +end --- Invert a table. --- @function invert -- @tparam table t a table with `{k=v, ...}` -- @treturn table inverted table `{v=k, ...}` -- @usage values = invert (t) -export (M, "invert (table)", function (t) +local function invert (t) local i = {} for k, v in pairs (t) do i[v] = k end return i -end) +end --- 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) -export (M, "keys (table)", function (t) +local function keys (t) local l = {} for k, _ in pairs (t) do l[#l + 1] = k end return l -end) +end --- Destructively merge another 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, ...}` @@ -201,13 +189,12 @@ end) -- @see clone -- @see merge_select -- @usage merge (_G, require "std.debug", {say = "log"}, ":nometa") -export (M, "merge (table, table, [table], boolean|:nometa?)", merge_allfields) +local merge = 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 @@ -217,44 +204,41 @@ export (M, "merge (table, table, [table], boolean|:nometa?)", merge_allfields) -- @see merge -- @see clone_select -- @usage merge_select (_G, require "std.debug", {"say"}, false) -export (M, "merge_select (table, table, [table], boolean|:nometa?)", - merge_namedfields) +local merge_select = merge_namedfields --- 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) -export (M, "new (any?, table?)", function (x, t) +local function new (x, t) return setmetatable (t or {}, {__index = function (t, i) return x end}) -end) +end --- Turn a tuple into a list. -- @param ... tuple -- @return list -function M.pack (...) +local function pack (...) return {...} end --- 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* -export (M, "project (any, list of tables)", function (fkey, tt) +local function project (fkey, tt) local r = {} for _, t in ipairs (tt) do r[#r + 1] = t[fkey] end return r -end) +end --- Shape a table according to a list of dimensions. @@ -273,11 +257,10 @@ end) -- -- @todo Use ileaves instead of flatten (needs a while instead of a -- for in fill function) --- @function shape -- @tparam table dims table of dimensions `{d1, ..., dn}` -- @tparam table t a table of elements -- @return reshaped list -export (M, "shape (table, table)", function (dims, t) +local function shape (dims, t) t = flatten (t) -- Check the shape and calculate the size of the zero, if any local size = 1 @@ -310,50 +293,47 @@ export (M, "shape (table, table)", function (dims, t) end end return (fill (1, 1)) -end) +end --- 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 count = size {foo = true, bar = true, baz = false} -export (M, "size (table)", function (t) +local function size (t) local n = 0 for _ in pairs (t) do n = n + 1 end return n -end) +end -- Preserve core table sort function. local _sort = table.sort --- Make table.sort return its result. --- @function sort -- @tparam table t unsorted table -- @tparam[opt=std.operator["<"]] comparator c ordering function callback -- lua `<` operator -- @return *t* with keys sorted accordind to *c* -- @usage table.concat (sort (object)) -export (M, "sort (table, function?)", function (t, c) +local function sort (t, c) _sort (t, c) return t -end) +end --- Overwrite core methods with `std` enhanced versions. -- -- Replaces core `table.sort` with `std.table` version. --- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local table = require "std.table".monkey_patch () -export (M, "monkey_patch (table?)", function (namespace) +local function monkey_patch (namespace) namespace.table.sort = M.sort return M -end) +end --- Turn an object into a table according to `__totable` metamethod. @@ -361,7 +341,10 @@ end) -- @tparam object|table|string x object to turn into a table -- @treturn table resulting table or `nil` -- @usage print (table.concat (totable (object))) -export (M, "totable (object|table|string)", function (x) + +local getmetamethod = base.getmetamethod + +local function totable (x) local m = getmetamethod (x, "__totable") if m then return m (x) @@ -374,22 +357,46 @@ export (M, "totable (object|table|string)", function (x) else return nil end -end) +end --- Make the list of values of a table. --- @function values -- @tparam table t any table -- @treturn table list of values in *t* -- @see keys -export (M, "values (table)", function (t) +local function values (t) local l = {} for _, v in pairs (t) do l[#l + 1] = v end return l -end) +end + +local export = base.export + +--- @export +M = { + clone = export "clone (table, [table], boolean|:nometa?)", + clone_select = export "clone_select (table, [table], boolean|:nometa?)", + depair = export "depair (list of lists)", + enpair = export "enpair (table)", + empty = export "empty (table)", + flatten = export "flatten (table)", + invert = export "invert (table)", + keys = export "keys (table)", + merge = export "merge (table, table, [table], boolean|:nometa?)", + merge_select = export "merge_select (table, table, [table], boolean|:nometa?)", + new = export "new (any?, table?)", + pack = pack, + project = export "project (any, list of tables)", + shape = export "shape (table, table)", + size = export "size (table)", + sort = export "sort (table, function?)", + monkey_patch = export "monkey_patch (table?)", + totable = export "totable (object|table|string)", + values = export "values (table)", +} --[[ ============= ]]-- diff --git a/local.mk b/local.mk index 19e23cd..1967279 100644 --- a/local.mk +++ b/local.mk @@ -86,6 +86,7 @@ luastdbasedir = $(luastddir)/base dist_luastdbase_DATA = \ lib/std/base/functional.lua \ + lib/std/base/string.lua \ lib/std/base/tree.lua \ $(NOTHING_ELSE) diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml index c9d42a5..740e328 100644 --- a/specs/base_spec.yaml +++ b/specs/base_spec.yaml @@ -19,24 +19,6 @@ specify std.base: f, badarg = init (M, this_module, "DEPRECATED") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - expect (f "version").to_raise (badarg (2, "string")) - expect (f ("version", "name")).to_raise (badarg (3, "string or function")) - expect (f ("version", "name", "extramsg")).to_raise (badarg (4, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("version", false)).to_raise (badarg (2, "string", "boolean")) - expect (f ("version", "name", false)). - to_raise (badarg (3, "string or function", "boolean")) - expect (f ("version", "name", "extramsg", false)). - to_raise (badarg (4, "function", "boolean")) - - it diagnoses too many arguments: | - expect (f ("version", "name", "extramsg", nop, false)). - to_raise (badarg (5)) - pending "issue #76" - expect (f ("version", "name", nop, false)).to_raise (badarg (4)) - - it returns a function: expect (type (f ("0", "deprecated", nop))).to_be "function" expect (f ("0", "deprecated", nop)).not_to_be (nop) @@ -105,117 +87,89 @@ specify std.base: ]], tostring (name), tostring (spec)) end - mkmagic = function () return "MAGIC" end - f, badarg = init (M, this_module, "export") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - expect (f ({})).to_raise (badarg (2, "string")) - expect (f ({}, "")).to_raise (badarg (3, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_raise (badarg (2, "string", "boolean")) - expect (f ({}, "", false)).to_raise (badarg (3, "function", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, "", function () end, false)).to_raise (badarg (4)) - - it diagnoses missing module name element at callsite: | - expect (luaproc (mkstack ("", ""))). - to_contain_error (":3: " .. badarg (1, "module name at index 1")) - - it diagnoses malformed declaration string at callsite: | - expect (luaproc (mkstack ('"mkstack"', ""))). - to_contain_error (":3: " .. badarg (2, "function name")) - expect (luaproc (mkstack ('"mkstack"', "woo"))). - to_contain_error (":3: " .. badarg (2, "argument type specification")) + mkmagic = function () return "MAGIC" end + local inner = mkmagic + wrapped = f "inner ()" - - it returns the supplied function: - M = { "base_spec.yaml" } - expect (f (M, "expect (any)", f)).to_be (f) - - it stores a function in supplied table at the specified key: - M = { "base_spec.yaml" } - f (M, "export (any?)", mkmagic) - expect (M.export ()).to_be "MAGIC" - - it stores the passed function when _ARGCHECK is disabled: | + - it returns the wrapped function: + expect (wrapped).not_to_be (inner) + expect (wrapped ()).to_be "MAGIC" + - it does not wrap the function when _ARGCHECK is disabled: | script = [[ _DEBUG = false local debug = require "std.debug_init" local export = require "std.base".export - M = { "base_spec.yaml" } - export (M, "export (any)", export) - os.exit (M.export == export and 0 or 1) + local function inner () return "MAGIC" end + local wrapped = export "inner (any?)" + os.exit (wrapped == inner and 0 or 1) ]] expect (luaproc (script)).to_succeed () - context when checking zero argument function: - before: - M = { "base_spec.yaml" } - _, badarg = init (M, "base_spec.yaml", "chk_function") - f (M, "chk_function ()", mkmagic) - chk = M.chk_function + _, badarg = init (M, "", "inner") - it diagnoses too many arguments: - expect (chk (false)).to_raise (badarg (1)) + expect (wrapped (false)).to_raise (badarg (1)) - it accepts correct argument types: - expect (chk ()).to_be "MAGIC" + expect (wrapped ()).to_be "MAGIC" - context when checking single argument function: - before: - M = { "base_spec.yaml" } - _, badarg = init (M, "base_spec.yaml", "chk_function") - f (M, "chk_function (#table)", mkmagic) - chk = M.chk_function + _, badarg = init (M, "", "inner") + local inner = mkmagic + wrapped = f "inner (#table)" - it diagnoses missing arguments: - expect (chk ()).to_raise (badarg (1, "non-empty table")) + expect (wrapped ()).to_raise (badarg (1, "non-empty table")) - it diagnoses wrong argument types: - expect (chk {}).to_raise (badarg (1, "non-empty table", "empty table")) + expect (wrapped {}).to_raise (badarg (1, "non-empty table", "empty table")) - it diagnoses too many arguments: - expect (chk ({1}, 2, nop, "", false)).to_raise (badarg (1, 5)) + expect (wrapped ({1}, 2, nop, "", false)).to_raise (badarg (1, 5)) - it accepts correct argument types: - expect (chk ({1})).to_be "MAGIC" + expect (wrapped ({1})).to_be "MAGIC" - context when checking multi-argument function: - before: - M = { "base_spec.yaml" } - _, badarg = init (M, "base_spec.yaml", "chk_function") - f (M, "chk_function (table, function)", mkmagic) - chk = M.chk_function + _, badarg = init (M, "", "inner") + local inner = mkmagic + wrapped = f "inner (table, function)" - it diagnoses missing arguments: - expect (chk ()).to_raise (badarg (1, "table")) - expect (chk ({})).to_raise (badarg (2, "function")) + expect (wrapped ()).to_raise (badarg (1, "table")) + expect (wrapped ({})).to_raise (badarg (2, "function")) - it diagnoses wrong argument types: - expect (chk (false)).to_raise (badarg (1, "table", "boolean")) - expect (chk ({}, false)).to_raise (badarg (2, "function", "boolean")) + expect (wrapped (false)).to_raise (badarg (1, "table", "boolean")) + expect (wrapped ({}, false)).to_raise (badarg (2, "function", "boolean")) - it diagnoses too many arguments: - expect (chk ({}, nop, false)).to_raise (badarg (3)) + expect (wrapped ({}, nop, false)).to_raise (badarg (3)) - it accepts correct argument types: - expect (chk ({}, nop)).to_be "MAGIC" + expect (wrapped ({}, nop)).to_be "MAGIC" - context when checking optional argument function: - before: - M = { "base_spec.yaml" } - _, badarg = init (M, "base_spec.yaml", "chk_function") - f (M, "chk_function ([int])", mkmagic) - chk = M.chk_function + _, badarg = init (M, "", "inner") + local inner = mkmagic + wrapped = f "inner ([int])" - it diagnoses wrong argument types: - expect (chk (false)).to_raise (badarg (1, "int or nil", "boolean")) + expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) - it diagnoses too many arguments: - expect (chk (1, nop)).to_raise (badarg (2)) + expect (wrapped (1, nop)).to_raise (badarg (2)) - it accepts correct argument types: - expect (chk ()).to_be "MAGIC" - expect (chk (1)).to_be "MAGIC" + expect (wrapped ()).to_be "MAGIC" + expect (wrapped (1)).to_be "MAGIC" - context when checking optional multi-argument function: - before: - M = { "base_spec.yaml" } - _, badarg = init (M, "base_spec.yaml", "chk_function") - f (M, "chk_function ([int], string)", mkmagic) - chk = M.chk_function + _, badarg = init (M, "", "inner") + local inner = mkmagic + wrapped = f "inner ([int], string)" - it diagnoses missing arguments: - expect (chk ()).to_raise (badarg (1, "int or string")) - expect (chk (1)).to_raise (badarg (2, "string")) + expect (wrapped ()).to_raise (badarg (1, "int or string")) + expect (wrapped (1)).to_raise (badarg (2, "string")) - it diagnoses wrong argument types: - expect (chk (false)).to_raise (badarg (1, "int or string", "boolean")) + expect (wrapped (false)).to_raise (badarg (1, "int or string", "boolean")) - it diagnoses too many arguments: - expect (chk (1, "two", nop)).to_raise (badarg (3)) + expect (wrapped (1, "two", nop)).to_raise (badarg (3)) - it accepts correct argument types: - expect (chk ("two")).to_be "MAGIC" - expect (chk (1, "two")).to_be "MAGIC" + expect (wrapped ("two")).to_be "MAGIC" + expect (wrapped (1, "two")).to_be "MAGIC" diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 261dc79..f22604d 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -4,7 +4,7 @@ before: this_module = "std.functional" global_table = "_G" - exported_apis = { 1, "bind", "callable", "case", "collect", "compose", + exported_apis = { "bind", "callable", "case", "collect", "compose", "cond", "curry", "eval", "filter", "fold", "foldl", "foldr", "id", "lambda", "map", "map_with", "memoize", "nop", "op", "reduce", "zip", "zip_with" } diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index d9fd194..fc6353a 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -2,7 +2,7 @@ before: this_module = "std.list" global_table = "_G" - exported_apis = { 1, "append", "compare", "concat", "cons", "depair", + exported_apis = { "append", "compare", "concat", "cons", "depair", "elems", "enpair", "filter", "flatten", "foldl", "foldr", "index_key", "index_value", "map", "map_with", "project", "relems", "rep", "reverse", diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 343a823..1d260e9 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -17,6 +17,9 @@ function prototype (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. @@ -102,12 +105,14 @@ end local function badarg (mname, fname, i, want, got) if want == nil then i, want = i - 1, i end + local fqfname = (mname .. "." .. fname):gsub ("^%.", "") + if got == nil and type (want) == "number" then - local s = "too many arguments to '%s.%s' (no more than %d expected, got %d)" - return string.format (s, mname, fname, i, want) + local s = "too many arguments to '%s' (no more than %d expected, got %d)" + return string.format (s, fqfname, i, want) end - return string.format ("bad argument #%d to '%s.%s' (%s expected, got %s)", - i, mname, fname, want, got or "no value") + return string.format ("bad argument #%d to '%s' (%s expected, got %s)", + i, fqfname, want, got or "no value") end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index e8cb8f8..66bd039 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -2,7 +2,7 @@ before: | this_module = "std" global_table = "_G" - exported_apis = { 1, "assert", "barrel", "elems", "eval", "getmetamethod", + exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", "ielems", "ipairs", "ireverse", "monkey_patch", "pairs", "require", "ripairs", "tostring", "version" } From d7793d9284c9fb8fac3cf6235a3bc116b390cd49 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 23 Aug 2014 23:16:40 +0100 Subject: [PATCH 388/703] maint: fix a typo in std.lua.in. * lib/std.lua.in (export): Import this symbol correctly. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 4f250be..08ef2b3 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -244,7 +244,7 @@ end -- @table std -- @field version release version string -local export = base.debug.export +local export = base.export M = { assert = export "assert (any?, string?, any?*)", From 9b5c454e58ca0122aa7257b84509719ece583c91 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 Aug 2014 11:21:55 +0100 Subject: [PATCH 389/703] refactor: remove spurious comments from base.lua. * lib/std/base.lua: Remove spurious comments. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index c282527..1d1a271 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -36,14 +36,8 @@ local function len (t) end ---[[ ====================== ]]-- ---[[ Documented in std.lua. ]]-- ---[[ ====================== ]]-- - - local _pairs = pairs - -- Respect __pairs metamethod, even in Lua 5.1. local function pairs (t) return ((getmetatable (t) or {}).__pairs or _pairs) (t) @@ -81,13 +75,6 @@ local function ireverse (t) end - - ---[[ ======================== ]]-- ---[[ Documented in table.lua. ]]-- ---[[ ======================== ]]-- - - local function getmetamethod (x, n) local _, m = pcall (function (x) return getmetatable (x)[n] @@ -100,12 +87,6 @@ local function getmetamethod (x, n) end - ---[[ ====================== ]]-- ---[[ Documented in std.lua. ]]-- ---[[ ====================== ]]-- - - --- Return a List object by splitting version string on periods. -- @string version a period delimited version string -- @treturn List a list of version components From c3a1803e76ae03dd0c18c3787dad48cdf1bd29ac Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 Aug 2014 13:49:57 +0100 Subject: [PATCH 390/703] refactor: simplify std.require, and improve error diagnostics. Closes #78. * specs/std_spec.yaml (require): Specify better diagnostics on failure. * lib/std/list.lua (compare): Move from here... * lib/std/base/list.lua (compare): ...to here. * local.mk (dist_luastdbase_DATA): Add lib/std/base/list.lua. * lib/std/base.lua: Break dependency on "std.list". (require): Use base.list.compare directly, and show verbose diagnostics on failure. (module_version, version_to_list): Remove; no longer used. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/base.lua | 44 ++++++++++++++++++------------------------- lib/std/base/list.lua | 36 +++++++++++++++++++++++++++++++++++ lib/std/list.lua | 20 +------------------- local.mk | 1 + specs/std_spec.yaml | 33 +++++++++++++++++++++----------- 6 files changed, 81 insertions(+), 56 deletions(-) create mode 100644 lib/std/base/list.lua diff --git a/NEWS b/NEWS index ea37e8d..131cdd9 100644 --- a/NEWS +++ b/NEWS @@ -122,6 +122,9 @@ Stdlib NEWS - User visible changes All of stdlib's implementation now uses `std.pairs` rather than `pairs` internally. + - `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, while respecting `__len`. diff --git a/lib/std/base.lua b/lib/std/base.lua index 1d1a271..3a7d864 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,9 +23,10 @@ ]] -local base = require "std.base.string" local callable = require "std.base.functional".callable -local copy, render, split = base.copy, base.render, base.split +local compare = require "std.base.list".compare +local bstring = require "std.base.string" +local copy, render, split = bstring.copy, bstring.render, bstring.split @@ -87,25 +88,6 @@ local function getmetamethod (x, n) end ---- Return a List object by splitting version string on periods. --- @string version a period delimited version string --- @treturn List a list of version components -local function version_to_list (version) - return require "std.list" (split (version, "%.")) -end - - ---- Extract a list of period delimited integer version components. --- @tparam table module returned from a `require` call --- @string pattern to capture version number from a string --- (default: `"([%.%d]+)%D*$"`) --- @treturn List a list of version components -local function module_version (module, pattern) - local version = module.version or module._VERSION - return version_to_list (version:match (pattern or "([%.%d]+)%D*$")) -end - - --- Iterator adaptor for discarding first value from core iterator function. -- @func factory iterator to be wrapped -- @param ... *factory* arguments @@ -148,13 +130,23 @@ local function eval (s) end -local function require_version (module, min, too_big, pattern) - local m = require (module) +local function vcompare (a, b) + return compare (split (a, "%."), split (b, "%.")) +end + + +local _require = require + +local function require (module, min, too_big, pattern) + local m = _require (module) + local v = (m.version or m._VERSION or ""):match (pattern or "([%.%d]+)%D*$") if min then - assert (module_version (m, pattern) >= version_to_list (min)) + assert (vcompare (v, min) >= 0, "require '" .. module .. + "' with at least version " .. min .. ", but found version " .. v) end if too_big then - assert (module_version (m, pattern) < version_to_list (too_big)) + assert (vcompare (v, too_big) < 0, "require '" .. module .. + "' with version less than " .. too_big .. ", but found version " .. v) end return m end @@ -816,7 +808,7 @@ return setmetatable ({ ireverse = ireverse, pairs = pairs, ripairs = ripairs, - require = require_version, + require = require, tostring = tostring, -- object.lua -- diff --git a/lib/std/base/list.lua b/lib/std/base/list.lua new file mode 100644 index 0000000..29133d2 --- /dev/null +++ b/lib/std/base/list.lua @@ -0,0 +1,36 @@ +--[[-- + Base implementations of functions exported by `std.list`. + + These functions are required by implementations of exported functions + in other stdlib modules. We keep them here to ensure argument checking + error messages report the correct module ('std.list') after "%.base" + has been stripped from `debug.getinfo (fn, "S").source`. + + @module std.base.list +]] + + +local function compare (l, m) + for i = 1, math.min (#l, #m) 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 #l < #m then + return -1 + elseif #l > #m then + return 1 + end + return 0 +end + + +return { + compare = compare, +} diff --git a/lib/std/list.lua b/lib/std/list.lua index 5228b6c..791c0fe 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -64,25 +64,7 @@ end -- @tparam table m another list -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 -- if `l` is greater than `m` -local function compare (l, m) - for i = 1, math.min (#l, #m) 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 #l < #m then - return -1 - elseif #l > #m then - return 1 - end - return 0 -end +local compare = base.list.compare --- Concatenate arguments into a list. diff --git a/local.mk b/local.mk index 1967279..7168567 100644 --- a/local.mk +++ b/local.mk @@ -86,6 +86,7 @@ luastdbasedir = $(luastddir)/base dist_luastdbase_DATA = \ lib/std/base/functional.lua \ + lib/std/base/list.lua \ lib/std/base/string.lua \ lib/std/base/tree.lua \ $(NOTHING_ELSE) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 66bd039..f70c4af 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -417,9 +417,11 @@ specify std: - 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 () + 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 () + 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") @@ -440,16 +442,25 @@ specify std: - after: std.version = ver - it diagnoses module too old: - expect (f ("std", "1.2.4")).to_raise () - expect (f ("std", "1.3")).to_raise () - expect (f ("std", "2.1.2")).to_raise () - expect (f ("std", "2")).to_raise () - expect (f ("std", "1.2.10")).to_raise () + 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 () - expect (f ("std", nil, "1.1")).to_raise () - expect (f ("std", nil, "1.1.2")).to_raise () - expect (f ("std", nil, "1")).to_raise () + 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) From 560e08266e21b353250ab93a137a4c642dce143c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 Aug 2014 14:49:41 +0100 Subject: [PATCH 391/703] base: remove export table metadata. * lib/std/base.lua: Remove export table metadata. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 3a7d864..9b06bbc 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -796,8 +796,6 @@ end -- @section Metamethods return setmetatable ({ - "std.base", - -- std.lua -- assert = assert, case = case, From 7cc3b6f1ce558a8ab18abf2dea0e208ff82decff Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 25 Aug 2014 16:03:49 +0100 Subject: [PATCH 392/703] base: support module name override with std.base.export. * lib/std/base/list.lua (compare): Move from here... * lib/std/base.lua (compare): ...to here. * lib/std/base/list.lua: Remove. * local.mk (dist_luastdbase_DATA): Remove lib/std/base/list.lua. * lib/std/list.lua (M.compare): Provide explicit module name argument. * lib/std/base.lua (export): If an explicit module name was passed, use that instead of reverse engineering it from the source file containing an exported function. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 38 +++++++++++++++++++++++++++++++++----- lib/std/base/list.lua | 36 ------------------------------------ lib/std/list.lua | 4 ++-- local.mk | 1 - 4 files changed, 35 insertions(+), 44 deletions(-) delete mode 100644 lib/std/base/list.lua diff --git a/lib/std/base.lua b/lib/std/base.lua index 9b06bbc..9b03a15 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,7 +24,6 @@ local callable = require "std.base.functional".callable -local compare = require "std.base.list".compare local bstring = require "std.base.string" local copy, render, split = bstring.copy, bstring.render, bstring.split @@ -125,6 +124,27 @@ local function assert (expect, f, arg1, ...) end +local function compare (l, m) + for i = 1, math.min (#l, #m) 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 #l < #m then + return -1 + elseif #l > #m then + return 1 + end + return 0 +end + + local function eval (s) return loadstring ("return " .. s)() end @@ -629,13 +649,13 @@ end -- element in the *types* table with `argcheck`, if the final element of -- *types* ends with an asterisk, remaining unchecked arguments are checked -- against that type. --- @function export --- @tparam table M module table +-- @string[opt] mname module name (default: looked up with *decl*) -- @string decl function type declaration string --- @func fn value to store at *name* in *M* -- @usage -- export (M, "round (number, int?)", std.math.round) -local function export (decl, ...) +local function export (mname, decl) + if decl == nil then mname, decl = nil, mname end + -- Parse "fname (argtype, argtype, argtype...)". local name, types = decl:match "([%w_][%d%w_]*)%s+%((.*)%)" if types == "" then @@ -647,6 +667,11 @@ local function export (decl, ...) end local fqfname, inner = getinfo (name, 2) + -- Trust the user *mname* argument, if given. + if mname then + fqfname = mname .. "." .. name + end + local fn = inner -- When argument checking is enabled, wrap in type checking function. @@ -809,6 +834,9 @@ return setmetatable ({ require = require, tostring = tostring, + -- list.lua -- + compare = compare, + -- object.lua -- prototype = prototype, diff --git a/lib/std/base/list.lua b/lib/std/base/list.lua deleted file mode 100644 index 29133d2..0000000 --- a/lib/std/base/list.lua +++ /dev/null @@ -1,36 +0,0 @@ ---[[-- - Base implementations of functions exported by `std.list`. - - These functions are required by implementations of exported functions - in other stdlib modules. We keep them here to ensure argument checking - error messages report the correct module ('std.list') after "%.base" - has been stripped from `debug.getinfo (fn, "S").source`. - - @module std.base.list -]] - - -local function compare (l, m) - for i = 1, math.min (#l, #m) 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 #l < #m then - return -1 - elseif #l > #m then - return 1 - end - return 0 -end - - -return { - compare = compare, -} diff --git a/lib/std/list.lua b/lib/std/list.lua index 791c0fe..bc2f9ab 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -64,7 +64,7 @@ end -- @tparam table m another list -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 -- if `l` is greater than `m` -local compare = base.list.compare +local compare = base.compare --- Concatenate arguments into a list. @@ -143,7 +143,7 @@ local export = base.export --- @export local M = { append = export "append (List, any)", - compare = export "compare (List, List|table)", + compare = export ("std.list", "compare (List, List|table)"), concat = export "concat (List, List|table*)", cons = export "cons (List, any)", rep = export "rep (List, int)", diff --git a/local.mk b/local.mk index 7168567..1967279 100644 --- a/local.mk +++ b/local.mk @@ -86,7 +86,6 @@ luastdbasedir = $(luastddir)/base dist_luastdbase_DATA = \ lib/std/base/functional.lua \ - lib/std/base/list.lua \ lib/std/base/string.lua \ lib/std/base/tree.lua \ $(NOTHING_ELSE) From 5bbdf503f4af005c54aafc1d61a64c4157f60f08 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 25 Aug 2014 16:48:49 +0100 Subject: [PATCH 393/703] refactor: break std.debug dependency on std.functional and std.string. * specs/debug.spec (say): Specify usage of std.tostring. * lib/std/debug.lua (tabify): Remove. (say) Manually unroll functional compose sequence, formerly knows as tabify. (trace): Manually unroll and consequently remove string.rep call. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 36 +++++++----------------------------- specs/debug_spec.yaml | 5 +++++ 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index d40e9de..f2f8105 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -30,39 +30,14 @@ local _DEBUG = require "std.debug_init"._DEBUG - local base = require "std.base" -local functional = require "std.functional" -local string = require "std.string" -local ielems = base.ielems +local tostring = base.tostring local M ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - ---- Stringify a list of objects, then tabulate the resulting list of strings. --- @tparam table list of elements --- @treturn string tab delimited string --- @usage s = tabify {...} -local tabify = functional.compose ( - -- map (elementfn, iterfn, unnbound_table_arg) - functional.bind (functional.map, {string.tostring, ielems}), - -- table.concat (unbound_strbuf_table, "\t") - functional.bind (table.concat, {[2] = "\t"})) - - - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - --- Control std.debug function behaviour. -- To activate debugging set _DEBUG either to any true value -- (equivalent to {level = 1}), or as documented below. @@ -78,7 +53,7 @@ local tabify = functional.compose ( --- Print a debugging message to `io.stderr`. --- Display arguments passed through `std.string.tostring` and separated by tab +-- Display arguments passed through `std.tostring` and separated by tab -- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` -- is a number greater than or equal to *n*. If `_DEBUG` is false or -- nil, nothing is written. @@ -99,7 +74,9 @@ local function say (n, ...) ((type (_DEBUG) == "table" and type (_DEBUG.level) == "number" and _DEBUG.level >= level) or level <= 1) then - io.stderr:write (tabify (arg) .. "\n") + local t = {} + for k, v in pairs (arg) do t[k] = tostring (v) end + io.stderr:write (table.concat (t, "\t") .. "\n") end end @@ -117,7 +94,8 @@ local level = 0 -- local debug = require "std.debug" local function trace (event) local t = debug.getinfo (3) - local s = " >>> " .. string.rep (" ", level) + 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 diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index f12ef92..0c58bd1 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -563,6 +563,11 @@ specify std.debug: table.concat (require "std.functional".map (mkwrap, {...}), ", ")) end + f = M.say + + - it uses stdlib tostring: + expect (luaproc [[require "std.debug".say {"debugging"}]]). + to_contain_error (require "std".tostring {"debugging"}) - context when _DEBUG is disabled: - it does nothing when message level is not set: expect (luaproc (mksay (false, "nothing to see here"))). From 3016c957ed66479d26437ef01df7c880f01bfaf4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 25 Aug 2014 18:53:51 +0100 Subject: [PATCH 394/703] refactor: move DEPRECATED, export et.al. from `base` to `debug`. * specs/base_spec.yaml (export, DEPRECATED): Move from here... * specs/debug_spec.yaml (export, DEPRECATED): ...to here. * specs/base_spec.yaml: Remove unused file. * specs/specs.mk (specl_SPECS): Remove specs/base_spec.yaml. * lib/std/base.lua (DEPRECATED, DEPRECATIONMSG, argcheck) (argerror, arglen, argscheck, export, getcompat, setcompat) (toomanyarg_fmt): Move from here... * lib/std/debug.lua (DEPRECATED, DEPRECATIONMSG, argcheck) (argerror, arglen, argscheck, export, getcompat, setcompat) (toomanyarg_fmt): ...to here. Adjust all callers. * lib/std/base.lua (argpairs, checktype, concat, formaterror) (getfenv, getinfo, match, merge, normalize, permutations) (setfenv, whatpath): Move from here... * lib/std/debug.lua (argpairs, checktype, concat, formaterror) (getfenv, getinfo, match, merge, normalize, permutations) (setfenv, whatpath): ...to here, but elide their definitions if _DEBUG.argcheck is false, or equivalent. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 10 +- lib/std.lua.in | 2 +- lib/std/base.lua | 643 +--------------------------------- lib/std/container.lua | 7 +- lib/std/debug.lua | 765 ++++++++++++++++++++++++++++++++++++----- lib/std/functional.lua | 13 +- lib/std/io.lua | 7 +- lib/std/list.lua | 5 +- lib/std/math.lua | 2 +- lib/std/package.lua | 3 +- lib/std/string.lua | 5 +- lib/std/table.lua | 7 +- lib/std/vector.lua | 3 +- specs/base_spec.yaml | 175 ---------- specs/debug_spec.yaml | 173 +++++++++- specs/specs.mk | 1 - 16 files changed, 893 insertions(+), 928 deletions(-) delete mode 100644 specs/base_spec.yaml diff --git a/NEWS b/NEWS index 131cdd9..06c07fe 100644 --- a/NEWS +++ b/NEWS @@ -13,9 +13,13 @@ Stdlib NEWS - User visible changes - New `debug.argscheck` function for checking all function parameter types with a single function call in the common case. - - New `_DEBUG.argcheck` field that disables `debug.argcheck` (and - `debug.argscheck`) for production code. Similarly `_DEBUG = false` - disables those functions too. + - 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`, + `debug.argscheck` and changes `debug.export` to return its function + argument unwrapped, for production code. Similarly `_DEBUG = false` + deactivates these functions in the same way. - New `std.vector` object, for clean and fast queue-like or stack-like container management. When alien is installed, and element types diff --git a/lib/std.lua.in b/lib/std.lua.in index 08ef2b3..d97aab3 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -244,7 +244,7 @@ end -- @table std -- @field version release version string -local export = base.export +local export = require "std.debug".export M = { assert = export "assert (any?, string?, any?*)", diff --git a/lib/std/base.lua b/lib/std/base.lua index 9b03a15..4596cdc 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -195,632 +195,12 @@ local function prototype (o) end - ---[[ ================== ]]-- ---[[ Argument Checking. ]]-- ---[[ ================== ]]-- - - -local debug_init = require "std.debug_init" - -local _ARGCHECK = debug_init._ARGCHECK -local _DEBUG = debug_init._DEBUG - - -local argcheck, argerror, argscheck -- forward declarations - - -local toomanyarg_fmt = - "too many arguments to '%s' (no more than %d expected, got %d)" - - ---- Concatenate a table of strings using ", " and " or " delimiters. --- @tparam table alternatives a table of strings --- @treturn string string of elements from alternatives delimited by ", " --- and " or " -local function concat (alternatives) - if #alternatives > 1 then - local t = copy (alternatives) - local top = table.remove (t) - t[#t] = t[#t] .. " or " .. top - alternatives = t - end - return table.concat (alternatives, ", ") -end - - ---- Normalize a list of type names. --- @tparam table list of type names, trailing "?" as required --- @treturn table a new list with "?" stripped, "nil" appended if so, --- and with duplicates stripped. -local function normalize (t) - local i, r, add_nil = 1, {}, false - for _, v in ipairs (t) do - local m = v:match "^(.+)%?$" - if m then - add_nil = true - r[m] = r[m] or i - i = i + 1 - elseif v then - r[v] = r[v] or i - i = i + 1 - end - end - if add_nil then - r["nil"] = r["nil"] or i - end - - -- Invert the return table. - local t = {} - for v, i in pairs (r) do t[i] = v end - return t -end - - ---- Argument list length. --- Like #table, but does not stop at the first nil value. --- @tparam table t a table --- @treturn int largest integer key in *t* --- @usage tmax = arglen (t) -local function arglen (t) - local len = 0 - for k in pairs (t) do - if type (k) == "number" and k > len then len = k end - end - return len -end - - ---- Ordered iterator for integer keyed values. --- Like ipairs, but does not stop at the first nil value. --- @tparam table t a table --- @treturn function iterator function --- @treturn table t --- @usage --- for i,v in argpairs {"one", nil, "three"} do print (i, v) end -local function argpairs (t) - local i, max = 0, 0 - for k in pairs (t) do - if type (k) == "number" and k > max then max = k end - end - return function (t) - i = i + 1 - if i <= max then return i, t[i] end - end, - t, true -end - - ---- Merge |-delimited type-specs, omitting duplicates. --- @string ... type-specs --- @treturn table list of merged and normalized type-specs -local function merge (...) - local i, t = 1, {} - for _, v in argpairs {...} do - v:gsub ("([^|]+)", function (m) t[i] = m; i = i + 1 end) - end - return normalize (t) -end - - ---- Calculate permutations of type lists with and without [optionals]. --- @tparam table types a list of expected types by argument position --- @treturn table set of possible type lists -local function permutations (types) - local p, sentinel = {{}}, {"optional arg"} - for i, v in ipairs (types) do - -- Remove sentinels before appending `v` to each list. - for _, v in ipairs (p) do - if v[#v] == sentinel then table.remove (v) end - end - - local opt = v:match "%[(.+)%]" - if opt == nil then - -- Append non-optional type-spec to each permutation. - for b = 1, #p do table.insert (p[b], v) end - else - -- Duplicate all existing permutations, and add optional type-spec - -- to the unduplicated permutations. - local o = #p - for b = 1, o do - p[b + o] = copy (p[b]) - table.insert (p[b], opt) - end - - -- Leave a marker for optional argument in final position. - for _, v in ipairs (p) do - table.insert (v, sentinel) - end - end - end - - -- Replace sentinels with "nil". - for i, v in ipairs (p) do - if v[#v] == sentinel then - table.remove (v) - if #v > 0 then - v[#v] = v[#v] .. "|nil" - else - v[1] = "nil" - end - end - end - - return p -end - - ---- Return index of the first mismatch between types and args, or `nil`. --- @tparam table types a list of expected types by argument position --- @tparam table args a table of arguments to compare --- @treturn int|nil position of first mismatch in *types* -local function match (types, args, allargs) - local typec, argc = #types, arglen (args) - for i = 1, typec do - local ok = pcall (argcheck, "pcall", i, types[i], args[i]) - if not ok then return i end - end - if allargs then - for i = typec + 1, argc do - local ok = pcall (argcheck, name, i, types[typec], args[i]) - if not ok then return i end - end - end -end - - ---- Format a type mismatch error. --- @tparam table expectedtypes a table of matchable types --- @string actual the actual argument to match with --- @treturn string formatted *extramsg* for this mismatch for @{argerror} -local function formaterror (expectedtypes, actual, index) - local actualtype = prototype (actual) - - -- Tidy up actual type for display. - if actualtype == "nil" then - actualtype = "no value" - elseif actualtype == "string" and actual:sub (1, 1) == ":" then - actualtype = actual - elseif type (actual) == "table" and next (actual) == nil then - local matchstr = "," .. table.concat (expectedtypes, ",") .. "," - if actualtype == "table" and matchstr == ",#list," then - actualtype = "empty list" - elseif actualtype == "table" or matchstr:match ",#" then - actualtype = "empty " .. actualtype - end - end - - if index then - actualtype = actualtype .. " at index " .. tostring (index) - end - - -- Tidy up expected types for display. - local expectedstr = expectedtypes - if type (expectedtypes) == "table" then - local t = {} - for i, v in ipairs (expectedtypes) do - if v == "func" then - t[i] = "function" - elseif v == "any" then - t[i] = "any value" - elseif not index then - t[i] = v:match "(%S+) of %S+" or v - else - t[i] = v - end - end - expectedstr = concat (t): - gsub ("#table", "non-empty table"): - gsub ("#list", "non-empty list"): - gsub ("(%S+ of %S+)", "%1s"): - gsub ("(%S+ of %S+)ss", "%1s") - end - - return expectedstr .. " expected, got " .. actualtype -end - - ---- Compare *check* against type of *actual* --- @string check extended type name expected --- @param actual object being typechecked --- @treturn boolean `true` if *actual* is of type *check*, otherwise --- `false` -local function checktype (check, actual) - local actualtype = prototype (actual) - if check == "#table" then - if actualtype == "table" and next (actual) then - return true - end - - elseif check == "any" then - if actual ~= nil then - return true - end - - elseif check == "file" then - if io.type (actual) == "file" then - return true - end - - elseif check == "function" or check == "func" then - if actualtype == "function" or - (getmetatable (actual) or {}).__call ~= nil - then - return true - end - - elseif check == "int" then - if actualtype == "number" and actual == math.floor (actual) then - return true - end - - elseif check == "list" or check == "#list" then - if actualtype == "table" or actualtype == "List" then - local len, count = #actual, 0 - local i = next (actual) - repeat - if i ~= nil then count = count + 1 end - i = next (actual, i) - until i == nil or count > len - if count == len and (check == "list" or count > 0) then - return true - end - end - - elseif check == "object" then - if actualtype ~= "table" and type (actual) == "table" then - return true - end - - elseif type (check) == "string" and check:sub (1, 1) == ":" then - if check == actual then - return true - end - - elseif check == actualtype then - return true - end - - return false -end - - -if _ARGCHECK then - - -- Doc-commented in debug.lua - function argcheck (name, i, expected, actual, level) - level = level or 2 - expected = normalize (split (expected, "|")) - - -- Check actual has one of the types from expected - local ok = false - for _, expect in ipairs (expected) do - local check, contents = expect:match "^(%S+) of (%S-)s?$" - check = check or expect - - -- Does the type of actual check out? - ok = checktype (check, actual) - - -- For "table of things", check all elements are a thing too. - if ok and contents and type (actual) == "table" then - for k, v in pairs (actual) do - if not checktype (contents, v) then - argerror (name, i, formaterror (expected, v, k), level + 1) - end - end - end - if ok then break end - end - - if not ok then - argerror (name, i, formaterror (expected, actual), level + 1) - end - end - - - -- Doc-commented in debug.lua - function argscheck (name, expected, actual) - if type (expected) ~= "table" then expected = {expected} end - if type (actual) ~= "table" then actual = {actual} end - - for i, v in ipairs (expected) do - argcheck (name, i, expected[i], actual[i], 3) - end - end - -else - - -- Turn off argument checking if _DEBUG is false, or a table containing - -- a false valued `argcheck` field. - - argcheck = nop - argscheck = nop - -end - - --- Doc-commented in debug.lua... --- This function is not disabled by setting _DEBUG. -function argerror (name, i, extramsg, level) - level = level or 1 - local s = string.format ("bad argument #%d to '%s'", i, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) -end - - - ---[[ ============ ]]-- ---[[ Maintenance. ]]-- ---[[ ============ ]]-- - - --- Lua 5.1 requires 'debug.setfenv' to change environment of C funcs; -local _setfenv = debug.setfenv - - -local function setfenv (f, t) - -- Unwrap functable: - if type (f) == "table" then - f = f.call or (getmetatable (f) or {}).__call - end - - if _setfenv then - return _setfenv (f, t) - - else - -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html - local name - local up = 0 - repeat - up = up + 1 - name = debug.getupvalue (f, up) - until name == '_ENV' or name == nil - if name then - debug.upvaluejoin (f, up, function () return name end, 1) - debug.setupvalue (f, up, t) - end - - return f - end -end - - --- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html -local getfenv = getfenv or function(f) - f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) - local name, val - local up = 0 - repeat - up = up + 1 - name, val = debug.getupvalue(f, up) - until name == '_ENV' or name == nil - return val -end - - -local dirsep, pathsep, path_mark = package.config:match "^(%S+)\n(%S+)\n(%S+)\n" -local pathpatt, markpatt = "[^" .. pathsep .. "]+", path_mark:gsub ("%p", "%%%0") - -local function whatpath (name, src) - local r - package.path:gsub (pathpatt, function (s) - local substituted = s:gsub (markpatt, (name:gsub ("%.", dirsep))) - if substituted == src then r = name end - end) - return r -end - - -local function getinfo (what, level) - local fqfname, s, fn - - for i = 1, math.huge do - s, fn = debug.getlocal (level + 1, i) - - if s == nil then - break - - elseif s == what or fn == what then - local t, src = {}, debug.getinfo (callable (fn), "S").source:gsub ("^@(.*)$", "%1") - src:gsub ("/([^/]+)", function (m) t[#t + 1] = m:gsub ("%.lua", "") end) - - local tryme - for i = #t, 1, -1 do - tryme = tryme and (t[i] .. "." .. tryme) or t[i] - if whatpath (tryme, src) then - fqfname = (tryme .. "." .. s):gsub ("^(std%.)base%.", "%1") - break - end - end - break - - end - end - - return fqfname, fn -end - - ---- Export a function definition, optionally with argument type checking. --- In addition to checking that each argument type matches the corresponding --- element in the *types* table with `argcheck`, if the final element of --- *types* ends with an asterisk, remaining unchecked arguments are checked --- against that type. --- @string[opt] mname module name (default: looked up with *decl*) --- @string decl function type declaration string --- @usage --- export (M, "round (number, int?)", std.math.round) -local function export (mname, decl) - if decl == nil then mname, decl = nil, mname end - - -- Parse "fname (argtype, argtype, argtype...)". - local name, types = decl:match "([%w_][%d%w_]*)%s+%((.*)%)" - if types == "" then - types = {} - elseif types then - types = split (types, ",%s+") - else - name = decl:match "([%w_][%d%w_]*)" - end - local fqfname, inner = getinfo (name, 2) - - -- Trust the user *mname* argument, if given. - if mname then - fqfname = mname .. "." .. name - end - - local fn = inner - - -- When argument checking is enabled, wrap in type checking function. - if _ARGCHECK then - -- If the final element of types ends with "*", then set max to a - -- sentinel value to denote type-checking of *all* remaining - -- unchecked arguments against that type-spec is required. - local max, fin = #types, (types[#types] or ""):match "^(.+)%*$" - if fin then - max = math.huge - types[#types] = fin - end - - -- For optional arguments wrapped in square brackets, make sure - -- type-specs allow for passing or omitting an argument of that - -- type. - local typec, type_specs = #types, permutations (types) - - fn = function (...) - local args = {...} - local argc, bestmismatch, at = arglen (args), 0, 0 - - for i, types in ipairs (type_specs) do - local mismatch = match (types, args, max == math.huge) - if mismatch == nil then - bestmismatch = nil - break -- every argument matched its type-spec - end - - if mismatch > bestmismatch then bestmismatch, at = mismatch, i end - end - - if bestmismatch ~= nil then - -- Report an error for all possible types at bestmismatch index. - local expected - if max == math.huge and bestmismatch >= typec then - expected = normalize (split (types[typec], "|")) - else - local tables = {} - for i, types in ipairs (type_specs) do - if types[bestmismatch] then - tables[#tables + 1] = types[bestmismatch] - end - end - expected = merge (unpack (tables)) - end - local i = bestmismatch - - -- For "table of things", check all elements are a thing too. - if types[i] then - local check, contents = types[i]:match "^(%S+) of (%S-)s?$" - if contents and type (args[i]) == "table" then - for k, v in pairs (args[i]) do - if not checktype (contents, v) then - argerror (fqfname or name, i, formaterror (expected, v, k), 2) - end - end - end - end - - -- Otherwise the argument type itself was mismatched. - argerror (fqfname or name, i, formaterror (expected, args[i]), 2) - end - - if argc > max then - error (string.format (toomanyarg_fmt, fqfname or name, max, argc), 2) - end - - -- Propagate outer environment to inner function. - setfenv (inner, getfenv (1)) - - return inner (...) - end - end - - return fn -end - - --- Whether to show a deprecation warning the next time a give key is set. -local compat = {} - - ---- Determine whether *key* will show a deprecation warning on next access. --- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; --- if `_DEBUG.compat` is false, warn every time *fn* is called; --- otherwise don't write any warnings, and run *fn* normally. --- @param key unique identifier for a deprecated API. -local function setcompat (key) - compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat == nil) or _DEBUG == true -end - - ---- Get the deprecation warning status for *key*. --- @param key unique identifier for a deprecated API. --- @treturn boolean whether to show a deprecation warning. -local function getcompat (key) - if compat[key] == nil then - -- Whether to warn on first access. - compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat) or _DEBUG == false - end - return compat[key] -end - - ---- Format a deprecation warning message. --- @string version first deprecation release version --- @string name function name for automatic warning message --- @string[opt] extramsg additional warning text --- @int level call stack level to blame for the error --- @treturn string deprecation warning message -local function DEPRECATIONMSG (version, name, extramsg, level) - if level == nil then level, extramsg = extramsg, nil end - extramsg = extramsg or "and will be removed entirely in a future release" - - local _, where = pcall (function () error ("", level + 3) end) - return (where .. string.format ("%s was deprecated in release %s, %s.\n", - name, version, extramsg)) -end - - ---- Write a deprecation warning to stderr. --- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; --- if `_DEBUG.compat` is false, warn every time *fn* is called; --- otherwise don't write any warnings, and run *fn* normally. --- @string version first deprecation release version --- @string name function name for automatic warning message --- @string[opt] extramsg additional warning text --- @func fn deprecated function --- @return a function to show the warning on first call, and hand off to *fn* --- @usage funcname = deprecate (function (...) ... end, "funcname") -local function DEPRECATED (version, name, extramsg, fn) - if fn == nil then fn, extramsg = extramsg, nil end - - return function (...) - if not getcompat (name) then - io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) - setcompat (name) - end - return fn (...) - end -end - - - --- Metamethods -- @section Metamethods return setmetatable ({ + len = len, + -- std.lua -- assert = assert, case = case, @@ -834,6 +214,9 @@ return setmetatable ({ require = require, tostring = tostring, + -- functional.lua -- + nop = function () end, + -- list.lua -- compare = compare, @@ -847,22 +230,6 @@ return setmetatable ({ -- table.lua -- getmetamethod = getmetamethod, - -- Argument Checking. -- - argcheck = argcheck, - argerror = argerror, - arglen = arglen, - argscheck = argscheck, - - -- Maintenance -- - DEPRECATED = DEPRECATED, - DEPRECATIONMSG = DEPRECATIONMSG, - getcompat = getcompat, - setcompat = setcompat, - - export = export, - len = len, - toomanyarg_fmt = toomanyarg_fmt, - }, { --- Lazy loading of shared base modules. diff --git a/lib/std/container.lua b/lib/std/container.lua index b45ba48..4132a51 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -67,11 +67,12 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK -local base = require "std.base" +local base = require "std.base" +local debug = require "std.debug" local ipairs, pairs = base.ipairs, base.pairs local prototype = base.prototype -local argcheck, export = base.argcheck, base.export +local argcheck, export = debug.argcheck, debug.export @@ -237,7 +238,7 @@ local M = { if _ARGCHECK then - local arglen, toomanyarg_fmt = base.arglen, base.toomanyarg_fmt + local arglen, toomanyarg_fmt = debug.arglen, debug.toomanyarg_fmt M.__call = function (self, x, ...) local mt = getmetatable (self) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index f2f8105..b4b6111 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -29,10 +29,13 @@ ]] -local _DEBUG = require "std.debug_init"._DEBUG +local debug_init = require "std.debug_init" local base = require "std.base" -local tostring = base.tostring +local _ARGCHECK = debug_init._ARGCHECK +local _DEBUG = debug_init._DEBUG +local callable = base.functional.callable +local split, tostring = base.string.split, base.tostring local M @@ -52,6 +55,63 @@ local M -- @usage _DEBUG = { argcheck = false, level = 9 } +--- Extend `debug.setfenv` to unwrap functables correctly. +-- @function setfenv +-- @tparam function|functable fn target function +-- @tparam table env new function environment +-- @treturn function *fn* + +local _setfenv = debug.setfenv + +local function setfenv (fn, env) + -- Unwrap functable: + if type (fn) == "table" then + fn = fn.call or (getmetatable (fn) or {}).__call + end + + if _setfenv then + return _setfenv (fn, env) + + else + -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html + local name + local up = 0 + repeat + up = up + 1 + name = debug.getupvalue (fn, up) + until name == '_ENV' or name == nil + if name then + debug.upvaluejoin (fn, up, function () return name end, 1) + debug.setupvalue (fn, up, env) + end + + return fn + end +end + + +--- Extend `debug.getfenv` to unwrap functables correctly. +-- @function getfenv +-- @tparam int|function|functable fn target function, or stack level +-- @treturn table environment of *fn* +local getfenv = getfenv or function (fn) + -- Unwrap functable: + if type (fn) == "table" then + fn = fn.call or (getmetatable (fn) or {}).__call + elseif type (fn) == "number" then + fn = debug.getinfo (fn + 1, "f").func + end + + local name, env + local up = 0 + repeat + up = up + 1 + name, env = debug.getupvalue (fn, up) + until name == '_ENV' or name == nil + return env +end + + --- Print a debugging message to `io.stderr`. -- Display arguments passed through `std.tostring` and separated by tab -- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` @@ -81,8 +141,6 @@ local function say (n, ...) end -local level = 0 - --- Trace function calls. -- Use as debug.sethook (trace, "cr"), which is done automatically -- when `_DEBUG.call` is set. @@ -92,6 +150,9 @@ local level = 0 -- @usage -- _DEBUG = { call = true } -- local debug = require "std.debug" + +local level = 0 + local function trace (event) local t = debug.getinfo (3) local s = " >>> " @@ -140,103 +201,633 @@ end -- local h, err = input_handle (file) -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end -- ... -local argerror = base.argerror +local function argerror (name, i, extramsg, level) + level = level or 1 + local s = string.format ("bad argument #%d to '%s'", i, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) +end ---[[ - Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, - and this function has to count stack frames correctly and so breaks in - that case. After 5.1 support is dropped, we can enable the - following: -export (M, "argerror (string, int, string?, int?)", base.argerror) -]] +--- Argument list length. +-- Like #table, but does not stop at the first nil value. +-- @tparam table t a table +-- @treturn int largest integer key in *t* +-- @usage tmax = arglen (t) +local function arglen (t) + local len = 0 + for k in pairs (t) do + if type (k) == "number" and k > len then len = k end + end + return len +end ---- Check the type of an argument against expected types. --- Equivalent to luaL_argcheck in the Lua C API. --- --- Call `argerror` if there is a type mismatch. --- --- Argument `actual` must match one of the types from in `expected`, each --- of which can be the name of a primitive Lua type, a stdlib object type, --- or one of the special options below: --- --- #table accept any non-empty table --- any accept any non-nil argument type --- file accept an open file object --- function accept a function, or object with a __call metamethod --- int accept an integer valued number --- list accept a table where all keys are a contiguous 1-based integer range --- #list accept any non-empty list --- object accept any std.Object derived type --- :foo accept only the exact string ":foo", works for any :-prefixed string --- --- The `:foo` format allows for type-checking of self-documenting --- boolean-like constant string parameters predicated on `nil` versus --- `:option` instead of `false` versus `true`. Or you could support --- both: --- --- argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa) --- --- A very common pattern is to have a list of possible types including --- "nil" when the argument is optional. Rather than writing long-hand --- as above, append a question mark to at least one of the list types --- and omit the explicit "nil" entry: --- --- argcheck ("table.copy", 2, "boolean|:nometa?", predicate) --- --- Normally, you should not need to use the `level` parameter, as the --- default is to blame the caller of the function using `argcheck` in --- error messages; which is almost certainly what you want. --- @function argcheck --- @string name function to blame in error message --- @int i argument number to blame in error message --- @string expected specification for acceptable argument types --- @param actual argument passed --- @int[opt=2] level call stack level to blame for the error --- @usage --- local function case (with, branches) --- argcheck ("std.functional.case", 2, "#table", branches) --- ... -local argcheck = base.argcheck +local toomanyarg_fmt = + "too many arguments to '%s' (no more than %d expected, got %d)" ---[[ - Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, - and this function has to count stack frames correctly and so breaks in - that case. After 5.1 support is dropped, we can enable the - following: +local argcheck, argscheck, _export -- forward declarations -export (M, "argcheck (string, int, string, any?, int?)", base.argcheck) -]] +if _ARGCHECK then + + local copy, prototype = base.string.copy, base.prototype + + --- Concatenate a table of strings using ", " and " or " delimiters. + -- @tparam table alternatives a table of strings + -- @treturn string string of elements from alternatives delimited by ", " + -- and " or " + local function concat (alternatives) + if #alternatives > 1 then + local t = copy (alternatives) + local top = table.remove (t) + t[#t] = t[#t] .. " or " .. top + alternatives = t + end + return table.concat (alternatives, ", ") + end + + + --- Normalize a list of type names. + -- @tparam table t list of type names, trailing "?" as required + -- @treturn table a new list with "?" stripped, "nil" appended if so, + -- and with duplicates stripped. + local function normalize (t) + local i, r, add_nil = 1, {}, false + for _, v in ipairs (t) do + local m = v:match "^(.+)%?$" + if m then + add_nil = true + r[m] = r[m] or i + i = i + 1 + elseif v then + r[v] = r[v] or i + i = i + 1 + end + end + if add_nil then + r["nil"] = r["nil"] or i + end + + -- Invert the return table. + local t = {} + for v, i in pairs (r) do t[i] = v end + return t + end + + + --- Ordered iterator for integer keyed values. + -- Like ipairs, but does not stop at the first nil value. + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table t + -- @usage + -- for i,v in argpairs {"one", nil, "three"} do print (i, v) end + local function argpairs (t) + local i, max = 0, 0 + for k in pairs (t) do + if type (k) == "number" and k > max then max = k end + end + return function (t) + i = i + 1 + if i <= max then return i, t[i] end + end, + t, true + end + + + --- Merge |-delimited type-specs, omitting duplicates. + -- @string ... type-specs + -- @treturn table list of merged and normalized type-specs + local function merge (...) + local i, t = 1, {} + for _, v in argpairs {...} do + v:gsub ("([^|]+)", function (m) t[i] = m; i = i + 1 end) + end + return normalize (t) + end + + + --- Calculate permutations of type lists with and without [optionals]. + -- @tparam table types a list of expected types by argument position + -- @treturn table set of possible type lists + local function permutations (types) + local p, sentinel = {{}}, {"optional arg"} + for i, v in ipairs (types) do + -- Remove sentinels before appending `v` to each list. + for _, v in ipairs (p) do + if v[#v] == sentinel then table.remove (v) end + end + + local opt = v:match "%[(.+)%]" + if opt == nil then + -- Append non-optional type-spec to each permutation. + for b = 1, #p do table.insert (p[b], v) end + else + -- Duplicate all existing permutations, and add optional type-spec + -- to the unduplicated permutations. + local o = #p + for b = 1, o do + p[b + o] = copy (p[b]) + table.insert (p[b], opt) + end + + -- Leave a marker for optional argument in final position. + for _, v in ipairs (p) do + table.insert (v, sentinel) + end + end + end + + -- Replace sentinels with "nil". + for i, v in ipairs (p) do + if v[#v] == sentinel then + table.remove (v) + if #v > 0 then + v[#v] = v[#v] .. "|nil" + else + v[1] = "nil" + end + end + end + + return p + end + + + --- Return index of the first mismatch between types and args, or `nil`. + -- @tparam table types a list of expected types by argument position + -- @tparam table args a table of arguments to compare + -- @tparam boolean allargs whether to match all arguments + -- @treturn int|nil position of first mismatch in *types* + local function match (types, args, allargs) + local typec, argc = #types, arglen (args) + for i = 1, typec do + local ok = pcall (argcheck, "pcall", i, types[i], args[i]) + if not ok then return i end + end + if allargs then + for i = typec + 1, argc do + local ok = pcall (argcheck, name, i, types[typec], args[i]) + if not ok then return i end + end + end + end + + + --- Format a type mismatch error. + -- @tparam table expectedtypes a table of matchable types + -- @string actual the actual argument to match with + -- @number[opt] index erroring container element index + -- @treturn string formatted *extramsg* for this mismatch for @{argerror} + local function formaterror (expectedtypes, actual, index) + local actualtype = prototype (actual) + + -- Tidy up actual type for display. + if actualtype == "nil" then + actualtype = "no value" + elseif actualtype == "string" and actual:sub (1, 1) == ":" then + actualtype = actual + elseif type (actual) == "table" and next (actual) == nil then + local matchstr = "," .. table.concat (expectedtypes, ",") .. "," + if actualtype == "table" and matchstr == ",#list," then + actualtype = "empty list" + elseif actualtype == "table" or matchstr:match ",#" then + actualtype = "empty " .. actualtype + end + end + + if index then + actualtype = actualtype .. " at index " .. tostring (index) + end + + -- Tidy up expected types for display. + local expectedstr = expectedtypes + if type (expectedtypes) == "table" then + local t = {} + for i, v in ipairs (expectedtypes) do + if v == "func" then + t[i] = "function" + elseif v == "any" then + t[i] = "any value" + elseif not index then + t[i] = v:match "(%S+) of %S+" or v + else + t[i] = v + end + end + expectedstr = concat (t): + gsub ("#table", "non-empty table"): + gsub ("#list", "non-empty list"): + gsub ("(%S+ of %S+)", "%1s"): + gsub ("(%S+ of %S+)ss", "%1s") + end + + return expectedstr .. " expected, got " .. actualtype + end + + + --- Compare *check* against type of *actual* + -- @string check extended type name expected + -- @param actual object being typechecked + -- @treturn boolean `true` if *actual* is of type *check*, otherwise + -- `false` + local function checktype (check, actual) + local actualtype = prototype (actual) + if check == "#table" then + if actualtype == "table" and next (actual) then + return true + end + + elseif check == "any" then + if actual ~= nil then + return true + end + + elseif check == "file" then + if io.type (actual) == "file" then + return true + end + + elseif check == "function" or check == "func" then + if actualtype == "function" or + (getmetatable (actual) or {}).__call ~= nil + then + return true + end + + elseif check == "int" then + if actualtype == "number" and actual == math.floor (actual) then + return true + end + + elseif check == "list" or check == "#list" then + if actualtype == "table" or actualtype == "List" then + local len, count = #actual, 0 + local i = next (actual) + repeat + if i ~= nil then count = count + 1 end + i = next (actual, i) + until i == nil or count > len + if count == len and (check == "list" or count > 0) then + return true + end + end + + elseif check == "object" then + if actualtype ~= "table" and type (actual) == "table" then + return true + end + + elseif type (check) == "string" and check:sub (1, 1) == ":" then + if check == actual then + return true + end + + elseif check == actualtype then + return true + end + + return false + end + + + --- Check the type of an argument against expected types. + -- Equivalent to luaL_argcheck in the Lua C API. + -- + -- Call `argerror` if there is a type mismatch. + -- + -- Argument `actual` must match one of the types from in `expected`, each + -- of which can be the name of a primitive Lua type, a stdlib object type, + -- or one of the special options below: + -- + -- #table accept any non-empty table + -- any accept any non-nil argument type + -- file accept an open file object + -- function accept a function, or object with a __call metamethod + -- int accept an integer valued number + -- list accept a table where all keys are a contiguous 1-based integer range + -- #list accept any non-empty list + -- object accept any std.Object derived type + -- :foo accept only the exact string ":foo", works for any :-prefixed string + -- + -- The `:foo` format allows for type-checking of self-documenting + -- boolean-like constant string parameters predicated on `nil` versus + -- `:option` instead of `false` versus `true`. Or you could support + -- both: + -- + -- argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa) + -- + -- A very common pattern is to have a list of possible types including + -- "nil" when the argument is optional. Rather than writing long-hand + -- as above, append a question mark to at least one of the list types + -- and omit the explicit "nil" entry: + -- + -- argcheck ("table.copy", 2, "boolean|:nometa?", predicate) + -- + -- Normally, you should not need to use the `level` parameter, as the + -- default is to blame the caller of the function using `argcheck` in + -- error messages; which is almost certainly what you want. + -- @function argcheck + -- @string name function to blame in error message + -- @int i argument number to blame in error message + -- @string expected specification for acceptable argument types + -- @param actual argument passed + -- @int[opt=2] level call stack level to blame for the error + -- @usage + -- local function case (with, branches) + -- argcheck ("std.functional.case", 2, "#table", branches) + -- ... + function argcheck (name, i, expected, actual, level) + level = level or 2 + expected = normalize (split (expected, "|")) + + -- Check actual has one of the types from expected + local ok = false + for _, expect in ipairs (expected) do + local check, contents = expect:match "^(%S+) of (%S-)s?$" + check = check or expect + + -- Does the type of actual check out? + ok = checktype (check, actual) + + -- For "table of things", check all elements are a thing too. + if ok and contents and type (actual) == "table" then + for k, v in pairs (actual) do + if not checktype (contents, v) then + argerror (name, i, formaterror (expected, v, k), level + 1) + end + end + end + if ok then break end + end + + if not ok then + argerror (name, i, formaterror (expected, actual), level + 1) + end + end + + + --- Check that all arguments match specified types. + -- @function argscheck + -- @string name function to blame in error message + -- @tparam table expected a list of acceptable argument types + -- @tparam table actual table of argument values + -- @usage + -- local function curry (f, n) + -- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) + -- ... + function argscheck (name, expected, actual) + if type (expected) ~= "table" then expected = {expected} end + if type (actual) ~= "table" then actual = {actual} end + + for i, v in ipairs (expected) do + argcheck (name, i, expected[i], actual[i], 3) + end + end + + + function _export (inner, name, fqfname, types) + -- If the final element of types ends with "*", then set max to a + -- sentinel value to denote type-checking of *all* remaining + -- unchecked arguments against that type-spec is required. + local max, fin = #types, (types[#types] or ""):match "^(.+)%*$" + if fin then + max = math.huge + types[#types] = fin + end + + -- For optional arguments wrapped in square brackets, make sure + -- type-specs allow for passing or omitting an argument of that + -- type. + local typec, type_specs = #types, permutations (types) + + return function (...) + local args = {...} + local argc, bestmismatch, at = arglen (args), 0, 0 + + for i, types in ipairs (type_specs) do + local mismatch = match (types, args, max == math.huge) + if mismatch == nil then + bestmismatch = nil + break -- every argument matched its type-spec + end + + if mismatch > bestmismatch then bestmismatch, at = mismatch, i end + end + + if bestmismatch ~= nil then + -- Report an error for all possible types at bestmismatch index. + local expected + if max == math.huge and bestmismatch >= typec then + expected = normalize (split (types[typec], "|")) + else + local tables = {} + for i, types in ipairs (type_specs) do + if types[bestmismatch] then + tables[#tables + 1] = types[bestmismatch] + end + end + expected = merge (unpack (tables)) + end + local i = bestmismatch + + -- For "table of things", check all elements are a thing too. + if types[i] then + local check, contents = types[i]:match "^(%S+) of (%S-)s?$" + if contents and type (args[i]) == "table" then + for k, v in pairs (args[i]) do + if not checktype (contents, v) then + argerror (fqfname or name, i, formaterror (expected, v, k), 2) + end + end + end + end + + -- Otherwise the argument type itself was mismatched. + argerror (fqfname or name, i, formaterror (expected, args[i]), 2) + end + + if argc > max then + error (string.format (toomanyarg_fmt, fqfname or name, max, argc), 2) + end + + -- Propagate outer environment to inner function. + setfenv (inner, getfenv (1)) + + return inner (...) + end + end + +else + + -- Turn off argument checking if _DEBUG is false, or a table containing + -- a false valued `argcheck` field. + + argcheck = base.nop + argscheck = base.nop + + _export = function (inner) return inner end + +end + + +local dirsep, pathsep, path_mark = package.config:match "^(%S+)\n(%S+)\n(%S+)\n" +local pathpatt, markpatt = "[^" .. pathsep .. "]+", path_mark:gsub ("%p", "%%%0") + +local function whatpath (name, src) + local r + package.path:gsub (pathpatt, function (s) + local substituted = s:gsub (markpatt, (name:gsub ("%.", dirsep))) + if substituted == src then r = name end + end) + return r +end + + +local function getinfo (what, level) + local fqfname, s, fn + + for i = 1, math.huge do + s, fn = debug.getlocal (level + 1, i) ---- Check that all arguments match specified types. --- @function argscheck --- @string name function to blame in error message --- @tparam table expected a list of acceptable argument types --- @tparam table actual table of argument values + if s == nil then + break + + elseif s == what or fn == what then + local t, src = {}, debug.getinfo (callable (fn), "S").source:gsub ("^@(.*)$", "%1") + src:gsub ("/([^/]+)", function (m) t[#t + 1] = m:gsub ("%.lua", "") end) + + local tryme + for i = #t, 1, -1 do + tryme = tryme and (t[i] .. "." .. tryme) or t[i] + if whatpath (tryme, src) then + fqfname = (tryme .. "." .. s):gsub ("^(std%.)base%.", "%1") + break + end + end + break + + end + end + + return fqfname, fn +end + + +--- Export a function definition, optionally with argument type checking. +-- In addition to checking that each argument type matches the corresponding +-- element in the *types* table with `argcheck`, if the final element of +-- *types* ends with an asterisk, remaining unchecked arguments are checked +-- against that type. +-- @string[opt] mname module name (default: looked up with *decl*) +-- @string decl function type declaration string -- @usage --- local function curry (f, n) --- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) --- ... -local argscheck = base.argscheck +-- M.round = export "round (number, int?)" +local function export (mname, decl) + if decl == nil then mname, decl = nil, mname end + + -- Parse "fname (argtype, argtype, argtype...)". + local name, types = decl:match "([%w_][%d%w_]*)%s+%((.*)%)" + if types == "" then + types = {} + elseif types then + types = split (types, ",%s+") + else + name = decl:match "([%w_][%d%w_]*)" + end + local fqfname, inner = getinfo (name, 2) + + -- Trust the user *mname* argument, if given. + if mname then + fqfname = mname .. "." .. name + end + + return _export (inner, name, fqfname, types) +end ---[[ - Puc-Rio Lua 5.1 messes up tail-call elimination in the argcheck wrapper, - and this function has to count stack frames correctly and so breaks in - that case. After 5.1 support is dropped, we can enable the - following: -export (M, "argscheck (string, #list, table)", base.argscheck) -]] +-- Whether to show a deprecation warning the next time a give key is set. +local compat = {} + + +--- Determine whether *key* will show a deprecation warning on next access. +-- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; +-- if `_DEBUG.compat` is false, warn every time *fn* is called; +-- otherwise don't write any warnings, and run *fn* normally. +-- @param key unique identifier for a deprecated API. +local function setcompat (key) + compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat == nil) or _DEBUG == true +end + + +--- Get the deprecation warning status for *key*. +-- @param key unique identifier for a deprecated API. +-- @treturn boolean whether to show a deprecation warning. +local function getcompat (key) + if compat[key] == nil then + -- Whether to warn on first access. + compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat) or _DEBUG == false + end + return compat[key] +end + + +--- Format a deprecation warning message. +-- @string version first deprecation release version +-- @string name function name for automatic warning message +-- @string[opt] extramsg additional warning text +-- @int level call stack level to blame for the error +-- @treturn string deprecation warning message +local function DEPRECATIONMSG (version, name, extramsg, level) + if level == nil then level, extramsg = extramsg, nil end + extramsg = extramsg or "and will be removed entirely in a future release" + + local _, where = pcall (function () error ("", level + 3) end) + return (where .. string.format ("%s was deprecated in release %s, %s.\n", + name, version, extramsg)) +end + + +--- Write a deprecation warning to stderr. +-- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; +-- if `_DEBUG.compat` is false, warn every time *fn* is called; +-- otherwise don't write any warnings, and run *fn* normally. +-- @string version first deprecation release version +-- @string name function name for automatic warning message +-- @string[opt] extramsg additional warning text +-- @func fn deprecated function +-- @return a function to show the warning on first call, and hand off to *fn* +-- @usage funcname = deprecate (function (...) ... end, "funcname") +local function DEPRECATED (version, name, extramsg, fn) + if fn == nil then fn, extramsg = extramsg, nil end + + return function (...) + if not getcompat (name) then + io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) + setcompat (name) + end + return fn (...) + end +end + --- @export M = { - argcheck = argcheck, - argerror = argerror, - argscheck = argscheck, - say = say, - trace = trace, + DEPRECATED = DEPRECATED, + DEPRECATIONMSG = DEPRECATIONMSG, + argcheck = argcheck, + argerror = argerror, + arglen = arglen, + argscheck = argscheck, + export = export, + getcompat = getcompat, + say = say, + setcompat = setcompat, + toomanyarg_fmt = toomanyarg_fmt, + trace = trace, } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 736e66a..f1f246f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -9,6 +9,7 @@ local base = require "std.base" +local debug = require "std.debug" local operator = require "std.operator" local ipairs, ireverse, len, pairs = @@ -28,11 +29,11 @@ local function bind (fn, ...) if type (argt[1]) == "table" and argt[2] == nil then argt = argt[1] else - if not base.getcompat (bind) then - io.stderr:write (base.DEPRECATIONMSG ("39", + if not debug.getcompat (bind) then + io.stderr:write (debug.DEPRECATIONMSG ("39", "multi-argument 'std.functional.bind'", "use a table of arguments as the second parameter instead", 2)) - base.setcompat (bind) + debug.setcompat (bind) end end @@ -443,7 +444,7 @@ end -- @function nop -- @usage -- if unsupported then vtable["memrmem"] = nop end -local function nop () end +local nop = base.nop --- Zip a table of tables. @@ -489,7 +490,7 @@ local function zip_with (fn, tt) end -local export = base.export +local export = debug.export --- @export local M = { @@ -523,7 +524,7 @@ M.op = operator -- for backwards compatibility --[[ ============= ]]-- -local DEPRECATED = base.DEPRECATED +local DEPRECATED = debug.DEPRECATED M.eval = DEPRECATED ("41", "'std.functional.eval'", diff --git a/lib/std/io.lua b/lib/std/io.lua index d426763..b0cd752 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,14 +11,15 @@ ]] -local base = require "std.base" +local base = require "std.base" +local debug = require "std.debug" local package = { dirsep = string.match (package.config, "^([^\n]+)\n"), } local ipairs, pairs = base.ipairs, base.pairs -local argerror = base.argerror +local argerror = debug.argerror local leaves = base.tree.leaves local split = base.split @@ -256,7 +257,7 @@ local function die (...) end -local export = base.export +local export = debug.export --- @export M = { diff --git a/lib/std/list.lua b/lib/std/list.lua index bc2f9ab..844b8b6 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -27,6 +27,7 @@ local base = require "std.base" +local debug = require "std.debug" local func = require "std.functional" local object = require "std.object" @@ -138,7 +139,7 @@ local function tail (l) end -local export = base.export +local export = debug.export --- @export local M = { @@ -162,7 +163,7 @@ local M = { -- object constructor at the end of this file. -local DEPRECATED = base.DEPRECATED +local DEPRECATED = debug.DEPRECATED local function depair (ls) diff --git a/lib/std/math.lua b/lib/std/math.lua index 85b6168..cab7fc7 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -59,7 +59,7 @@ local function round (n, p) end -local export = require "std.base".export +local export = require "std.debug".export --- @export M = { diff --git a/lib/std/package.lua b/lib/std/package.lua index 39baef1..7608ed6 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -12,6 +12,7 @@ local base = require "std.base" +local debug = require "std.debug" local catfile = require "std.io".catfile local invert = require "std.table".invert local escape_pattern = require "std.string".escape_pattern @@ -177,7 +178,7 @@ local function remove (pathstrings, pos) end -local export = base.export +local export = debug.export --- @export M = { diff --git a/lib/std/string.lua b/lib/std/string.lua index b5fb86a..cf0bbc0 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -11,6 +11,7 @@ ]] local base = require "std.base" +local debug = require "std.debug" local strbuf = require "std.strbuf" local table = require "std.table" @@ -491,7 +492,7 @@ local function pickle (x) end -local export = base.export +local export = debug.export --- @export M = { @@ -525,7 +526,7 @@ M = { --[[ ============= ]]-- -local DEPRECATED = base.DEPRECATED +local DEPRECATED = debug.DEPRECATED M.assert = DEPRECATED ("41", "'std.string.assert'", diff --git a/lib/std/table.lua b/lib/std/table.lua index c1fcc17..62b6c10 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,7 +11,8 @@ ]] -local base = require "std.base" +local base = require "std.base" +local debug = require "std.debug" local collect = base.functional.collect local leaves = base.tree.leaves @@ -373,7 +374,7 @@ local function values (t) end -local export = base.export +local export = debug.export --- @export M = { @@ -404,7 +405,7 @@ M = { --[[ ============= ]]-- -local DEPRECATED = base.DEPRECATED +local DEPRECATED = debug.DEPRECATED M.clone_rename = DEPRECATED ("39", "'std.table.clone_rename'", "use the new `map` argument to 'std.table.clone' instead", diff --git a/lib/std/vector.lua b/lib/std/vector.lua index 25018a8..ad2ffaa 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -36,13 +36,14 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local have_alien, alien = pcall (require, "alien") local base = require "std.base" local container = require "std.container" +local debug = require "std.debug" local Container = container {} local typeof = type local argcheck, argscheck, pairs, prototype = - base.argcheck, base.argscheck, base.pairs, base.prototype + debug.argcheck, debug.argscheck, base.pairs, base.prototype --[[ ================= ]]-- diff --git a/specs/base_spec.yaml b/specs/base_spec.yaml deleted file mode 100644 index 740e328..0000000 --- a/specs/base_spec.yaml +++ /dev/null @@ -1,175 +0,0 @@ -before: - this_module = "std.base" - - M = require (this_module) - - nop = function () end - -specify std.base: -- describe DEPRECATED: - - before: | - function runscript (body, name, args) - return luaproc ( - "require 'std.base'.DEPRECATED ('0', '" .. (name or "runscript") .. "', function (...)" .. - " " .. body .. - " end) " .. - "('" .. table.concat (args or {}, "', '") .. "')" - ) - end - - f, badarg = init (M, this_module, "DEPRECATED") - - - it returns a function: - expect (type (f ("0", "deprecated", nop))).to_be "function" - expect (f ("0", "deprecated", nop)).not_to_be (nop) - - context with deprecated function: - - it executes the deprecated function: - expect (runscript 'error "oh noes!"').to_contain_error "oh noes!" - - it passes arguments to the deprecated function: - expect (runscript ("print (table.concat ({...}, ', '))", nil, - {"foo", "bar", "baz"})).to_output "foo, bar, baz\n" - - it returns deprecated function results: | - script = [[ - DEPRECATED = require "std.base".DEPRECATED - fn = DEPRECATED ("0", "fn", function () return "foo", "bar", "baz" end) - print (fn ()) - ]] - expect (luaproc (script)).to_output "foo\tbar\tbaz\n" - - it writes a warning to stderr: - expect (runscript 'error "oh noes!"'). - to_match_error "deprecated.*, and will be removed" - - it writes the version string to stderr: - expect (runscript 'error "oh noes!"'). - to_contain_error "in release 0" - - it writes the call location to stderr: | - expect (runscript 'error "oh noes!"'). - to_match_error "^%S+:1: " - - context with _DEBUG: - - before: | - script = [[ - DEPRECATED = require "std.base".DEPRECATED - fn = DEPRECATED ("0", "fn", function () io.stderr:write "oh noes!\n" end) - fn () -- line 3 - fn () -- line 4 - ]] - - it warns only on first call by default: - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" - - it warns only on first call with _DEBUG set to true: - script = "_DEBUG = true " .. script - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" - - it warns only on first call with _DEBUG.compat unset: - script = "_DEBUG = {} " .. script - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" - - it does not warn at all when set to false: | - script = "_DEBUG = false " .. script - expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - - it does not warn at all when compat field is set to true: | - script = "_DEBUG = { compat = true } " .. script - expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - - it warns on every call with compat field set to false: - script = "_DEBUG = { compat = false } " .. script - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" - - -- describe export: - - before: | - function mkstack (name, spec) - return string.format ([[ - local export = require "std.base".export -- line 1 - local function caller () -- line 2 - export ({%s}, "%s", function () end) -- line 3 - end -- line 4 - caller () -- line 5 - ]], tostring (name), tostring (spec)) - end - - f, badarg = init (M, this_module, "export") - - mkmagic = function () return "MAGIC" end - local inner = mkmagic - wrapped = f "inner ()" - - - it returns the wrapped function: - expect (wrapped).not_to_be (inner) - expect (wrapped ()).to_be "MAGIC" - - it does not wrap the function when _ARGCHECK is disabled: | - script = [[ - _DEBUG = false - local debug = require "std.debug_init" - local export = require "std.base".export - local function inner () return "MAGIC" end - local wrapped = export "inner (any?)" - os.exit (wrapped == inner and 0 or 1) - ]] - expect (luaproc (script)).to_succeed () - - - context when checking zero argument function: - - before: - _, badarg = init (M, "", "inner") - - it diagnoses too many arguments: - expect (wrapped (false)).to_raise (badarg (1)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - - - context when checking single argument function: - - before: - _, badarg = init (M, "", "inner") - local inner = mkmagic - wrapped = f "inner (#table)" - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "non-empty table")) - - it diagnoses wrong argument types: - expect (wrapped {}).to_raise (badarg (1, "non-empty table", "empty table")) - - it diagnoses too many arguments: - expect (wrapped ({1}, 2, nop, "", false)).to_raise (badarg (1, 5)) - - it accepts correct argument types: - expect (wrapped ({1})).to_be "MAGIC" - - - context when checking multi-argument function: - - before: - _, badarg = init (M, "", "inner") - local inner = mkmagic - wrapped = f "inner (table, function)" - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "table")) - expect (wrapped ({})).to_raise (badarg (2, "function")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "table", "boolean")) - expect (wrapped ({}, false)).to_raise (badarg (2, "function", "boolean")) - - it diagnoses too many arguments: - expect (wrapped ({}, nop, false)).to_raise (badarg (3)) - - it accepts correct argument types: - expect (wrapped ({}, nop)).to_be "MAGIC" - - - context when checking optional argument function: - - before: - _, badarg = init (M, "", "inner") - local inner = mkmagic - wrapped = f "inner ([int])" - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, nop)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - expect (wrapped (1)).to_be "MAGIC" - - - context when checking optional multi-argument function: - - before: - _, badarg = init (M, "", "inner") - local inner = mkmagic - wrapped = f "inner ([int], string)" - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "int or string")) - expect (wrapped (1)).to_raise (badarg (2, "string")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int or string", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, "two", nop)).to_raise (badarg (3)) - - it accepts correct argument types: - expect (wrapped ("two")).to_be "MAGIC" - expect (wrapped (1, "two")).to_be "MAGIC" diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 0c58bd1..5b45fd4 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -3,7 +3,9 @@ before: | this_module = "std.debug" global_table = "_G" - extend_base = { "argcheck", "argerror", "argscheck", "say", "trace" } + extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", + "arglen", "argscheck", "export", "getcompat", "say", + "setcompat", "toomanyarg_fmt", "trace" } M = require "std.debug" @@ -29,6 +31,75 @@ specify std.debug: to_equal {} +- describe DEPRECATED: + - before: | + function runscript (body, name, args) + return luaproc ( + "require 'std.debug'.DEPRECATED ('0', '" .. (name or "runscript") .. "', function (...)" .. + " " .. body .. + " end) " .. + "('" .. table.concat (args or {}, "', '") .. "')" + ) + end + + f, badarg = init (M, this_module, "DEPRECATED") + + - it returns a function: + expect (type (f ("0", "deprecated", nop))).to_be "function" + expect (f ("0", "deprecated", nop)).not_to_be (nop) + - context with deprecated function: + - it executes the deprecated function: + expect (runscript 'error "oh noes!"').to_contain_error "oh noes!" + - it passes arguments to the deprecated function: + expect (runscript ("print (table.concat ({...}, ', '))", nil, + {"foo", "bar", "baz"})).to_output "foo, bar, baz\n" + - it returns deprecated function results: | + script = [[ + DEPRECATED = require "std.debug".DEPRECATED + fn = DEPRECATED ("0", "fn", function () return "foo", "bar", "baz" end) + print (fn ()) + ]] + expect (luaproc (script)).to_output "foo\tbar\tbaz\n" + - it writes a warning to stderr: + expect (runscript 'error "oh noes!"'). + to_match_error "deprecated.*, and will be removed" + - it writes the version string to stderr: + expect (runscript 'error "oh noes!"'). + to_contain_error "in release 0" + - it writes the call location to stderr: | + expect (runscript 'error "oh noes!"'). + to_match_error "^%S+:1: " + - context with _DEBUG: + - before: | + script = [[ + DEPRECATED = require "std.debug".DEPRECATED + fn = DEPRECATED ("0", "fn", function () io.stderr:write "oh noes!\n" end) + fn () -- line 3 + fn () -- line 4 + ]] + - it warns only on first call by default: + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" + - it warns only on first call with _DEBUG set to true: + script = "_DEBUG = true " .. script + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" + - it warns only on first call with _DEBUG.compat unset: + script = "_DEBUG = {} " .. script + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" + - it does not warn at all when set to false: | + script = "_DEBUG = false " .. script + expect (luaproc (script)).not_to_match_error "%d:.*deprecated" + - it does not warn at all when compat field is set to true: | + script = "_DEBUG = { compat = true } " .. script + expect (luaproc (script)).not_to_match_error "%d:.*deprecated" + - it warns on every call with compat field set to false: + script = "_DEBUG = { compat = false } " .. script + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" + + - describe argerror: - before: | function mkstack (level) @@ -546,6 +617,106 @@ specify std.debug: to_contain_error "debugging" +- describe export: + - before: | + function mkstack (name, spec) + return string.format ([[ + local export = require "std.debug".export -- line 1 + local function caller () -- line 2 + export ({%s}, "%s", function () end) -- line 3 + end -- line 4 + caller () -- line 5 + ]], tostring (name), tostring (spec)) + end + + f, badarg = init (M, this_module, "export") + + mkmagic = function () return "MAGIC" end + local inner = mkmagic + wrapped = f "inner ()" + + - it returns the wrapped function: + expect (wrapped).not_to_be (inner) + expect (wrapped ()).to_be "MAGIC" + - it does not wrap the function when _ARGCHECK is disabled: | + script = [[ + _DEBUG = false + local debug = require "std.debug_init" + local export = require "std.debug".export + local function inner () return "MAGIC" end + local wrapped = export "inner (any?)" + os.exit (wrapped == inner and 0 or 1) + ]] + expect (luaproc (script)).to_succeed () + + - context when checking zero argument function: + - before: + _, badarg = init (M, "", "inner") + - it diagnoses too many arguments: + expect (wrapped (false)).to_raise (badarg (1)) + - it accepts correct argument types: + expect (wrapped ()).to_be "MAGIC" + + - context when checking single argument function: + - before: + _, badarg = init (M, "", "inner") + local inner = mkmagic + wrapped = f "inner (#table)" + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "non-empty table")) + - it diagnoses wrong argument types: + expect (wrapped {}).to_raise (badarg (1, "non-empty table", "empty table")) + - it diagnoses too many arguments: + expect (wrapped ({1}, 2, nop, "", false)).to_raise (badarg (1, 5)) + - it accepts correct argument types: + expect (wrapped ({1})).to_be "MAGIC" + + - context when checking multi-argument function: + - before: + _, badarg = init (M, "", "inner") + local inner = mkmagic + wrapped = f "inner (table, function)" + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "table")) + expect (wrapped ({})).to_raise (badarg (2, "function")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "table", "boolean")) + expect (wrapped ({}, false)).to_raise (badarg (2, "function", "boolean")) + - it diagnoses too many arguments: + expect (wrapped ({}, nop, false)).to_raise (badarg (3)) + - it accepts correct argument types: + expect (wrapped ({}, nop)).to_be "MAGIC" + + - context when checking optional argument function: + - before: + _, badarg = init (M, "", "inner") + local inner = mkmagic + wrapped = f "inner ([int])" + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, nop)).to_raise (badarg (2)) + - it accepts correct argument types: + expect (wrapped ()).to_be "MAGIC" + expect (wrapped (1)).to_be "MAGIC" + + - context when checking optional multi-argument function: + - before: + _, badarg = init (M, "", "inner") + local inner = mkmagic + wrapped = f "inner ([int], string)" + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "int or string")) + expect (wrapped (1)).to_raise (badarg (2, "string")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "int or string", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, "two", nop)).to_raise (badarg (3)) + - it accepts correct argument types: + expect (wrapped ("two")).to_be "MAGIC" + expect (wrapped (1, "two")).to_be "MAGIC" + + - describe say: - before: | function mkwrap (k, v) diff --git a/specs/specs.mk b/specs/specs.mk index 4c64227..70061a3 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -23,7 +23,6 @@ SPECL_OPTS = --unicode ## affected. specl_SPECS = \ - $(srcdir)/specs/base_spec.yaml \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ $(srcdir)/specs/functional_spec.yaml \ From 89d3d4cf9c5b9f31d00d7f4101639361246fa341 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 25 Aug 2014 22:26:13 +0100 Subject: [PATCH 395/703] refactor: factor getcompat and setcompat out of debug api. * specs/debug_spec.yaml (getcompat, setcompat): Remove. * lib/std/debug.lua (DEPRECATIONMSG): Use getcompat and setcompat internally, returning an empty string if necessary. (DEPRECATED): Don't use getcompat or setcompat now that DEPRECATIONMSG does that. (M): Remove getcompat and setcompat. * lib/std/functional.lua (bind): Simplify. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 26 +++++++++++---------- lib/std/functional.lua | 9 +++----- specs/debug_spec.yaml | 51 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 23 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index b4b6111..1b5b61c 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -775,25 +775,32 @@ end --- Format a deprecation warning message. +-- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; +-- if `_DEBUG.compat` is false, warn every time *fn* is called; +-- otherwise don't write any warnings, and run *fn* normally. -- @string version first deprecation release version -- @string name function name for automatic warning message -- @string[opt] extramsg additional warning text -- @int level call stack level to blame for the error --- @treturn string deprecation warning message +-- @treturn string deprecation warning message, or empty string +-- @usage +-- io.stderr:write ("42", "multi-argument 'module.fname", 2) local function DEPRECATIONMSG (version, name, extramsg, level) if level == nil then level, extramsg = extramsg, nil end extramsg = extramsg or "and will be removed entirely in a future release" local _, where = pcall (function () error ("", level + 3) end) - return (where .. string.format ("%s was deprecated in release %s, %s.\n", - name, version, extramsg)) + if not getcompat (name) then + setcompat (name) + return (where .. string.format ("%s was deprecated in release %s, %s.\n", + name, tostring (version), extramsg)) + end + + return "" end --- Write a deprecation warning to stderr. --- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; --- if `_DEBUG.compat` is false, warn every time *fn* is called; --- otherwise don't write any warnings, and run *fn* normally. -- @string version first deprecation release version -- @string name function name for automatic warning message -- @string[opt] extramsg additional warning text @@ -804,10 +811,7 @@ local function DEPRECATED (version, name, extramsg, fn) if fn == nil then fn, extramsg = extramsg, nil end return function (...) - if not getcompat (name) then - io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) - setcompat (name) - end + io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) return fn (...) end end @@ -823,9 +827,7 @@ M = { arglen = arglen, argscheck = argscheck, export = export, - getcompat = getcompat, say = say, - setcompat = setcompat, toomanyarg_fmt = toomanyarg_fmt, trace = trace, } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index f1f246f..3ecd6bb 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -29,12 +29,9 @@ local function bind (fn, ...) if type (argt[1]) == "table" and argt[2] == nil then argt = argt[1] else - if not debug.getcompat (bind) then - io.stderr:write (debug.DEPRECATIONMSG ("39", - "multi-argument 'std.functional.bind'", - "use a table of arguments as the second parameter instead", 2)) - debug.setcompat (bind) - end + io.stderr:write (debug.DEPRECATIONMSG ("39", + "multi-argument 'std.functional.bind'", + "use a table of arguments as the second parameter instead", 2)) end return function (...) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 5b45fd4..fe13c82 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,10 +4,10 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "arglen", "argscheck", "export", "getcompat", "say", - "setcompat", "toomanyarg_fmt", "trace" } + "arglen", "argscheck", "export", "say", "toomanyarg_fmt", + "trace" } - M = require "std.debug" + M = require (this_module) specify std.debug: - context when required: @@ -35,7 +35,8 @@ specify std.debug: - before: | function runscript (body, name, args) return luaproc ( - "require 'std.debug'.DEPRECATED ('0', '" .. (name or "runscript") .. "', function (...)" .. + "require '" .. this_module .. "'.DEPRECATED ('0', '" .. + (name or "runscript") .. "', function (...)" .. " " .. body .. " end) " .. "('" .. table.concat (args or {}, "', '") .. "')" @@ -72,7 +73,7 @@ specify std.debug: - context with _DEBUG: - before: | script = [[ - DEPRECATED = require "std.debug".DEPRECATED + DEPRECATED = require "]] .. this_module .. [[".DEPRECATED fn = DEPRECATED ("0", "fn", function () io.stderr:write "oh noes!\n" end) fn () -- line 3 fn () -- line 4 @@ -100,6 +101,32 @@ specify std.debug: expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" +- describe DEPRECATIONMSG: + - before: | + function mkscript (lvl) + return [[ + DEPRECATIONMSG = require "]] .. this_module .. [[".DEPRECATIONMSG + function fn () + io.stderr:write (DEPRECATIONMSG ("42", "spec file", ]] .. lvl .. [[)) + end + fn () -- line 5 + fn () -- line 6 + ]] + end + + f = M.DEPRECATIONMSG + + - it contains deprecating the release version: + expect (f ("41", "foo", 2)).to_contain "41" + - it contains the deprecation function name: + expect (f ("41", "some.module.fname", 2)).to_contain "some.module.fname" + - it appends an optional extra message: + expect (f ("41", "wuh", "ah boo", 2)).to_contain ", ah boo." + - it blames the given stack level: + expect (luaproc (mkscript (1))).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (mkscript (2))).to_match_error "^%S+:5:.*deprecated" + + - describe argerror: - before: | function mkstack (level) @@ -148,6 +175,20 @@ specify std.debug: expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" +- describe arglen: + - before: + f = M.arglen + + - it returns the length of a table: + expect (f {1, 2, 5}).to_be (3) + - it works with an empty table: + expect (f {}).to_be (0) + - it ignores the hash part of a table: + expect (f {1, x=2, y=3, 4, 5}).to_be (3) + - it counts nil values in the array part of a table: + expect (f {1, nil, 2, 5}).to_be (4) + + - describe argcheck: - before: | Object = require 'std.object' From bd8106b52e125711d33d0fc120e2cb12cace8ca4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 25 Aug 2014 22:33:50 +0100 Subject: [PATCH 396/703] refactor: use toomanyargmsg function instead of toomanyarg_fmt string. * lib/std/debug.lua (toomanyarg_fmt): Remove. (toomanyargmsg): A replacement function that returns the formatted string. Adjust all callers. * specs/debug_spec.yaml (extend_base): Adjust accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 4 ++-- lib/std/debug.lua | 16 ++++++++++++---- specs/debug_spec.yaml | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 4132a51..82d2803 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -238,7 +238,7 @@ local M = { if _ARGCHECK then - local arglen, toomanyarg_fmt = debug.arglen, debug.toomanyarg_fmt + local arglen, toomanyargmsg = debug.arglen, debug.toomanyargmsg M.__call = function (self, x, ...) local mt = getmetatable (self) @@ -251,7 +251,7 @@ if _ARGCHECK then -- it just refers back to the object being called: `Container {"x"}. argcheck (name, 1, "table", x) if next (argt) then - error (string.format (toomanyarg_fmt, name, 1, 1 + arglen (argt)), 2) + error (toomanyargmsg (name, 1, 1 + arglen (argt)), 2) end end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 1b5b61c..424a359 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -225,8 +225,16 @@ local function arglen (t) end -local toomanyarg_fmt = - "too many arguments to '%s' (no more than %d expected, got %d)" +--- Format a standard "too many arguments" error message. +-- @string name function name +-- @number expect maximum number of arguments accepted +-- @number actual number of arguments received +-- @treturn string standard "too many arguments" error message +local function toomanyargmsg (name, expect, actual) + local fmt = "too many arguments to '%s' (no more than %d expected, got %d)" + return string.format (fmt, name, expect, actual) +end + local argcheck, argscheck, _export -- forward declarations @@ -650,7 +658,7 @@ if _ARGCHECK then end if argc > max then - error (string.format (toomanyarg_fmt, fqfname or name, max, argc), 2) + error (toomanyargmsg (fqfname or name, max, argc), 2) end -- Propagate outer environment to inner function. @@ -828,7 +836,7 @@ M = { argscheck = argscheck, export = export, say = say, - toomanyarg_fmt = toomanyarg_fmt, + toomanyargmsg = toomanyargmsg, trace = trace, } diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index fe13c82..39e4d04 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,7 +4,7 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "arglen", "argscheck", "export", "say", "toomanyarg_fmt", + "arglen", "argscheck", "export", "say", "toomanyargmsg", "trace" } M = require (this_module) From 9793bec19ad457e10c8b96ce7ab1e652f7fb1ab9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 15:14:33 +0100 Subject: [PATCH 397/703] refactor: simplify functional.memoize. * lib/std/functional.lua (memoize): Remove spurious require and associated comment. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 3ecd6bb..bd4735e 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -299,9 +299,7 @@ end -- local fast = memoize (function (...) --[[ slow code ]] end) local function memoize (fn, normalize) if normalize == nil then - -- Call require here, to avoid pulling in all of 'std.string' - -- even when memoize is never called. - normalize = function (...) return require "std.base".tostring {...} end + normalize = function (...) return base.tostring {...} end end return setmetatable ({}, { From a65a22f275ab9bfe78e0d566667a64af77847030 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 16:12:28 +0100 Subject: [PATCH 398/703] list: remove workaround for old module metadata layout. * lib/std/list.lua (transpose): Remove workaround for old module data layout. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 844b8b6..8180f32 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -319,9 +319,7 @@ local function transpose (ls) for i = 1, math.max (unpack (dims)) do rs[i] = List {} for j = 1, len do - -- FIXME: the if wrapper is only needed to stop the i index - -- falling through to the metatable[2] index :( - if i <= #ls[j] then rs[i][j] = ls[j][i] end + rs[i][j] = ls[j][i] end end end From 096ec36331a1ceb55cb70898f714632502229733 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 17:18:27 +0100 Subject: [PATCH 399/703] refactor: remove obsolete __ipairs specs. stdlib no longer supports __ipairs metamethods. * specs/vector_spec.yaml (__ipairs): Remove. Signed-off-by: Gary V. Vaughan --- specs/vector_spec.yaml | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/specs/vector_spec.yaml b/specs/vector_spec.yaml index a759ee9..89cc45a 100644 --- a/specs/vector_spec.yaml +++ b/specs/vector_spec.yaml @@ -195,40 +195,6 @@ specify Vector: expect (a.allocated).to_be (0) -- describe __ipairs: - - before: - -- Some luajit builds, and PUC RIO 5.1 don't respect __ipairs - local t = setmetatable ({}, { - __ipairs = function (self) - local i = 0 - return function () - i = i + 1 - if i == 1 then return 1, 42 end - end - end, - }) - meta__ipairs = false - for _ in ipairs (t) do meta__ipairs = true end - - context with alien buffer elements: - - it iterates over elements: - elements = {1, 4, 9, 16, 25} - vector = Vector ("int", elements) - if meta__ipairs then - local t = {} - for i, v in ipairs (vector) do t[i] = v end - expect (t).to_equal (elements) - end - - context with table elements: - - it iterates over elements: - elements = {"a", "b", {{"c"}, "d"}, "e"} - vector = Vector ("any", elements) - if meta__ipairs then - local t = {} - for i, v in ipairs (vector) do t[i] = v end - expect (t).to_equal (elements) - end - - - describe __len: - before: | -- Some luajit releases, and PUC RIO 5.1 don't respect __len From e2d4b22804d870d1d3027d12f1ef47a374bfd9ea Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 17:26:05 +0100 Subject: [PATCH 400/703] refactor: simplify export implementation and api. * specs/debug_spec.yaml (export): specify behaviours with explicit arguments instead of introspection. * lib/std/debug.lua (export): expect explicit declaration string with argument types, and inner function. (whatpath, getinfo): Remove unused introspection functions. * lib/std.lua.in, lib/std/base/string.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua: Move LDocs and export declarations to module table constructors. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 331 ++++++++++++------------- lib/std/base/string.lua | 23 ++ lib/std/container.lua | 8 +- lib/std/debug.lua | 106 ++------ lib/std/functional.lua | 526 +++++++++++++++++++++------------------- lib/std/io.lua | 278 ++++++++++----------- lib/std/list.lua | 124 +++++----- lib/std/math.lua | 61 +++-- lib/std/package.lua | 174 +++++++------ lib/std/string.lua | 497 +++++++++++++++++-------------------- lib/std/table.lua | 368 ++++++++++++++-------------- specs/debug_spec.yaml | 21 +- 12 files changed, 1201 insertions(+), 1316 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index d97aab3..d6a40d1 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -27,158 +27,6 @@ local base = require "std.base" local M - - ---- 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 (expected ~= nil, "100% unexpected!") --- std.assert (expected ~= nil, "%s unexpected!", expected) -local assert = base.assert - - ---- An iterator over all elements of a sequence. --- 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 --- for value in std.elems {a = 1, b = 2, c = 5} do process (value) end -local elems = base.elems - - ---- Evaluate a string as Lua code. --- @function eval --- @string s string of Lua code --- @return result of evaluating `s` --- @usage std.eval "math.pow (2, 10)" -local eval = base.eval - - ---- An iterator over the integer keyed elements of a sequence. --- If *t* has a `__len` metamethod, iterate up to the index it returns. --- @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 --- for v in std.ielems {"a", "b", "c"} do process (v) end -local ielems = base.ielems - - ---- An iterator over elements `1..#t`, respecting `__len` metamethod. --- --- Unlike Lua 5.1, which ignores `__len`, and Lua 5.2, which looks for --- and uses `__ipairs`, this iterator always iterates over elements with --- integer keys starting at 1, up to and including `#t`. --- @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 pairs --- @usage --- -- don't stop at first nil --- args = {"first", "second", nil, "last"} --- setmetatable (args, { __len = std.functional.lambda "=4" }) --- for i, v in std.ipairs (args) do process (i, v) end -local ipairs = base.ipairs - - ---- Return a new table with element order reversed. --- Apart from the order of the elments returned, this function follows --- the same rules as @{ipairs} for determining first and last elements. --- @function ireverse --- @tparam table t a table --- @treturn table a new table with integer keyed elements in reverse --- order with respect to *t* --- @see ielems --- @see ipairs --- @usage --- local rielems = std.functional.compose (std.ireverse, std.ielems) --- for e in rielems (l) do process (e) end -local ireverse = base.ireverse - - ---- Return named metamethod, if any, otherwis `nil`. --- @function getmetamethod --- @tparam table t table to get metamethod of --- @string n name of metamethod to get --- @treturn function|nil metamethod function, or `nil` if no metamethod --- @usage lookup = std.getmetamethod (require "std.object", "__index") -local getmetamethod = base.getmetamethod - - ---- 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 --- for k, v in pairs {"a", b = "c", foo = 42} do process (k, v) end -local pairs = base.pairs - - ---- 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") -local require = base.require - - ---- An iterator like ipairs, but in reverse. --- Apart from the order of the elments 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` --- @usage for i, v = ripairs (t) do ... end -local ripairs = base.ripairs - - ---- Enhance core `tostring` to render table contents as a string. --- @function tostring --- @param x object to convert to string --- @treturn string compact string rendering of *x* --- @usage --- -- {1=baz,foo=bar} --- print (std.tostring {foo="bar","baz"}) -local tostring = base.tostring - - ---- Overwrite core methods and metamethods with `std` enhanced versions. --- --- Write all functions from this module, except `std.barrel` and --- `std.monkey_patch`, into the given namespace. --- @function monkey_patch --- @tparam[opt=_G] table namespace where to install global functions --- @treturn table the module table --- @usage local std = require "std".monkey_patch () local function monkey_patch (namespace) namespace = namespace or _G @@ -192,15 +40,6 @@ local function monkey_patch (namespace) end ---- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). --- --- Apply **all** `monkey_patch` functions. Additionally, for backwards --- compatibility only, write a selection of sub-module functions into --- the given namespace. --- @function barrel --- @tparam[opt=_G] table namespace where to install global functions --- @treturn table module table --- @usage local std = require "std".barrel () local function barrel (namespace) namespace = namespace or _G @@ -244,24 +83,164 @@ end -- @table std -- @field version release version string -local export = require "std.debug".export +local function X (decl, fn) + return require "std.debug".export ("std." .. decl, fn) +end M = { - assert = export "assert (any?, string?, any?*)", - barrel = export "barrel (table?)", - elems = export "elems (table)", - eval = export "eval (string)", - ielems = export "ielems (table)", - ipairs = export "ipairs (table)", - ireverse = export "ireverse (table)", - getmetamethod = export "getmetamethod (object|table, string)", - monkey_patch = export "monkey_patch (table?)", - pairs = export "pairs (table)", - require = export "require (string, string?, string?, string?)", - ripairs = export "ripairs (table)", - tostring = export "tostring (any?)", - version = "General Lua libraries / @VERSION@", + + --- 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 (expected ~= nil, "100% unexpected!") + -- std.assert (expected ~= nil, "%s unexpected!", expected) + assert = X ("assert (any?, string?, any?*)", base.assert), + + --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). + -- + -- Apply **all** `monkey_patch` functions. Additionally, for backwards + -- compatibility only, write a selection of sub-module functions into + -- the given namespace. + -- @function barrel + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table module table + -- @usage local std = require "std".barrel () + barrel = X ("barrel (table?)", barrel), + + --- An iterator over all elements of a sequence. + -- 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 + -- for value in std.elems {a = 1, b = 2, c = 5} do process (value) end + elems = X ("elems (table)", base.elems), + + --- Evaluate a string as Lua code. + -- @function eval + -- @string s string of Lua code + -- @return result of evaluating `s` + -- @usage std.eval "math.pow (2, 10)" + eval = X ("eval (string)", base.eval), + + --- An iterator over the integer keyed elements of a sequence. + -- If *t* has a `__len` metamethod, iterate up to the index it returns. + -- @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 + -- for v in std.ielems {"a", "b", "c"} do process (v) end + ielems = X ("ielems (table)", base.ielems), + + --- An iterator over elements `1..#t`, respecting `__len` metamethod. + -- + -- Unlike Lua 5.1, which ignores `__len`, and Lua 5.2, which looks for + -- and uses `__ipairs`, this iterator always iterates over elements with + -- integer keys starting at 1, up to and including `#t`. + -- @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 pairs + -- @usage + -- -- don't stop at first nil + -- args = {"first", "second", nil, "last"} + -- setmetatable (args, { __len = std.functional.lambda "=4" }) + -- for i, v in std.ipairs (args) do process (i, v) end + ipairs = X ("ipairs (table)", base.ipairs), + + --- Return a new table with element order reversed. + -- Apart from the order of the elments returned, this function follows + -- the same rules as @{ipairs} for determining first and last elements. + -- @function ireverse + -- @tparam table t a table + -- @treturn table a new table with integer keyed elements in reverse + -- order with respect to *t* + -- @see ielems + -- @see ipairs + -- @usage + -- local rielems = std.functional.compose (std.ireverse, std.ielems) + -- for e in rielems (l) do process (e) end + ireverse = X ("ireverse (table)", base.ireverse), + + --- Return named metamethod, if any, otherwis `nil`. + -- @function getmetamethod + -- @tparam table t table to get metamethod of + -- @string n name of metamethod to get + -- @treturn function|nil metamethod function, or `nil` if no metamethod + -- @usage lookup = std.getmetamethod (require "std.object", "__index") + getmetamethod = X ("getmetamethod (object|table, string)", base.getmetamethod), + + --- Overwrite core methods and metamethods with `std` enhanced versions. + -- + -- Write all functions from this module, except `std.barrel` and + -- `std.monkey_patch`, into the given namespace. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage local std = require "std".monkey_patch () + monkey_patch = X ("monkey_patch (table?)", monkey_patch), + + --- 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 + -- for k, v in pairs {"a", b = "c", foo = 42} do process (k, v) end + pairs = X ("pairs (table)", base.pairs), + + --- 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?)", base.require), + + --- An iterator like ipairs, but in reverse. + -- Apart from the order of the elments 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` + -- @usage for i, v = ripairs (t) do ... end + ripairs = X ("ripairs (table)", base.ripairs), + + --- Enhance core `tostring` to render table contents as a string. + -- @function tostring + -- @param x object to convert to string + -- @treturn string compact string rendering of *x* + -- @usage + -- -- {1=baz,foo=bar} + -- print (std.tostring {foo="bar","baz"}) + tostring = X ("tostring (any?)", base.tostring), } diff --git a/lib/std/base/string.lua b/lib/std/base/string.lua index f758688..880a4cd 100644 --- a/lib/std/base/string.lua +++ b/lib/std/base/string.lua @@ -20,6 +20,29 @@ local function copy (t) end +-- Write pretty-printing based on: +-- +-- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators +-- +-- Based on "The Design of a Pretty-printing Library in Advanced +-- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925 +-- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps +-- Heavily modified by Simon Peyton Jones, Dec 96 +-- +-- Haskell types: +-- data Doc list of lines +-- quote :: Char -> Char -> Doc -> Doc Wrap document in ... +-- (<>) :: Doc -> Doc -> Doc Beside +-- (<+>) :: Doc -> Doc -> Doc Beside, separated by space +-- ($$) :: Doc -> Doc -> Doc Above; if there is no overlap it "dovetails" the two +-- nest :: Int -> Doc -> Doc Nested +-- punctuate :: Doc -> [Doc] -> [Doc] punctuate p [d1, ... dn] = [d1 <> p, d2 <> p, ... dn-1 <> p, dn] +-- render :: Int Line length +-- -> Float Ribbons per line +-- -> (TextDetails -> a -> a) What to do with text +-- -> a What to do at the end +-- -> Doc The document +-- -> a Result local function render (x, open, close, elem, pair, sep, roots) local function stop_roots (x) diff --git a/lib/std/container.lua b/lib/std/container.lua index 82d2803..31e49c1 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -72,7 +72,7 @@ local debug = require "std.debug" local ipairs, pairs = base.ipairs, base.pairs local prototype = base.prototype -local argcheck, export = debug.argcheck, debug.export +local argcheck = debug.argcheck @@ -231,8 +231,12 @@ local function __call (self, x, ...) end +local function X (decl, fn) + return debug.export ("std.container." .. decl, fn) +end + local M = { - mapfields = export "mapfields (table, table|object, table?)", + mapfields = X ("mapfields (table, table|object, table?)", mapfields), } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 424a359..b6b5e8a 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -236,7 +236,7 @@ local function toomanyargmsg (name, expect, actual) end -local argcheck, argscheck, _export -- forward declarations +local argcheck, argscheck, export -- forward declarations if _ARGCHECK then @@ -596,7 +596,26 @@ if _ARGCHECK then end - function _export (inner, name, fqfname, types) + --- Export a function definition, optionally with argument type checking. + -- In addition to checking that each argument type matches the corresponding + -- element in the *types* table with `argcheck`, if the final element of + -- *types* ends with an asterisk, remaining unchecked arguments are checked + -- against that type. + -- @string decl function type declaration string + -- @func inner function to wrap with argument checking + -- @usage + -- M.square = export ("util.square (number)", function (n) return n * n end) + function export (decl, inner) + -- Parse "fname (argtype, argtype, argtype...)". + local fname, types = decl:match "([%w_][%.%d%w_]*)%s+%((.*)%)" + if types == "" then + types = {} + elseif types then + types = split (types, ",%s+") + else + fname = decl:match "([%w_][%d%d%w_]*)" + end + -- If the final element of types ends with "*", then set max to a -- sentinel value to denote type-checking of *all* remaining -- unchecked arguments against that type-spec is required. @@ -647,18 +666,18 @@ if _ARGCHECK then if contents and type (args[i]) == "table" then for k, v in pairs (args[i]) do if not checktype (contents, v) then - argerror (fqfname or name, i, formaterror (expected, v, k), 2) + argerror (fname, i, formaterror (expected, v, k), 2) end end end end -- Otherwise the argument type itself was mismatched. - argerror (fqfname or name, i, formaterror (expected, args[i]), 2) + argerror (fname, i, formaterror (expected, args[i]), 2) end if argc > max then - error (toomanyargmsg (fqfname or name, max, argc), 2) + error (toomanyargmsg (fname, max, argc), 2) end -- Propagate outer environment to inner function. @@ -676,83 +695,8 @@ else argcheck = base.nop argscheck = base.nop - _export = function (inner) return inner end - -end - - -local dirsep, pathsep, path_mark = package.config:match "^(%S+)\n(%S+)\n(%S+)\n" -local pathpatt, markpatt = "[^" .. pathsep .. "]+", path_mark:gsub ("%p", "%%%0") - -local function whatpath (name, src) - local r - package.path:gsub (pathpatt, function (s) - local substituted = s:gsub (markpatt, (name:gsub ("%.", dirsep))) - if substituted == src then r = name end - end) - return r -end - - -local function getinfo (what, level) - local fqfname, s, fn - - for i = 1, math.huge do - s, fn = debug.getlocal (level + 1, i) - - if s == nil then - break - - elseif s == what or fn == what then - local t, src = {}, debug.getinfo (callable (fn), "S").source:gsub ("^@(.*)$", "%1") - src:gsub ("/([^/]+)", function (m) t[#t + 1] = m:gsub ("%.lua", "") end) - - local tryme - for i = #t, 1, -1 do - tryme = tryme and (t[i] .. "." .. tryme) or t[i] - if whatpath (tryme, src) then - fqfname = (tryme .. "." .. s):gsub ("^(std%.)base%.", "%1") - break - end - end - break - - end - end - - return fqfname, fn -end - - ---- Export a function definition, optionally with argument type checking. --- In addition to checking that each argument type matches the corresponding --- element in the *types* table with `argcheck`, if the final element of --- *types* ends with an asterisk, remaining unchecked arguments are checked --- against that type. --- @string[opt] mname module name (default: looked up with *decl*) --- @string decl function type declaration string --- @usage --- M.round = export "round (number, int?)" -local function export (mname, decl) - if decl == nil then mname, decl = nil, mname end - - -- Parse "fname (argtype, argtype, argtype...)". - local name, types = decl:match "([%w_][%d%w_]*)%s+%((.*)%)" - if types == "" then - types = {} - elseif types then - types = split (types, ",%s+") - else - name = decl:match "([%w_][%d%w_]*)" - end - local fqfname, inner = getinfo (name, 2) - - -- Trust the user *mname* argument, if given. - if mname then - fqfname = mname .. "." .. name - end + export = function (decl, inner) return inner end - return _export (inner, name, fqfname, types) end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index bd4735e..e8b0fbc 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -14,16 +14,10 @@ local operator = require "std.operator" local ipairs, ireverse, len, pairs = base.ipairs, base.ireverse, base.len, base.pairs +local callable, reduce = + base.functional.callable, base.functional.reduce - ---- Partially apply a function. --- @function bind --- @func fn function to apply partially --- @tparam table argt table of *fn* arguments to bind --- @return function with *argt* arguments already bound --- @usage --- cube = bind (lambda "^", {[2] = 3}) local function bind (fn, ...) local argt = {...} if type (argt[1]) == "table" and argt[2] == nil then @@ -49,33 +43,6 @@ local function bind (fn, ...) end ---- Identify callable types. --- @function callable --- @param x an object or primitive --- @return `true` if *x* can be called, otherwise `false` --- @usage --- if callable (functable) then functable (args) end -local callable = base.functional.callable - - ---- A rudimentary case statement. --- Match *with* against keys in *branches* table. --- @function case --- @param with expression to match --- @tparam table branches map possible matches to functions --- @return the value associated with a matching key, or the first non-key --- value if no key matches. Function or functable valued matches are --- called using *with* as the sole argument, and the result of that call --- returned; otherwise the matching value associated with the matching --- key is returned directly; or else `nil` if there is no match and no --- default. --- @see cond --- @usage --- return case (type (object), { --- table = "table", --- string = function () return "string" end, --- function (s) error ("unhandled type: " .. s) end, --- }) local function case (with, branches) local match = branches[with] or branches[1] if callable (match) then @@ -85,32 +52,6 @@ local function case (with, branches) end ---- Collect the results of an iterator. --- @function collect --- @func[opt=std.ipairs] ifn iterator function --- @param ... *ifn* arguments --- @treturn table of results from running *ifn* on *args* --- @see filter --- @see map --- @usage --- --> {"a", "b", "c"} --- collect {"a", "b", "c", x=1, y=2, z=5} -local collect = base.functional.collect - - ---- Compose functions. --- @function compose --- @func ... functions to compose --- @treturn function composition of fnN .. fn1: note that this is the --- reverse of what you might expect, but means that code like: --- --- functional.compose (function (x) return f (x) end, --- function (x) return g (x) end)) --- --- can be read from top to bottom. --- @usage --- vpairs = compose (table.invert, ipairs) --- for v, i in vpairs {"a", "b", "c"} do process (v, i) end local function compose (...) local arg = {...} local fns, n = arg, #arg @@ -128,26 +69,6 @@ local function compose (...) end ---- A rudimentary condition-case statement. --- If *expr* is "truthy" return *branch* if given, otherwise *expr* --- itself. If the return value is a function or functable, then call it --- with *expr* as the sole argument and return the result; otherwise --- return it explicitly. If *expr* is "falsey", then recurse with the --- first two arguments stripped. --- @function cond --- @param expr a Lua expression --- @param branch a function, functable or value to use if *expr* is --- "truthy" --- @param ... additional arguments to retry if *expr* is "falsey" --- @see case --- @usage --- -- recursively calculate the nth triangular number --- function triangle (n) --- return cond ( --- n <= 0, 0, --- n == 1, 1, --- function () return n + triangle (n - 1) end) --- end local function cond (expr, branch, ...) if branch == nil and select ("#", ...) == 0 then expr, branch = true, expr @@ -162,14 +83,6 @@ local function cond (expr, branch, ...) end ---- Curry a function. --- @function curry --- @func fn function to curry --- @int n number of arguments --- @treturn function curried version of *fn* --- @usage --- add = curry (function (x, y) return x + y end, 2) --- incr, decr = add (1), add (-1) local function curry (fn, n) if n <= 1 then return fn @@ -181,17 +94,6 @@ local function curry (fn, n) end ---- Filter an iterator with a predicate. --- @function filter --- @tparam predicate pfn predicate function --- @func[opt=std.pairs] ifn iterator function --- @param ... iterator arguments --- @treturn table elements e for which `pfn (e)` is not "falsey". --- @see collect --- @see map --- @usage --- --> {2, 4} --- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) local function filter (pfn, ifn, ...) local argt = {...} if not callable (ifn) then @@ -217,33 +119,6 @@ local function filter (pfn, ifn, ...) end ---- Fold a binary function into an iterator. --- @function reduce --- @func fn reduce function --- @param d initial first argument --- @func ifn iterator function --- @param ... iterator arguments --- @return result --- @see foldl --- @see foldr --- @usage --- --> 2 ^ 3 ^ 4 ==> 4096 --- reduce (lambda '^', 2, std.ipairs, {3, 4}) -local reduce = base.functional.reduce - - ---- Fold a binary function left associatively. --- If parameter *d* is omitted, the first element of *t* is used, --- and *t* treated as if it had been passed without that element. --- @function foldl --- @func fn binary function --- @param[opt=t[1]] d initial left-most argument --- @tparam table t a table --- @return result --- @see foldr --- @see reduce --- @usage --- foldl (lambda "/", {10000, 100, 10}) == (10000 / 100) / 10 local function foldl (fn, d, t) if t == nil then local tail = {} @@ -254,18 +129,6 @@ local function foldl (fn, d, t) end ---- Fold a binary function right associatively. --- If parameter *d* is omitted, the last element of *t* is used, --- and *t* treated as if it had been passed without that element. --- @function foldr --- @func fn binary function --- @param[opt=t[1]] d initial right-most argument --- @tparam table t a table --- @return result --- @see foldl --- @see reduce --- @usage --- foldr (lambda "/", {10000, 100, 10}) == 10000 / (100 / 10) local function foldr (fn, d, t) if t == nil then local u, last = {}, len (d) @@ -276,27 +139,11 @@ local function foldr (fn, d, t) end ---- Identity function. --- @function id --- @param ... arguments --- @return *arguments* local function id (...) return ... end ---- Memoize a function, by wrapping it in a functable. --- --- To ensure that memoize always returns the same results for the same --- arguments, it passes arguments to *fn*. You can specify a more --- sophisticated function if memoize should handle complicated argument --- equivalencies. --- @function memoize --- @func fn pure function: a function with no side effects --- @tparam[opt=std.tostring] normalize normfn function to normalize arguments --- @treturn functable memoized function --- @usage --- local fast = memoize (function (...) --[[ slow code ]] end) local function memoize (fn, normalize) if normalize == nil then normalize = function (...) return base.tostring {...} end @@ -316,28 +163,6 @@ local function memoize (fn, normalize) end ---- Compile a lambda string into a Lua function. --- --- A valid lambda string takes one of the following forms: --- --- 1. `'operator'`: where *op* is a key in @{std.operator}, equivalent to that operation --- 1. `'=expression'`: equivalent to `function (...) return (expression) end` --- 1. `'|args|expression'`: equivalent to `function (args) return (expression) end` --- --- The second form (starting with `=`) automatically assigns the first --- nine arguments to parameters `_1` through `_9` for use within the --- expression body. --- --- The results are memoized, so recompiling an previously compiled --- lambda string is extremely fast. --- @function lambda --- @string s a lambda string --- @treturn table compiled lambda string, can be called like a function --- @usage --- -- The following are all equivalent: --- lambda '<' --- lambda '= _1 < _2' --- lambda '|a,b| a {1, 4, 9, 16} --- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) local function map (mapfn, ifn, ...) local argt = {...} if not callable (ifn) or not next (argt) then @@ -414,17 +227,6 @@ local function map (mapfn, ifn, ...) end ---- Map a function over a table of argument lists. --- @function map_with --- @func fn map function --- @tparam table tt a table of *fn* argument lists --- @treturn table new table of *fn* results --- @see map --- @see zip_with --- @usage --- --> {"123", "45"}, {a="123", b="45"} --- conc = bind (map_with, {lambda '|...|table.concat {...}'}) --- conc {{1, 2, 3}, {4, 5}}, conc {a={1, 2, 3, x="y"}, b={4, 5, z=6}} local function map_with (mapfn, tt) local r = {} for k, v in pairs (tt) do @@ -434,26 +236,6 @@ local function map_with (mapfn, tt) end ---- No operation. --- This function ignores all arguments, and returns no values. --- @function nop --- @usage --- if unsupported then vtable["memrmem"] = nop end -local nop = base.nop - - ---- Zip a table of tables. --- Make a new table, with lists of elements at the same index in the --- original table. This function is effectively its own inverse. --- @function zip --- @tparam table tt a table of tables --- @treturn table new table with lists of elements of the same key --- from *tt* --- @see map --- @see zip_with --- @usage --- --> {{1, 3, 5}, {2, 4}}, {a={x=1, y=3, z=5}, b={x=2, y=4}} --- zip {{1, 2}, {3, 4}, {5}}, zip {x={a=1, b=2}, y={a=3, b=4}, z={a=5}} local function zip (tt) local r = {} for outerk, inner in pairs (tt) do @@ -466,54 +248,288 @@ local function zip (tt) end ---- Zip a list of tables together with a function. --- @function zip_with --- @tparam function fn function --- @tparam table tt table of tables --- @treturn table a new table of results from calls to *fn* with arguments --- made from all elements the same key in the original tables; effectively --- the "columns" in a simple list --- of lists. --- @see map_with --- @see zip --- @usage --- --> {"135", "24"}, {a="1", b="25"} --- conc = bind (zip_with, {lambda '|...|table.concat {...}'}) --- conc {{1, 2}, {3, 4}, {5}}, conc {{a=1, b=2}, x={a=3, b=4}, {b=5}} local function zip_with (fn, tt) return map_with (fn, zip (tt)) end -local export = debug.export ---- @export +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X (decl, fn) + return debug.export ("std.functional." .. decl, fn) +end + local M = { - bind = export "bind (func, any?*)", - callable = callable, - case = export "case (any?, #table)", - collect = export "collect ([func], any*)", - compose = export "compose (func*)", - cond = cond, - curry = export "curry (func, int)", - filter = export "filter (func, [func], any*)", - foldl = export "foldl (function, [any], table)", - foldr = export "foldr (function, [any], table)", - id = id, - lambda = export "lambda (string)", - map = export "map (func, [func], any*)", - map_with = export "map_with (function, table of tables)", - memoize = export "memoize (func, func?)", - nop = nop, - reduce = export "reduce (func, any, func, any*)", - zip = export "zip (table of tables)", - zip_with = export "zip_with (function, table of tables)", + --- Partially apply a function. + -- @function bind + -- @func fn function to apply partially + -- @tparam table argt table of *fn* arguments to bind + -- @return function with *argt* arguments already bound + -- @usage + -- cube = bind (lambda "^", {[2] = 3}) + bind = X ("bind (func, any?*)", bind), + + --- Identify callable types. + -- @function callable + -- @param x an object or primitive + -- @return `true` if *x* can be called, otherwise `false` + -- @usage + -- if callable (functable) then functable (args) end + callable = X ("callable (any)", callable), + + --- A rudimentary case statement. + -- Match *with* against keys in *branches* table. + -- @function case + -- @param with expression to match + -- @tparam table branches map possible matches to functions + -- @return the value associated with a matching key, or the first non-key + -- value if no key matches. Function or functable valued matches are + -- called using *with* as the sole argument, and the result of that call + -- returned; otherwise the matching value associated with the matching + -- key is returned directly; or else `nil` if there is no match and no + -- default. + -- @see cond + -- @usage + -- return case (type (object), { + -- table = "table", + -- string = function () return "string" end, + -- function (s) error ("unhandled type: " .. s) end, + -- }) + case = X ("case (any?, #table)", case), + + --- Collect the results of an iterator. + -- @function collect + -- @func[opt=std.ipairs] ifn iterator function + -- @param ... *ifn* arguments + -- @treturn table of results from running *ifn* on *args* + -- @see filter + -- @see map + -- @usage + -- --> {"a", "b", "c"} + -- collect {"a", "b", "c", x=1, y=2, z=5} + collect = X ("collect ([func], any*)", base.functional.collect), + + --- Compose functions. + -- @function compose + -- @func ... functions to compose + -- @treturn function composition of fnN .. fn1: note that this is the + -- reverse of what you might expect, but means that code like: + -- + -- functional.compose (function (x) return f (x) end, + -- function (x) return g (x) end)) + -- + -- can be read from top to bottom. + -- @usage + -- vpairs = compose (table.invert, ipairs) + -- for v, i in vpairs {"a", "b", "c"} do process (v, i) end + compose = X ("compose (func*)", compose), + + --- A rudimentary condition-case statement. + -- If *expr* is "truthy" return *branch* if given, otherwise *expr* + -- itself. If the return value is a function or functable, then call it + -- with *expr* as the sole argument and return the result; otherwise + -- return it explicitly. If *expr* is "falsey", then recurse with the + -- first two arguments stripped. + -- @function cond + -- @param expr a Lua expression + -- @param branch a function, functable or value to use if *expr* is + -- "truthy" + -- @param ... additional arguments to retry if *expr* is "falsey" + -- @see case + -- @usage + -- -- recursively calculate the nth triangular number + -- function triangle (n) + -- return cond ( + -- n <= 0, 0, + -- n == 1, 1, + -- function () return n + triangle (n - 1) end) + -- end + cond = cond, -- any number of any type arguments! + + --- Curry a function. + -- @function curry + -- @func fn function to curry + -- @int n number of arguments + -- @treturn function curried version of *fn* + -- @usage + -- add = curry (function (x, y) return x + y end, 2) + -- incr, decr = add (1), add (-1) + curry = X ("curry (func, int)", curry), + + --- Filter an iterator with a predicate. + -- @function filter + -- @tparam predicate pfn predicate function + -- @func[opt=std.pairs] ifn iterator function + -- @param ... iterator arguments + -- @treturn table elements e for which `pfn (e)` is not "falsey". + -- @see collect + -- @see map + -- @usage + -- --> {2, 4} + -- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) + filter = X ("filter (func, [func], any*)", filter), + + --- Fold a binary function left associatively. + -- If parameter *d* is omitted, the first element of *t* is used, + -- and *t* treated as if it had been passed without that element. + -- @function foldl + -- @func fn binary function + -- @param[opt=t[1]] d initial left-most argument + -- @tparam table t a table + -- @return result + -- @see foldr + -- @see reduce + -- @usage + -- foldl (lambda "/", {10000, 100, 10}) == (10000 / 100) / 10 + foldl = X ("foldl (function, [any], table)", foldl), + + --- Fold a binary function right associatively. + -- If parameter *d* is omitted, the last element of *t* is used, + -- and *t* treated as if it had been passed without that element. + -- @function foldr + -- @func fn binary function + -- @param[opt=t[1]] d initial right-most argument + -- @tparam table t a table + -- @return result + -- @see foldl + -- @see reduce + -- @usage + -- foldr (lambda "/", {10000, 100, 10}) == 10000 / (100 / 10) + foldr = X ("foldr (function, [any], table)", foldr), + + --- Identity function. + -- @function id + -- @param ... arguments + -- @return *arguments* + id = id, -- any number of any type arguments! + + --- Compile a lambda string into a Lua function. + -- + -- A valid lambda string takes one of the following forms: + -- + -- 1. `'operator'`: where *op* is a key in @{std.operator}, equivalent to that operation + -- 1. `'=expression'`: equivalent to `function (...) return (expression) end` + -- 1. `'|args|expression'`: equivalent to `function (args) return (expression) end` + -- + -- The second form (starting with `=`) automatically assigns the first + -- nine arguments to parameters `_1` through `_9` for use within the + -- expression body. + -- + -- The results are memoized, so recompiling an previously compiled + -- lambda string is extremely fast. + -- @function lambda + -- @string s a lambda string + -- @treturn table compiled lambda string, can be called like a function + -- @usage + -- -- The following are all equivalent: + -- lambda '<' + -- lambda '= _1 < _2' + -- lambda '|a,b| a {1, 4, 9, 16} + -- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) + map = X ("map (func, [func], any*)", map), + + --- Map a function over a table of argument lists. + -- @function map_with + -- @func fn map function + -- @tparam table tt a table of *fn* argument lists + -- @treturn table new table of *fn* results + -- @see map + -- @see zip_with + -- @usage + -- --> {"123", "45"}, {a="123", b="45"} + -- conc = bind (map_with, {lambda '|...|table.concat {...}'}) + -- conc {{1, 2, 3}, {4, 5}}, conc {a={1, 2, 3, x="y"}, b={4, 5, z=6}} + map_with = X ("map_with (function, table of tables)", map_with), + + --- Memoize a function, by wrapping it in a functable. + -- + -- To ensure that memoize always returns the same results for the same + -- arguments, it passes arguments to *fn*. You can specify a more + -- sophisticated function if memoize should handle complicated argument + -- equivalencies. + -- @function memoize + -- @func fn pure function: a function with no side effects + -- @tparam[opt=std.tostring] normalize normfn function to normalize arguments + -- @treturn functable memoized function + -- @usage + -- local fast = memoize (function (...) --[[ slow code ]] end) + memoize = X ("memoize (func, func?)", memoize), + + --- No operation. + -- This function ignores all arguments, and returns no values. + -- @function nop + -- @see id + -- @usage + -- if unsupported then vtable["memrmem"] = nop end + nop = base.nop, -- ignores all arguments + + --- Fold a binary function into an iterator. + -- @function reduce + -- @func fn reduce function + -- @param d initial first argument + -- @func ifn iterator function + -- @param ... iterator arguments + -- @return result + -- @see foldl + -- @see foldr + -- @usage + -- --> 2 ^ 3 ^ 4 ==> 4096 + -- reduce (lambda '^', 2, std.ipairs, {3, 4}) + reduce = X ("reduce (func, any, func, any*)", reduce), + + --- Zip a table of tables. + -- Make a new table, with lists of elements at the same index in the + -- original table. This function is effectively its own inverse. + -- @function zip + -- @tparam table tt a table of tables + -- @treturn table new table with lists of elements of the same key + -- from *tt* + -- @see map + -- @see zip_with + -- @usage + -- --> {{1, 3, 5}, {2, 4}}, {a={x=1, y=3, z=5}, b={x=2, y=4}} + -- zip {{1, 2}, {3, 4}, {5}}, zip {x={a=1, b=2}, y={a=3, b=4}, z={a=5}} + zip = X ("zip (table of tables)", zip), + + --- Zip a list of tables together with a function. + -- @function zip_with + -- @tparam function fn function + -- @tparam table tt table of tables + -- @treturn table a new table of results from calls to *fn* with arguments + -- made from all elements the same key in the original tables; effectively + -- the "columns" in a simple list + -- of lists. + -- @see map_with + -- @see zip + -- @usage + -- --> {"135", "24"}, {a="1", b="25"} + -- conc = bind (zip_with, {lambda '|...|table.concat {...}'}) + -- conc {{1, 2}, {3, 4}, {5}}, conc {{a=1, b=2}, x={a=3, b=4}, {b=5}} + zip_with = X ("zip_with (function, table of tables)", zip_with), } M.op = operator -- for backwards compatibility + --[[ ============= ]]-- --[[ Deprecations. ]]-- --[[ ============= ]]-- diff --git a/lib/std/io.lua b/lib/std/io.lua index b0cd752..eb33838 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -14,28 +14,17 @@ local base = require "std.base" local debug = require "std.debug" -local package = { - dirsep = string.match (package.config, "^([^\n]+)\n"), -} - local ipairs, pairs = base.ipairs, base.pairs local argerror = debug.argerror local leaves = base.tree.leaves local split = base.split - -local M - +local dirsep = string.match (package.config, "^(%S+)\n") ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- +local M ---- Get an input file handle. --- @tparam[opt=io.input()] file|string h file handle or name --- @return file handle, or nil on error local function input_handle (h) if h == nil then return io.input () @@ -46,19 +35,6 @@ local function input_handle (h) end - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - ---- 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) local function slurp (file) local h, err = input_handle (file) if h == nil then argerror ("std.io.slurp", 1, err, 2) end @@ -71,13 +47,6 @@ local function slurp (file) end ---- 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" local function readlines (file) local h, err = input_handle (file) if h == nil then argerror ("std.io.readlines", 1, err, 2) end @@ -91,12 +60,6 @@ local function readlines (file) end ---- 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") local function writelines (h, ...) if io.type (h) ~= "file" then io.write (h, "\n") @@ -108,13 +71,6 @@ local function writelines (h, ...) end ---- Overwrite core methods and metamethods with `std` enhanced versions. --- --- Adds @{readlines} and @{writelines} metamethods to core file objects. --- @function monkey_patch --- @tparam[opt=_G] table namespace where to install global functions --- @treturn table the `std.io` module table --- @usage local io = require "std.io".monkey_patch () local function monkey_patch (namespace) namespace = namespace or _G @@ -126,65 +82,6 @@ local function monkey_patch (namespace) 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) -local function splitdir (path) - return split (path, package.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") -local function catfile (...) - return table.concat ({...}, package.dirsep) -end - - ---- Concatenate directory names into a path. --- @function catdir --- @string ... path components --- @return path without trailing separator --- @see catfile --- @usage dirpath = catdir ("", "absolute", "directory") -local function catdir (...) - return table.concat ({...}, package.dirsep):gsub("^$", package.dirsep) -end - - ---- 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;}']] -local function shell (c) - return slurp (io.popen (c)) -end - - ---- 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) local function process_files (fn) -- N.B. "arg" below refers to the global array of command-line args if #arg == 0 then @@ -201,25 +98,6 @@ local function process_files (fn) 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. @{std.optparse:parse} --- returns an `opts` table that provides the required `program` --- field, as long as you assign it back to `_G.opts`. --- @function warn --- @string msg format string --- @param ... additional arguments to plug format string specifiers --- @see std.optparse:parse --- @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 local function warn (msg, ...) local prefix = "" if (prog or {}).name then @@ -243,39 +121,139 @@ local function warn (msg, ...) end ---- 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)) -local function die (...) - warn (...) - error () -end +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X (decl, fn) + return debug.export ("std.io." .. decl, fn) +end -local export = debug.export ---- @export M = { - catdir = export "catdir (string*)", - catfile = export "catfile (string*)", - die = export "die (string, any?*)", - monkey_patch = export "monkey_patch (table?)", - process_files = export "process_files (function)", - readlines = export "readlines (file|string|nil)", - shell = export "shell (string)", - slurp = export "slurp (file|string|nil)", - splitdir = export "splitdir (string)", - warn = export "warn (string, any?*)", - writelines = export "writelines (file|string|number?, string|number?*)", + --- 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 table.concat ({...}, dirsep):gsub("^$", 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*)", + function (...) return table.concat ({...}, dirsep) end), + + --- 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 (...) warn (...); error () end), + + --- Overwrite core methods and metamethods with `std` enhanced versions. + -- + -- Adds @{readlines} and @{writelines} metamethods to core file objects. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the `std.io` module table + -- @usage local io = require "std.io".monkey_patch () + monkey_patch = X ("monkey_patch (table?)", monkey_patch), + + --- 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|nil)", 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 (io.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|nil)", slurp), + + --- 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), + + --- 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. @{std.optparse:parse} + -- returns an `opts` table that provides the required `program` + -- field, as long as you assign it back to `_G.opts`. + -- @function warn + -- @string msg format string + -- @param ... additional arguments to plug format string specifiers + -- @see std.optparse:parse + -- @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), + + --- 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), } - for k, v in pairs (io) do M[k] = M[k] or v end diff --git a/lib/std/list.lua b/lib/std/list.lua index 8180f32..778fe8b 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -32,25 +32,14 @@ local func = require "std.functional" local object = require "std.object" local Object = object {} -local List -- forward declaration -local ipairs, pairs = base.ipairs, base.pairs -local ielems = base.ielems +local ielems, ipairs, pairs = base.ielems, base.ipairs, base.pairs +local compare = base.compare local prototype = base.prototype -local M = {} +local M, List - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - ---- Append an item to a list. --- @tparam List l a list --- @param x item --- @treturn List new list containing `{l[1], ..., l[#l], x}` local function append (l, x) local r = l {} r[#r + 1] = x @@ -58,21 +47,6 @@ local function append (l, x) end ---- Compare two lists element-by-element, from left-to-right. --- --- if a_list:compare (another_list) == 0 then print "same" end --- @tparam List l a list --- @tparam table m another list --- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 --- if `l` is greater than `m` -local compare = base.compare - - ---- Concatenate arguments into a list. --- @tparam List l a list --- @param ... tuple of lists --- @treturn List new list containing --- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` local function concat (l, ...) local r = List {} for e in ielems {l, ...} do @@ -84,19 +58,6 @@ local function concat (l, ...) end ---- Prepend an item to a list. --- @tparam List l a list --- @param x item --- @treturn List new list containing `{x, unpack (l)}` -local function cons (l, x) - return List {x, unpack (l)} -end - - ---- Repeat a list. --- @tparam List l a list --- @int n number of times to repeat --- @treturn List `n` copies of `l` appended together local function rep (l, n) local r = List {} for i = 1, n do @@ -106,13 +67,6 @@ local function rep (l, n) end ---- Return a sub-range of a list. --- (The equivalent of `string.sub` on strings; negative list indices --- count from the end of the list.) --- @tparam List l a list --- @int from start of range (default: 1) --- @int to end of range (default: `#l`) --- @treturn List new list containing `{l[from], ..., l[to]}` local function sub (l, from, to) local r = List {} local len = #l @@ -131,25 +85,65 @@ local function sub (l, from, to) end ---- Return a list with its first element removed. --- @tparam List l a list --- @treturn List new list containing `{l[2], ..., l[#l]}` -local function tail (l) - return sub (l, 2) -end +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X (decl, fn) + return debug.export ("std.list." .. decl, fn) +end -local export = debug.export ---- @export -local M = { - append = export "append (List, any)", - compare = export ("std.list", "compare (List, List|table)"), - concat = export "concat (List, List|table*)", - cons = export "cons (List, any)", - rep = export "rep (List, int)", - sub = export "sub (List, int?, int?)", - tail = export "tail (List)", +M = { + --- Append an item to a list. + -- @tparam List l a list + -- @param x item + -- @treturn List new list containing `{l[1], ..., l[#l], x}` + append = X ("append (List, any)", append), + + --- Compare two lists element-by-element, from left-to-right. + -- + -- if a_list:compare (another_list) == 0 then print "same" end + -- @tparam List l a list + -- @tparam table m another list + -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 + -- if `l` is greater than `m` + compare = X ("compare (List, List|table)", compare), + + --- Concatenate arguments into a list. + -- @tparam List l a list + -- @param ... tuple of lists + -- @treturn List new list containing + -- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` + concat = X ("concat (List, List|table*)", concat), + + --- Prepend an item to a list. + -- @tparam List l a list + -- @param x item + -- @treturn List new list containing `{x, unpack (l)}` + cons = X ("cons (List, any)", function (l, x) return List {x, unpack (l)} end), + + --- Repeat a list. + -- @tparam List l a list + -- @int n number of times to repeat + -- @treturn List `n` copies of `l` appended together + rep = X ("rep (List, int)", rep), + + --- Return a sub-range of a list. + -- (The equivalent of `string.sub` on strings; negative list indices + -- count from the end of the list.) + -- @tparam List l a list + -- @int from start of range (default: 1) + -- @int to end of range (default: `#l`) + -- @treturn List new list containing `{l[from], ..., l[to]}` + sub = X ("sub (List, int?, int?)", sub), + + --- Return a list with its first element removed. + -- @tparam List l a list + -- @treturn List new list containing `{l[2], ..., l[#l]}` + tail = X ("tail (List)", function (l) return sub (l, 2) end), } diff --git a/lib/std/math.lua b/lib/std/math.lua index cab7fc7..ffb8c4e 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,15 +11,10 @@ ]] -local M +local debug = require "std.debug" +local M ---- 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) local _floor = math.floor @@ -33,13 +28,6 @@ local function floor (n, p) end ---- Overwrite core methods with `std` enhanced versions. --- --- Replaces core `math.floor` with `std.math` version. --- @function monkey_patch --- @tparam[opt=_G] table namespace where to install global functions --- @treturn table the module table --- @usage require "std.math".monkey_patch () local function monkey_patch (namespace) namespace = namespace or _G namespace.math.floor = M.floor @@ -47,25 +35,48 @@ local function monkey_patch (namespace) end ---- 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) local function round (n, p) local e = 10 ^ (p or 0) return _floor (n * e + 0.5) / e end -local export = require "std.debug".export ---- @export +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X (decl, fn) + return debug.export ("std.math." .. decl, fn) +end + + M = { - floor = export "floor (number, int?)", - monkey_patch = export "monkey_patch (table?)", - round = export "round (number, int?)", + --- 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), + + --- Overwrite core methods with `std` enhanced versions. + -- + -- Replaces core `math.floor` with `std.math` version. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage require "std.math".monkey_patch () + monkey_patch = X ("monkey_patch (table?)", monkey_patch), + + --- 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), } diff --git a/lib/std/package.lua b/lib/std/package.lua index 7608ed6..0fa6483 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -23,17 +23,18 @@ local M ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- +--- 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 path_mark 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 dirsep, pathsep, path_mark, execdir, igmark = + string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") ---- Substitute special characters in a path string. --- Characters prefixed with `%` have the `%` stripped, but are not --- subject to further substitution. --- @string path a path element with explicit `/` and `?` as necessary --- @treturn string `path` with `dirsep` and `path_mark` substituted --- for `/` and `?` local function pathsub (path) return path:gsub ("%%?.", function (capture) if capture == "?" then @@ -47,36 +48,6 @@ local function pathsub (path) end - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - ---- 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 path_mark 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 dirsep, pathsep, path_mark, execdir, igmark = - string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") - - ---- 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 .. "/]") local function find (pathstrings, patt, init, plain) local paths = split (pathstrings, M.pathsep) if plain then patt = escape_pattern (patt) end @@ -88,16 +59,6 @@ local function find (pathstrings, patt, init, plain) end ---- 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 --- `path_mark` (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) local function normalize (...) local i, paths, pathstrings = 1, {}, table.concat ({...}, pathsep) for _, path in ipairs (split (pathstrings, pathsep)) do @@ -120,17 +81,6 @@ local function normalize (...) end ------- --- 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") - local unpack = unpack or table.unpack local function insert (pathstrings, ...) @@ -140,22 +90,6 @@ local function insert (pathstrings, ...) end ------- --- Function signature of a callback for @{mappath}. --- @function mappath_callback --- @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 - - ---- Call a function with each element of a path string. --- @function mappath --- @string pathstrings a `package.path` like string --- @tparam mappath_callback 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) local function mappath (pathstrings, callback, ...) for _, path in ipairs (split (pathstrings, pathsep)) do local r = callback (path, ...) @@ -164,13 +98,6 @@ local function mappath (pathstrings, callback, ...) end ---- 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) local function remove (pathstrings, pos) local paths = split (pathstrings, pathsep) table.remove (paths, pos) @@ -178,15 +105,71 @@ local function remove (pathstrings, pos) end -local export = debug.export ---- @export +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X (decl, fn) + return debug.export ("std.package." .. decl, fn) +end + M = { - find = export "find (string, string, int?, boolean|:plain?)", - insert = export "insert (string, [int], string)", - mappath = export "mappath (string, function, any?*)", - normalize = export "normalize (string*)", - remove = export "remove (string, int?)", + --- 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 mappath_callback 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 + -- `path_mark` (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), } @@ -202,3 +185,14 @@ for k, v in pairs (package) do end return M + + +--- Types +-- @section Types + +--- Function signature of a callback for @{mappath}. +-- @function mappath_callback +-- @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/lib/std/string.lua b/lib/std/string.lua index cf0bbc0..a93fc28 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -19,59 +19,20 @@ local StrBuf = strbuf {} local getmetamethod = base.getmetamethod local pairs = base.pairs +local render = base.render local totable = table.totable -local _format = string.format -local _tostring = base.tostring - local M ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - ---- Pack return arguments for `tfind`. --- @int from start of match --- @int to end of match --- @param ... captures --- @treturn int from --- @treturn int to --- @treturn table captures --- @usage return tfind_pack (string.find -local function tpack (from, to, ...) - return from, to, {...} -end - - - ---[[ ============ ]]-- ---[[ Metamethods. ]]-- ---[[ ============ ]]-- - +local _tostring = base.tostring ---- String concatenation operation. --- @string s initial string --- @param o object to stringify and concatenate --- @return s .. tostring (o) --- @usage --- local string = require "std.string".monkey_patch () --- concatenated = "foo" .. {"bar"} local function __concat (s, o) return _tostring (s) .. _tostring (o) end ---- String subscript operation. --- @string s string --- @tparam int|string i index or method name --- @return `s:sub (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] local function __index (s, i) if type (i) == "number" then return s:sub (i, i) @@ -82,52 +43,22 @@ local function __index (s, i) end +local _format = string.format ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - ---- 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!") local function format (f, arg1, ...) return (arg1 ~= nil) and _format (f, arg1, ...) or f end ---- 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) +local function tpack (from, to, ...) + return from, to, {...} +end + local function tfind (s, ...) return tpack (s:find (...)) end ---- 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 local function finds (s, p, i, ...) i = i or 1 local l = {} @@ -143,27 +74,6 @@ local function finds (s, p, i, ...) 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" -local split = base.split - - ---- Overwrite core methods and metamethods with `std` enhanced versions. --- --- Adds auto-stringification to `..` operator on core strings, and --- integer indexing of strings with `[]` dereferencing. --- --- Also replaces core `tostring` functions with `std.string` version. --- @function monkey_patch --- @tparam[opt=_G] table namespace where to install global functions --- @treturn table the module table --- @usage local string = require "std.string".monkey_patch () local function monkey_patch (namespace) local string_metatable = getmetatable "" string_metatable.__concat = M.__concat @@ -173,55 +83,21 @@ local function monkey_patch (namespace) end ---- Capitalise each word in a string. --- @function caps --- @string s any string --- @treturn string *s* with each word capitalized --- @usage userfullname = caps (input_string) local function caps (s) return s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end) end ---- 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) -local function chomp (s) - return s:gsub ("\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 = inputstr:match (escape_pattern (literal)) local function escape_pattern (s) return s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") end ---- 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)) local function escape_shell (s) return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) end ---- 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)) local function ordinal_suffix (n) n = math.abs (n) % 100 local d = n % 10 @@ -237,16 +113,6 @@ local function ordinal_suffix (n) end ---- 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") local function pad (s, w, p) p = string.rep (p or " ", math.abs (w)) if w < 0 then @@ -256,15 +122,6 @@ local function pad (s, w, p) end ---- 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)) local function wrap (s, w, ind, ind1) w = w or 78 ind = ind or 0 @@ -293,12 +150,6 @@ local function wrap (s, w, ind, ind1) 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") local function numbertosi (n) local SIprefix = { [-8] = "y", [-7] = "z", [-6] = "a", [-5] = "f", @@ -318,95 +169,12 @@ local function numbertosi (n) end ---- 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)) -local function ltrim (s, r) - r = r or "%s+" - return s:gsub ("^" .. r, "") -end - - ---- 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)) -local function rtrim (s, r) - r = r or "%s+" - return s:gsub (r .. "$", "") -end - - ---- 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)) local function trim (s, r) r = r or "%s+" return s:gsub ("^" .. r, ""):gsub (r .. "$", "") end --- Write pretty-printing based on: --- --- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators --- --- Based on "The Design of a Pretty-printing Library in Advanced --- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925 --- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps --- Heavily modified by Simon Peyton Jones, Dec 96 --- --- Haskell types: --- data Doc list of lines --- quote :: Char -> Char -> Doc -> Doc Wrap document in ... --- (<>) :: Doc -> Doc -> Doc Beside --- (<+>) :: Doc -> Doc -> Doc Beside, separated by space --- ($$) :: Doc -> Doc -> Doc Above; if there is no overlap it "dovetails" the two --- nest :: Int -> Doc -> Doc Nested --- punctuate :: Doc -> [Doc] -> [Doc] punctuate p [d1, ... dn] = [d1 <> p, d2 <> p, ... dn-1 <> p, dn] --- render :: Int Line length --- -> Float Ribbons per line --- -> (TextDetails -> a -> a) What to do with text --- -> a What to do at the end --- -> Doc The document --- -> a Result - - ---- Turn tables into strings with recursion detection. --- N.B. Functions calling render should not recurse, or recursion --- detection will not work. --- @function render --- @param x object to convert to string --- @tparam opentablecb open open table rendering function --- @tparam closetablecb close close table rendering function --- @tparam elementcb elem element rendering function --- @tparam paircb pair pair rendering function --- @tparam separatorcb sep separator rendering function --- @tparam[opt] table roots accumulates table references to detect recursion --- @return string representation of *x* --- @usage --- function tostring (x) --- return render (x, lambda '="{"', lambda '="}"', tostring, --- lambda '=_4.."=".._5', lambda '= _4 and "," or ""', --- lambda '=","') --- end -local render = base.render - - ---- 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, " ")) local function prettytostring (x, indent, spacing) indent = indent or "\t" spacing = spacing or "" @@ -461,14 +229,6 @@ local function prettytostring (x, indent, spacing) end ---- Convert a value to a string. --- The string can be passed to `functional.eval` to retrieve the value. --- @todo Make it work for recursive tables. --- @param x object to pickle --- @treturn string reversible string rendering of *x* --- @see std.eval --- @usage --- function slow_identity (x) return functional.eval (pickle (x)) end local function pickle (x) if type (x) == "string" then return format ("%q", x) @@ -492,31 +252,228 @@ local function pickle (x) end -local export = debug.export ---- @export +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X (decl, fn) + return debug.export ("std.string." .. decl, fn) +end + M = { - __concat = __concat, - __index = __index, - caps = export "caps (string)", - chomp = export "chomp (string)", - escape_pattern = export "escape_pattern (string)", - escape_shell = export "escape_shell (string)", - finds = export "finds (string, string, int?, boolean|:plain?)", - format = export "format (string, any?*)", - ltrim = export "ltrim (string, string?)", - monkey_patch = export "monkey_patch (table?)", - numbertosi = export "numbertosi (number|string)", - ordinal_suffix = export "ordinal_suffix (int|string)", - pad = export "pad (string, int, string?)", - pickle = pickle, - prettytostring = export "prettytostring (any?, string?, string?)", - render = export "render (any?, func, func, func, func, func, table?)", - rtrim = export "rtrim (string, string?)", - split = export "split (string, string?)", - tfind = export "tfind (string, string, int?, boolean|:plain?)", - trim = export "trim (string, string?)", - wrap = export "wrap (string, int?, int?, int?)", + --- String concatenation operation. + -- @string s initial string + -- @param o object to stringify and concatenate + -- @return s .. tostring (o) + -- @usage + -- local string = require "std.string".monkey_patch () + -- concatenated = "foo" .. {"bar"} + __concat = __concat, + + --- String subscript operation. + -- @string s string + -- @tparam int|string i index or method name + -- @return `s:sub (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, + + --- 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 s:gsub ("\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 = inputstr:match (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 s:gsub ("^" .. (r or "%s+"), "") end), + + --- Overwrite core methods and metamethods with `std` enhanced versions. + -- + -- Adds auto-stringification to `..` operator on core strings, and + -- integer indexing of strings with `[]` dereferencing. + -- + -- Also replaces core `tostring` functions with `std.string` version. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage local string = require "std.string".monkey_patch () + monkey_patch = X ("monkey_patch (table?)", monkey_patch), + + --- 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), + + --- Convert a value to a string. + -- The string can be passed to `functional.eval` to retrieve the value. + -- @todo Make it work for recursive tables. + -- @param x object to pickle + -- @treturn string reversible string rendering of *x* + -- @see std.eval + -- @usage + -- function slow_identity (x) return functional.eval (pickle (x)) end + pickle = pickle, + + --- 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), + + --- Turn tables into strings with recursion detection. + -- N.B. Functions calling render should not recurse, or recursion + -- detection will not work. + -- @function render + -- @param x object to convert to string + -- @tparam opentablecb open open table rendering function + -- @tparam closetablecb close close table rendering function + -- @tparam elementcb elem element rendering function + -- @tparam paircb pair pair rendering function + -- @tparam separatorcb sep separator rendering function + -- @tparam[opt] table roots accumulates table references to detect recursion + -- @return string representation of *x* + -- @usage + -- function tostring (x) + -- return render (x, lambda '="{"', lambda '="}"', tostring, + -- lambda '=_4.."=".._5', lambda '= _4 and "," or ""', + -- lambda '=","') + -- end + render = X ("render (any?, func, func, func, func, func, table?)", render), + + --- 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 s:gsub ((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?)", base.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), } diff --git a/lib/std/table.lua b/lib/std/table.lua index 62b6c10..1b737fb 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -22,18 +22,6 @@ local ielems, ipairs, pairs = base.ielems, base.ipairs, base.pairs local M - ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - ---- Merge one table's fields into another. --- @tparam table t destination table --- @tparam table u table with fields to merge --- @tparam[opt={}] table map table of `{old_key=new_key, ...}` --- @param nometa if non-nil don't copy metatable --- @treturn table *t* with fields from *u* merged in local function merge_allfields (t, u, map, nometa) map = map or {} if type (map) ~= "table" then @@ -50,13 +38,6 @@ local function merge_allfields (t, u, map, nometa) end ---- Merge one table's named fields into another. --- @tparam table t destination table --- @tparam table u table with fields to merge --- @tparam[opt={}] table keys list of keys to copy --- @param nometa if non-nil don't copy metatable --- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s --- metatable unless *nometa* local function merge_namedfields (t, u, keys, nometa) keys = keys or {} if type (keys) ~= "table" then @@ -73,47 +54,11 @@ local function merge_namedfields (t, u, keys, nometa) end - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - ---- Make a shallow copy of a table, including any metatable. --- --- To make deep copies, use @{tree.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") -local function clone (...) return merge_allfields ({}, ...) end - - ---- Make a partial clone of a table. --- --- Like `clone`, but does not copy any fields by default. --- @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) -local function clone_select (...) return merge_namedfields ({}, ...) end +local function clone (...) + return merge_allfields ({}, ...) +end ---- Turn a list of pairs into a table. --- @todo Find a better name. --- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` --- @treturn table a new list containing table `{i1=v1, ..., in=vn}` --- @see enpair local function depair (ls) local t = {} for v in ielems (ls) do @@ -123,11 +68,6 @@ local function depair (ls) end ---- Turn a table into a list of pairs. --- @todo Find a better name. --- @tparam table t a table `{i1=v1, ..., in=vn}` --- @treturn table a new list of pairs containing `{{i1, v1}, ..., {in, vn}}` --- @see depair local function enpair (t) local tt = {} for i, v in pairs (t) do @@ -137,27 +77,11 @@ local function enpair (t) end ---- Return whether table is empty. --- @tparam table t any table --- @treturn boolean `true` if *t* is empty, otherwise `false` --- @usage if empty (t) then error "ohnoes" end -local function empty (t) - return not next (t) -end - - ---- Flatten a nested table into a list. --- @tparam table t a table --- @treturn table a list of all non-table elements of *t* local function flatten (t) return collect (leaves, ipairs, t) end ---- Invert a table. --- @tparam table t a table with `{k=v, ...}` --- @treturn table inverted table `{v=k, ...}` --- @usage values = invert (t) local function invert (t) local i = {} for k, v in pairs (t) do @@ -167,11 +91,6 @@ local function invert (t) end ---- Make the list of keys in table. --- @tparam table t a table --- @treturn table list of keys from *t* --- @see values --- @usage globals = keys (_G) local function keys (t) local l = {} for k, _ in pairs (t) do @@ -181,38 +100,7 @@ local function keys (t) end ---- Destructively merge another table's fields into another. --- @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") -local merge = merge_allfields - - ---- Destructively merge another table's named fields into *table*. --- --- Like `merge`, but does not merge any fields by default. --- @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) -local merge_select = merge_namedfields - - ---- Make a table with a default value for unset keys. --- @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) + local function new (x, t) return setmetatable (t or {}, {__index = function (t, i) @@ -221,18 +109,11 @@ local function new (x, t) end ---- Turn a tuple into a list. --- @param ... tuple --- @return list local function pack (...) return {...} end ---- Project a list of fields from a list of tables. --- @param fkey field to project --- @tparam table tt a list of tables --- @treturn table list of *fkey* fields from *tt* local function project (fkey, tt) local r = {} for _, t in ipairs (tt) do @@ -242,25 +123,6 @@ local function project (fkey, tt) end ---- Shape a table according to a list of dimensions. --- --- Dimensions are given outermost first and items from the original --- list are distributed breadth first; there may be one 0 indicating --- an indefinite number. Hence, `{0}` is a flat list, --- `{1}` is a singleton, `{2, 0}` is a list of --- two lists, and `{0, 2}` is a list of pairs. --- --- Algorithm: turn shape into all positive numbers, calculating --- the zero if necessary and making sure there is at most one; --- recursively walk the shape, adding empty tables until the bottom --- level is reached at which point add table items instead, using a --- counter to walk the flattened original list. --- --- @todo Use ileaves instead of flatten (needs a while instead of a --- for in fill function) --- @tparam table dims table of dimensions `{d1, ..., dn}` --- @tparam table t a table of elements --- @return reshaped list local function shape (dims, t) t = flatten (t) -- Check the shape and calculate the size of the zero, if any @@ -297,10 +159,6 @@ local function shape (dims, t) end ---- Find the number of elements in a table. --- @tparam table t any table --- @treturn int number of non-nil values in *t* --- @usage count = size {foo = true, bar = true, baz = false} local function size (t) local n = 0 for _ in pairs (t) do @@ -313,36 +171,18 @@ end -- Preserve core table sort function. local _sort = table.sort ---- Make table.sort return its result. --- @tparam table t unsorted table --- @tparam[opt=std.operator["<"]] comparator c ordering function callback --- lua `<` operator --- @return *t* with keys sorted accordind to *c* --- @usage table.concat (sort (object)) local function sort (t, c) _sort (t, c) return t end ---- Overwrite core methods with `std` enhanced versions. --- --- Replaces core `table.sort` with `std.table` version. --- @tparam[opt=_G] table namespace where to install global functions --- @treturn table the module table --- @usage local table = require "std.table".monkey_patch () local function monkey_patch (namespace) namespace.table.sort = M.sort return M end ---- Turn an object into a table according to `__totable` metamethod. --- @function totable --- @tparam object|table|string x object to turn into a table --- @treturn table resulting table or `nil` --- @usage print (table.concat (totable (object))) - local getmetamethod = base.getmetamethod local function totable (x) @@ -361,10 +201,6 @@ local function totable (x) end ---- Make the list of values of a table. --- @tparam table t any table --- @treturn table list of values in *t* --- @see keys local function values (t) local l = {} for _, v in pairs (t) do @@ -374,29 +210,183 @@ local function values (t) end -local export = debug.export ---- @export +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X (decl, fn) + return debug.export ("std.table." .. decl, fn) +end + M = { - clone = export "clone (table, [table], boolean|:nometa?)", - clone_select = export "clone_select (table, [table], boolean|:nometa?)", - depair = export "depair (list of lists)", - enpair = export "enpair (table)", - empty = export "empty (table)", - flatten = export "flatten (table)", - invert = export "invert (table)", - keys = export "keys (table)", - merge = export "merge (table, table, [table], boolean|:nometa?)", - merge_select = export "merge_select (table, table, [table], boolean|:nometa?)", - new = export "new (any?, table?)", - pack = pack, - project = export "project (any, list of tables)", - shape = export "shape (table, table)", - size = export "size (table)", - sort = export "sort (table, function?)", - monkey_patch = export "monkey_patch (table?)", - totable = export "totable (object|table|string)", - values = export "values (table)", + --- Make a shallow copy of a table, including any metatable. + -- + -- To make deep copies, use @{tree.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?)", clone), + + --- Make a partial clone of a table. + -- + -- Like `clone`, but does not copy any fields by default. + -- @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. + -- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` + -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` + -- @see enpair + depair = X ("depair (list of lists)", depair), + + --- Turn a table into a list of pairs. + -- @todo Find a better name. + -- @tparam table t a table `{i1=v1, ..., in=vn}` + -- @treturn table a new list of pairs containing `{{i1, v1}, ..., {in, vn}}` + -- @see depair + enpair = X ("enpair (table)", enpair), + + --- Return whether table is 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), + + --- Flatten a nested table into a list. + -- @tparam table t a table + -- @treturn table a list of all non-table elements of *t* + flatten = X ("flatten (table)", flatten), + + --- Invert a table. + -- @tparam table t a table with `{k=v, ...}` + -- @treturn table inverted table `{v=k, ...}` + -- @usage values = invert (t) + invert = X ("invert (table)", invert), + + --- Make the list of keys in table. + -- @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 another table's fields into another. + -- @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. + -- @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), + + --- Make a table with a default value for unset keys. + -- @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), + + --- Turn a tuple into a list. + -- @param ... tuple + -- @return list + pack = function (...) return {...} end, + + --- Project a list of fields from a list of tables. + -- @param fkey field to project + -- @tparam table tt a list of tables + -- @treturn table list of *fkey* fields from *tt* + project = X ("project (any, list of tables)", project), + + --- Shape a table according to a list of dimensions. + -- + -- Dimensions are given outermost first and items from the original + -- list are distributed breadth first; there may be one 0 indicating + -- an indefinite number. Hence, `{0}` is a flat list, + -- `{1}` is a singleton, `{2, 0}` is a list of + -- two lists, and `{0, 2}` is a list of pairs. + -- + -- Algorithm: turn shape into all positive numbers, calculating + -- the zero if necessary and making sure there is at most one; + -- recursively walk the shape, adding empty tables until the bottom + -- level is reached at which point add table items instead, using a + -- counter to walk the flattened original list. + -- + -- @todo Use ileaves instead of flatten (needs a while instead of a + -- for in fill function) + -- @tparam table dims table of dimensions `{d1, ..., dn}` + -- @tparam table t a table of elements + -- @return reshaped list + shape = X ("shape (table, table)", shape), + + --- Find the number of elements in a table. + -- @tparam table t any table + -- @treturn int number of non-nil values in *t* + -- @usage count = size {foo = true, bar = true, baz = false} + size = X ("size (table)", size), + + --- Make table.sort return its result. + -- @tparam table t unsorted table + -- @tparam[opt=std.operator["<"]] comparator c ordering function callback + -- lua `<` operator + -- @return *t* with keys sorted accordind to *c* + -- @usage table.concat (sort (object)) + sort = X ("sort (table, function?)", sort), + + --- Overwrite core methods with `std` enhanced versions. + -- + -- Replaces core `table.sort` with `std.table` version. + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage local table = require "std.table".monkey_patch () + monkey_patch = X ("monkey_patch (table?)", monkey_patch), + + --- Turn an object into a table according to `__totable` metamethod. + -- @function totable + -- @tparam object|table|string x object to turn into a table + -- @treturn table resulting table or `nil` + -- @usage print (table.concat (totable (object))) + totable = X ("totable (object|table|string)", totable), + + --- Make the list of values of a table. + -- @tparam table t any table + -- @treturn table list of values in *t* + -- @see keys + values = X ("values (table)", values), } diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 39e4d04..c3323a3 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -192,7 +192,7 @@ specify std.debug: - describe argcheck: - before: | Object = require 'std.object' - List = require "std.list" + List = Object { _type = "List" } Foo = Object { _type = "Foo" } function fn (...) return M.argcheck ('expect', 1, ...) end @@ -664,7 +664,7 @@ specify std.debug: return string.format ([[ local export = require "std.debug".export -- line 1 local function caller () -- line 2 - export ({%s}, "%s", function () end) -- line 3 + export ("%s", function () end) -- line 3 end -- line 4 caller () -- line 5 ]], tostring (name), tostring (spec)) @@ -673,8 +673,7 @@ specify std.debug: f, badarg = init (M, this_module, "export") mkmagic = function () return "MAGIC" end - local inner = mkmagic - wrapped = f "inner ()" + wrapped = f ("inner ()", mkmagic) - it returns the wrapped function: expect (wrapped).not_to_be (inner) @@ -685,7 +684,7 @@ specify std.debug: local debug = require "std.debug_init" local export = require "std.debug".export local function inner () return "MAGIC" end - local wrapped = export "inner (any?)" + local wrapped = export ("inner (any?)", inner) os.exit (wrapped == inner and 0 or 1) ]] expect (luaproc (script)).to_succeed () @@ -701,8 +700,7 @@ specify std.debug: - context when checking single argument function: - before: _, badarg = init (M, "", "inner") - local inner = mkmagic - wrapped = f "inner (#table)" + wrapped = f ("inner (#table)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "non-empty table")) - it diagnoses wrong argument types: @@ -715,8 +713,7 @@ specify std.debug: - context when checking multi-argument function: - before: _, badarg = init (M, "", "inner") - local inner = mkmagic - wrapped = f "inner (table, function)" + wrapped = f ("inner (table, function)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "table")) expect (wrapped ({})).to_raise (badarg (2, "function")) @@ -731,8 +728,7 @@ specify std.debug: - context when checking optional argument function: - before: _, badarg = init (M, "", "inner") - local inner = mkmagic - wrapped = f "inner ([int])" + wrapped = f ("inner ([int])", mkmagic) - it diagnoses wrong argument types: expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) - it diagnoses too many arguments: @@ -744,8 +740,7 @@ specify std.debug: - context when checking optional multi-argument function: - before: _, badarg = init (M, "", "inner") - local inner = mkmagic - wrapped = f "inner ([int], string)" + wrapped = f ("inner ([int], string)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "int or string")) expect (wrapped (1)).to_raise (badarg (2, "string")) From 275270a864663570f81db303c64a078166390096 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 17:38:56 +0100 Subject: [PATCH 401/703] refactor: std.vector simplifications. * lib/std/vector.lua: Fix usage examples to use `avector` instead of `anvector`. (set): Factor away use of debug.argscheck. Signed-off-by: Gary V. Vaughan --- lib/std/vector.lua | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/std/vector.lua b/lib/std/vector.lua index ad2ffaa..cc9df54 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -42,8 +42,8 @@ local Container = container {} local typeof = type -local argcheck, argscheck, pairs, prototype = - debug.argcheck, debug.argscheck, base.pairs, base.prototype +local argcheck, pairs, prototype = + debug.argcheck, base.pairs, base.prototype --[[ ================= ]]-- @@ -104,7 +104,7 @@ local core_functions = { --- Remove the right-most element. -- @function pop -- @return the right-most element - -- @usage removed = anvector:pop () + -- @usage removed = avector:pop () pop = function (self) self.length = math.max (self.length - 1, 0) return table.remove (self.buffer) @@ -115,7 +115,7 @@ local core_functions = { -- @function push -- @param elem new element to be pushed -- @return elem - -- @usage added = anvector:push (anelement) + -- @usage added = avector:push (anelement) push = function (self, elem) local length = self.length + 1 self.buffer[length] = elem @@ -128,7 +128,7 @@ local core_functions = { -- @function realloc -- @int n the number of elements required -- @treturn std.vector the vector - -- @usage anvector = anvector:realloc (anvector.length) + -- @usage avector = avector:realloc (avector.length) realloc = function (self, n) argcheck ("realloc", 2, "int", n) @@ -148,10 +148,11 @@ local core_functions = { -- @param v value to store -- @int n number of elements to set -- @treturn std.vector the vector - -- @usage anvector:realloc (anvector.length):set (1, -1, anvector.length) + -- @usage avector:realloc (avector.length):set (1, -1, avector.length) set = function (self, from, v, n) - argscheck ("set", {"Vector", "int", "any", "int"}, - {self, from, v, n}) + argcheck ("set", 2, "int", from) + argcheck ("set", 3, "any", v) + argcheck ("set", 4, "int", n) local length = self.length if from < 0 then from = from + length + 1 end @@ -169,7 +170,7 @@ local core_functions = { -- This makes the vector 1 element shorter than it was before the shift. -- @function shift -- @return the removed element. - -- @usage removed = anvector:shift () + -- @usage removed = avector:shift () shift = function (self) self.length = math.max (self.length - 1, 0) return table.remove (self.buffer, 1) @@ -180,7 +181,7 @@ local core_functions = { -- @function unshift -- @param elem new element to be pushed -- @treturn elem - -- @usage added = anvector:unshift (anelement) + -- @usage added = avector:unshift (anelement) unshift = function (self, elem) self.length = self.length + 1 table.insert (self.buffer, 1, elem) @@ -300,7 +301,7 @@ core_metatable = { -- @function __index -- @int n 1-based index, or negative to index starting from the right -- @treturn string the element at index `n` - -- @usage rightmost = anvector[anvector.length] + -- @usage rightmost = avector[avector.length] __index = function (self, n) argcheck ("__index", 2, "int|string", n) @@ -320,7 +321,7 @@ core_metatable = { -- @int n 1-based index -- @param elem value to store at index n -- @treturn std.vector the vector - -- @usage anvector[1] = newvalue + -- @usage avector[1] = newvalue __newindex = function (self, n, elem) argcheck ("__newindex", 2, "int", n) @@ -345,7 +346,7 @@ core_metatable = { -- `vector.length` if you care about portability. -- @function __len -- @treturn int number of elements - -- @usage length = #anvector + -- @usage length = #avector __len = function (self) argcheck ("__len", 1, "Vector", self) @@ -356,7 +357,7 @@ core_metatable = { --- Return a string representation of the contents of this vector. -- @function __tostring -- @treturn string string representation - -- @usage print (anvector) + -- @usage print (avector) __tostring = function (self) argcheck ("__tostring", 1, "Vector", self) @@ -424,8 +425,9 @@ local alien_functions = { set = function (self, from, v, n) - argscheck ("set", {"Vector", "int", "number", "int"}, - {self, from, v, n}) + argcheck ("set", 2, "int", from) + argcheck ("set", 3, "number", v) + argcheck ("set", 4, "int", n) local used = self.length if from < 0 then from = from + used + 1 end From 4fce966c10590bd53972a1ee3114f75b8f2b413b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 18:25:23 +0100 Subject: [PATCH 402/703] refactor: merge lib/std/base files into std.base. * lib/std/base/functional.lua (callable, collect, reduce): Move from here... * lib/std/base/string.lua (copy, render, split): ...here... * lib/std/base/tree.lua (leaves): ...and here... * lib/std/base.lua (callable, collect, reduce, copy, render) (split, leaves): ...to here. Adjust all callers. * lib/std/base/functional.lua, lib/std/base/string.lua, lib/std/base/tree.lua: Remove files. * local.mk (luastdbasedir, dist_luastdbase_DATA): Remove. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 172 ++++++++++++++++++++++++++++-------- lib/std/base/functional.lua | 56 ------------ lib/std/base/string.lua | 94 -------------------- lib/std/base/tree.lua | 28 ------ lib/std/debug.lua | 6 +- lib/std/functional.lua | 5 +- lib/std/io.lua | 2 +- lib/std/list.lua | 6 +- lib/std/table.lua | 4 +- lib/std/tree.lua | 2 +- local.mk | 8 -- 11 files changed, 149 insertions(+), 234 deletions(-) delete mode 100644 lib/std/base/functional.lua delete mode 100644 lib/std/base/string.lua delete mode 100644 lib/std/base/tree.lua diff --git a/lib/std/base.lua b/lib/std/base.lua index 4596cdc..f942323 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,12 +23,6 @@ ]] -local callable = require "std.base.functional".callable -local bstring = require "std.base.string" -local copy, render, split = bstring.copy, bstring.render, bstring.split - - - local function len (t) -- Lua < 5.2 doesn't call `__len` automatically! local m = (getmetatable (t) or {}).__len @@ -36,6 +30,27 @@ local function len (t) end +local function copy (t) + local new = {} + for k, v in pairs (t) do new[k] = v end + return new +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 + coroutine.yield (n) + end + end + return coroutine.wrap (visit), tr +end + + local _pairs = pairs -- Respect __pairs metamethod, even in Lua 5.1. @@ -150,6 +165,19 @@ local function eval (s) end +local function split (s, sep) + sep = sep or "%s+" + local b, len, t, patt = 0, #s, {}, "(.-)" .. sep + if sep == "" then patt = "(.)"; t[#t + 1] = "" end + while b <= len do + local e, n, m = string.find (s, patt, b + 1) + t[#t + 1] = m or s:sub (b + 1, len) + b = n or len + 1 + end + return t +end + + local function vcompare (a, b) return compare (split (a, "%."), split (b, "%.")) end @@ -172,6 +200,60 @@ local function require (module, min, too_big, pattern) end +-- Write pretty-printing based on: +-- +-- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators +-- +-- Based on "The Design of a Pretty-printing Library in Advanced +-- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925 +-- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps +-- Heavily modified by Simon Peyton Jones, Dec 96 +-- +-- Haskell types: +-- data Doc list of lines +-- quote :: Char -> Char -> Doc -> Doc Wrap document in ... +-- (<>) :: Doc -> Doc -> Doc Beside +-- (<+>) :: Doc -> Doc -> Doc Beside, separated by space +-- ($$) :: Doc -> Doc -> Doc Above; if there is no overlap it "dovetails" the two +-- nest :: Int -> Doc -> Doc Nested +-- punctuate :: Doc -> [Doc] -> [Doc] punctuate p [d1, ... dn] = [d1 <> p, d2 <> p, ... dn-1 <> p, dn] +-- render :: Int Line length +-- -> Float Ribbons per line +-- -> (TextDetails -> a -> a) What to do with text +-- -> a What to do at the end +-- -> Doc The document +-- -> a Result + +local function render (x, open, close, elem, pair, sep, roots) + local function stop_roots (x) + return roots[x] or render (x, open, close, elem, pair, sep, copy (roots)) + end + roots = roots or {} + if type (x) ~= "table" or type ((getmetatable (x) or {}).__tostring) == "function" then + return elem (x) + else + local s = {} + s[#s + 1] = open (x) + roots[x] = elem (x) + + -- create a sorted list of keys + local ord = {} + for k, _ in pairs (x) do ord[#ord + 1] = k end + table.sort (ord, function (a, b) return tostring (a) < tostring (b) end) + + -- render x elements in order + local i, v = nil, nil + for _, j in ipairs (ord) do + local w = x[j] + s[#s + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) + i, v = j, w + end + s[#s + 1] = sep (x, i, v, nil, nil) .. close (x) + return table.concat (s) + end +end + + local _tostring = _G.tostring local function tostring (x) @@ -185,20 +267,53 @@ end ---[[ ========================= ]]-- ---[[ Documented in object.lua. ]]-- ---[[ ========================= ]]-- - - local function prototype (o) return (getmetatable (o) or {})._type or io.type (o) or type (o) end ---- Metamethods --- @section Metamethods +local function callable (x) + if type (x) == "function" then + return x + else + x = (getmetatable (x) or {}).__call + if type (x) == "function" then + return x + end + end +end + + +local function collect (ifn, ...) + local argt = {...} + if not callable (ifn) then + ifn, argt = ipairs, {ifn, ...} + end + + local r = {} + for k, v in ifn (unpack (argt)) do + if v == nil then k, v = #r + 1, k end + r[k] = v + end + return r +end + + +local function reduce (fn, d, ifn, ...) + local nextfn, state, k = ifn (...) + local t = {nextfn (state, k)} + + local r = d + while t[1] ~= nil do + r = fn (r, t[#t]) + t = {nextfn (state, t[1])} + end + return r +end -return setmetatable ({ + +return { + copy = copy, len = len, -- std.lua -- @@ -215,7 +330,10 @@ return setmetatable ({ tostring = tostring, -- functional.lua -- - nop = function () end, + callable = callable, + collect = collect, + nop = function () end, + reduce = reduce, -- list.lua -- compare = compare, @@ -230,23 +348,7 @@ return setmetatable ({ -- table.lua -- getmetamethod = getmetamethod, -}, { - - --- Lazy loading of shared base 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 base = require "base" - -- local memoize = base.functional.memoize - __index = function (self, name) - local ok, t = pcall (require, "std.base." .. name) - if ok then - rawset (self, name, t) - return t - end - end, -}) + -- tree.lua -- + leaves = leaves, + +} diff --git a/lib/std/base/functional.lua b/lib/std/base/functional.lua deleted file mode 100644 index 8d1e5a3..0000000 --- a/lib/std/base/functional.lua +++ /dev/null @@ -1,56 +0,0 @@ ---[[-- - Base implementations of functions exported by `std.functional`. - - These functions are required by implementations of exported functions - in other stdlib modules. We keep them here to avoid bloating std.base, - which is loaded by *every* stdlib module. - - @module std.base.functional -]] - - -local function callable (x) - if type (x) == "function" then - return x - else - x = (getmetatable (x) or {}).__call - if type (x) == "function" then - return x - end - end -end - - -local function collect (ifn, ...) - local argt = {...} - if not callable (ifn) then - ifn, argt = ipairs, {ifn, ...} - end - - local r = {} - for k, v in ifn (unpack (argt)) do - if v == nil then k, v = #r + 1, k end - r[k] = v - end - return r -end - - -local function reduce (fn, d, ifn, ...) - local nextfn, state, k = ifn (...) - local t = {nextfn (state, k)} - - local r = d - while t[1] ~= nil do - r = fn (r, t[#t]) - t = {nextfn (state, t[1])} - end - return r -end - - -return { - callable = callable, - collect = collect, - reduce = reduce, -} diff --git a/lib/std/base/string.lua b/lib/std/base/string.lua deleted file mode 100644 index 880a4cd..0000000 --- a/lib/std/base/string.lua +++ /dev/null @@ -1,94 +0,0 @@ ---[[-- - Base implementations of functions exported by `std.string`. - - These functions are required by implementations of exported functions - in other stdlib modules. We keep them here to ensure argument checking - error messages report the correct module ('std.string') after "%.base" - has been stripped from `debug.getinfo (fn, "S").short_src`. - - @module std.base.string -]] - - ---- Make a shallow copy of a table. --- @tparam table t source table --- @treturn table shallow copy of *t* -local function copy (t) - local new = {} - for k, v in pairs (t) do new[k] = v end - return new -end - - --- Write pretty-printing based on: --- --- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators --- --- Based on "The Design of a Pretty-printing Library in Advanced --- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925 --- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps --- Heavily modified by Simon Peyton Jones, Dec 96 --- --- Haskell types: --- data Doc list of lines --- quote :: Char -> Char -> Doc -> Doc Wrap document in ... --- (<>) :: Doc -> Doc -> Doc Beside --- (<+>) :: Doc -> Doc -> Doc Beside, separated by space --- ($$) :: Doc -> Doc -> Doc Above; if there is no overlap it "dovetails" the two --- nest :: Int -> Doc -> Doc Nested --- punctuate :: Doc -> [Doc] -> [Doc] punctuate p [d1, ... dn] = [d1 <> p, d2 <> p, ... dn-1 <> p, dn] --- render :: Int Line length --- -> Float Ribbons per line --- -> (TextDetails -> a -> a) What to do with text --- -> a What to do at the end --- -> Doc The document --- -> a Result - -local function render (x, open, close, elem, pair, sep, roots) - local function stop_roots (x) - return roots[x] or render (x, open, close, elem, pair, sep, copy (roots)) - end - roots = roots or {} - if type (x) ~= "table" or type ((getmetatable (x) or {}).__tostring) == "function" then - return elem (x) - else - local s = {} - s[#s + 1] = open (x) - roots[x] = elem (x) - - -- create a sorted list of keys - local ord = {} - for k, _ in pairs (x) do ord[#ord + 1] = k end - table.sort (ord, function (a, b) return tostring (a) < tostring (b) end) - - -- render x elements in order - local i, v = nil, nil - for _, j in ipairs (ord) do - local w = x[j] - s[#s + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) - i, v = j, w - end - s[#s + 1] = sep (x, i, v, nil, nil) .. close (x) - return table.concat (s) - end -end - - -local function split (s, sep) - sep = sep or "%s+" - local b, len, t, patt = 0, #s, {}, "(.-)" .. sep - if sep == "" then patt = "(.)"; t[#t + 1] = "" end - while b <= len do - local e, n, m = string.find (s, patt, b + 1) - t[#t + 1] = m or s:sub (b + 1, len) - b = n or len + 1 - end - return t -end - - -return { - copy = copy, - render = render, - split = split, -} diff --git a/lib/std/base/tree.lua b/lib/std/base/tree.lua deleted file mode 100644 index 62fad4e..0000000 --- a/lib/std/base/tree.lua +++ /dev/null @@ -1,28 +0,0 @@ ---[[-- - Base implementations of functions exported by `std.tree`. - - These functions are required by implementations of exported functions - in other stdlib modules. We keep them here to avoid bloating std.base, - which is loaded by *every* stdlib module. - - @module std.base.tree -]] - - -local function leaves (it, tr) - local function visit (n) - if type (n) == "table" then - for _, v in it (n) do - visit (v) - end - else - coroutine.yield (n) - end - end - return coroutine.wrap (visit), tr -end - - -return { - leaves = leaves, -} diff --git a/lib/std/debug.lua b/lib/std/debug.lua index b6b5e8a..2194974 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -34,8 +34,8 @@ local base = require "std.base" local _ARGCHECK = debug_init._ARGCHECK local _DEBUG = debug_init._DEBUG -local callable = base.functional.callable -local split, tostring = base.string.split, base.tostring +local callable = base.callable +local split, tostring = base.split, base.tostring local M @@ -240,7 +240,7 @@ local argcheck, argscheck, export -- forward declarations if _ARGCHECK then - local copy, prototype = base.string.copy, base.prototype + local copy, prototype = base.copy, base.prototype --- Concatenate a table of strings using ", " and " or " delimiters. -- @tparam table alternatives a table of strings diff --git a/lib/std/functional.lua b/lib/std/functional.lua index e8b0fbc..6d0918c 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -14,8 +14,7 @@ local operator = require "std.operator" local ipairs, ireverse, len, pairs = base.ipairs, base.ireverse, base.len, base.pairs -local callable, reduce = - base.functional.callable, base.functional.reduce +local callable, reduce = base.callable, base.reduce local function bind (fn, ...) @@ -311,7 +310,7 @@ local M = { -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} - collect = X ("collect ([func], any*)", base.functional.collect), + collect = X ("collect ([func], any*)", base.collect), --- Compose functions. -- @function compose diff --git a/lib/std/io.lua b/lib/std/io.lua index eb33838..d7f2015 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -16,7 +16,7 @@ local debug = require "std.debug" local ipairs, pairs = base.ipairs, base.pairs local argerror = debug.argerror -local leaves = base.tree.leaves +local leaves = base.leaves local split = base.split local dirsep = string.match (package.config, "^(%S+)\n") diff --git a/lib/std/list.lua b/lib/std/list.lua index 778fe8b..ef459a0 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -191,7 +191,7 @@ end local function flatten (l) local r = List {} - for v in base.tree.leaves (ipairs, l) do + for v in base.leaves (ipairs, l) do r[#r + 1] = v end return r @@ -204,7 +204,7 @@ local function foldl (fn, d, t) for i = 2, len (d) do tail[#tail + 1] = d[i] end d, t = d[1], tail end - return base.functional.reduce (fn, d, ipairs, t) + return base.reduce (fn, d, ipairs, t) end @@ -214,7 +214,7 @@ local function foldr (fn, d, t) for i = 1, last - 1 do u[#u + 1] = d[i] end d, t = d[last], u end - return base.functional.reduce ( + return base.reduce ( function (x, y) return fn (y, x) end, d, ipairs, base.ireverse (t)) end diff --git a/lib/std/table.lua b/lib/std/table.lua index 1b737fb..371cdb6 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -14,8 +14,8 @@ local base = require "std.base" local debug = require "std.debug" -local collect = base.functional.collect -local leaves = base.tree.leaves +local collect = base.collect +local leaves = base.leaves local ielems, ipairs, pairs = base.ielems, base.ipairs, base.pairs diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 7cb77a3..8678142 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -19,7 +19,7 @@ local operator = require "std.operator" local Container = container {} local ielems, ipairs, base_leaves, pairs, prototype = - base.ielems, base.ipairs, base.tree.leaves, base.pairs, base.prototype + base.ielems, base.ipairs, base.leaves, base.pairs, base.prototype local reduce = func.reduce local Tree -- forward declaration diff --git a/local.mk b/local.mk index 1967279..8186560 100644 --- a/local.mk +++ b/local.mk @@ -82,14 +82,6 @@ dist_luastd_DATA = \ lib/std/vector.lua \ $(NOTHING_ELSE) -luastdbasedir = $(luastddir)/base - -dist_luastdbase_DATA = \ - lib/std/base/functional.lua \ - lib/std/base/string.lua \ - lib/std/base/tree.lua \ - $(NOTHING_ELSE) - # For bugwards compatibility with LuaRocks 2.1, while ensuring that # `require "std.debug_init"` continues to work, we have to install # the former `$(luadir)/std/debug_init.lua` to `debug_init/init.lua`. From d95d95e344f63ae2a198f65af3ec9a8e51fc8e94 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 18:39:21 +0100 Subject: [PATCH 403/703] refactor: merge debug.argscheck and debug.export. * lib/std/debug.lua (argscheck): Remove. (export): Rename to argscheck. Adjust all callers. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 2 +- lib/std/debug.lua | 32 ++---------- lib/std/functional.lua | 2 +- lib/std/io.lua | 2 +- lib/std/list.lua | 2 +- lib/std/math.lua | 2 +- lib/std/package.lua | 2 +- lib/std/string.lua | 2 +- lib/std/table.lua | 2 +- specs/debug_spec.yaml | 108 ++++------------------------------------- 10 files changed, 23 insertions(+), 133 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 31e49c1..cc210eb 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -232,7 +232,7 @@ end local function X (decl, fn) - return debug.export ("std.container." .. decl, fn) + return debug.argscheck ("std.container." .. decl, fn) end local M = { diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 2194974..517f7a6 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -236,7 +236,7 @@ local function toomanyargmsg (name, expect, actual) end -local argcheck, argscheck, export -- forward declarations +local argcheck, argscheck -- forward declarations if _ARGCHECK then @@ -577,26 +577,7 @@ if _ARGCHECK then end - --- Check that all arguments match specified types. - -- @function argscheck - -- @string name function to blame in error message - -- @tparam table expected a list of acceptable argument types - -- @tparam table actual table of argument values - -- @usage - -- local function curry (f, n) - -- argscheck ("std.functional.curry", {"function", "int"}, {f, n}) - -- ... - function argscheck (name, expected, actual) - if type (expected) ~= "table" then expected = {expected} end - if type (actual) ~= "table" then actual = {actual} end - - for i, v in ipairs (expected) do - argcheck (name, i, expected[i], actual[i], 3) - end - end - - - --- Export a function definition, optionally with argument type checking. + --- Wrap a function definition with argument type and arity checking. -- In addition to checking that each argument type matches the corresponding -- element in the *types* table with `argcheck`, if the final element of -- *types* ends with an asterisk, remaining unchecked arguments are checked @@ -604,8 +585,8 @@ if _ARGCHECK then -- @string decl function type declaration string -- @func inner function to wrap with argument checking -- @usage - -- M.square = export ("util.square (number)", function (n) return n * n end) - function export (decl, inner) + -- M.square = argscheck ("util.square (number)", function (n) return n * n end) + function argscheck (decl, inner) -- Parse "fname (argtype, argtype, argtype...)". local fname, types = decl:match "([%w_][%.%d%w_]*)%s+%((.*)%)" if types == "" then @@ -693,9 +674,7 @@ else -- a false valued `argcheck` field. argcheck = base.nop - argscheck = base.nop - - export = function (decl, inner) return inner end + argscheck = function (decl, inner) return inner end end @@ -778,7 +757,6 @@ M = { argerror = argerror, arglen = arglen, argscheck = argscheck, - export = export, say = say, toomanyargmsg = toomanyargmsg, trace = trace, diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 6d0918c..fcc2add 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -259,7 +259,7 @@ end local function X (decl, fn) - return debug.export ("std.functional." .. decl, fn) + return debug.argscheck ("std.functional." .. decl, fn) end local M = { diff --git a/lib/std/io.lua b/lib/std/io.lua index d7f2015..b66f420 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -128,7 +128,7 @@ end local function X (decl, fn) - return debug.export ("std.io." .. decl, fn) + return debug.argscheck ("std.io." .. decl, fn) end diff --git a/lib/std/list.lua b/lib/std/list.lua index ef459a0..1a5555c 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -92,7 +92,7 @@ end local function X (decl, fn) - return debug.export ("std.list." .. decl, fn) + return debug.argscheck ("std.list." .. decl, fn) end diff --git a/lib/std/math.lua b/lib/std/math.lua index ffb8c4e..5b54524 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -48,7 +48,7 @@ end local function X (decl, fn) - return debug.export ("std.math." .. decl, fn) + return debug.argscheck ("std.math." .. decl, fn) end diff --git a/lib/std/package.lua b/lib/std/package.lua index 0fa6483..003de16 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -112,7 +112,7 @@ end local function X (decl, fn) - return debug.export ("std.package." .. decl, fn) + return debug.argscheck ("std.package." .. decl, fn) end M = { diff --git a/lib/std/string.lua b/lib/std/string.lua index a93fc28..559d525 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -259,7 +259,7 @@ end local function X (decl, fn) - return debug.export ("std.string." .. decl, fn) + return debug.argscheck ("std.string." .. decl, fn) end M = { diff --git a/lib/std/table.lua b/lib/std/table.lua index 371cdb6..ab9b90e 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -217,7 +217,7 @@ end local function X (decl, fn) - return debug.export ("std.table." .. decl, fn) + return debug.argscheck ("std.table." .. decl, fn) end M = { diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index c3323a3..5d2744d 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,8 +4,7 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "arglen", "argscheck", "export", "say", "toomanyargmsg", - "trace" } + "arglen", "argscheck", "say", "toomanyargmsg", "trace" } M = require (this_module) @@ -533,93 +532,6 @@ specify std.debug: expect (fn ("#table?|table?", {})).not_to_raise "any error" -- describe argscheck: - - before: | - function fn (...) return M.argscheck ('expect', ...) end - - function mkstack (debugp) - return string.format ([[ - _DEBUG = %s -- line 1 - local debug = require "std.debug" -- line 2 - function ohnoes (t, n) -- line 3 - debug.argscheck ("ohnoes", {"table", "number"}, {t, n}) -- line 4 - end -- line 5 - function caller () -- line 6 - local r = ohnoes ({}, "not a number") -- line 7 - return "not a tail call" -- line 8 - end -- line 9 - caller () -- line 10 - ]], tostring (debugp)) - end - - f, badarg = init (M, this_module, "argscheck") - - - it diagnoses missing arguments: - pending "Lua 5.1 support is dropped" - expect (f "foo").to_raise (badarg (2, "non-empty list")) - expect (f ("foo", {"bar"})).to_raise (badarg (3, "table")) - - it diagnoses wrong argument types: - pending "Lua 5.1 support is dropped" - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("foo", false)). - to_raise (badarg (2, "non-empty list", "boolean")) - expect (f ("foo", {"bar"}, false)).to_raise (badarg (3, "table", "boolean")) - - it diagnoses too many arguments: - pending "Lua 5.1 support is dropped" - expect (f ("foo", {"bar"}, {}, false)).to_raise (badarg (4)) - - - it blames the calling function: | - expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" - - it can be disabled by setting _DEBUG to false: - expect (luaproc (mkstack (false))). - not_to_contain_error "bad argument" - - it can be disabled by setting _DEBUG.argcheck to false: - expect (luaproc (mkstack ("{ argcheck = false }"))). - not_to_contain_error "bad argument" - - it is not disabled by setting _DEBUG.argcheck to true: - expect (luaproc (mkstack ("{ argcheck = true }"))). - to_contain_error "bad argument" - - it is not disabled by leaving _DEBUG.argcheck unset: - expect (luaproc (mkstack ("{}"))). - to_contain_error "bad argument" - - context with single argument table: - - it diagnoses missing argument: - expect (fn ({"boolean"}, {nil})). - to_raise "boolean expected, got no value" - - it reports the correct missing argument number: - expect (fn ({"boolean"}, {nil})).to_raise "#1 " - - it diagnoses mismatched argument: - expect (fn ({"boolean"}, {"false"})). - to_raise "boolean expected, got string" - - it reports the correct mismatched argument number: - expect (fn ({"boolean"}, {"false"})).to_raise "#1 " - - it matches argument type: - expect (fn ({"boolean"}, {false})).not_to_raise "any error" - - context with multi-argument table: - - it diagnoses missing argument: - expect (fn ({"boolean", "table"}, {false, nil})). - to_raise "table expected, got no value" - expect (fn ({"boolean", "table", "string"}, {false, nil, "nil"})). - to_raise "table expected, got no value" - - it reports the correct missing argument number: - expect (fn ({"boolean", "table"}, {false, nil})).to_raise "#2 " - expect (fn ({"boolean", "table", "string"}, {false, nil, "nil"})). - to_raise "#2 " - - it diagnoses mismatched argument: - expect (fn ({"boolean", "table"}, {false, "false"})). - to_raise "table expected, got string" - expect (fn ({"boolean", "table", "string"}, {false, "nil", "nil"})). - to_raise "table expected, got string" - - it reports the correct mismatched argument number: - expect (fn ({"boolean", "table"}, {false, "false"})).to_raise "#2 " - expect (fn ({"boolean", "table", "string"}, {false, "nil", "nil"})). - to_raise "#2 " - - it matches argument type: - expect (fn ({"boolean", "table"}, {false, {}})).not_to_raise "any error" - expect (fn ({"boolean", "table", "string"}, {false, {}, "{}"})). - not_to_raise "any error" - - - describe debug: - before: | function mkwrap (k, v) @@ -658,19 +570,19 @@ specify std.debug: to_contain_error "debugging" -- describe export: +- describe argscheck: - before: | function mkstack (name, spec) return string.format ([[ - local export = require "std.debug".export -- line 1 - local function caller () -- line 2 - export ("%s", function () end) -- line 3 - end -- line 4 - caller () -- line 5 + local argscheck = require "std.debug".argscheck -- line 1 + local function caller () -- line 2 + argscheck ("%s", function () end) -- line 3 + end -- line 4 + caller () -- line 5 ]], tostring (name), tostring (spec)) end - f, badarg = init (M, this_module, "export") + f, badarg = init (M, this_module, "argscheck") mkmagic = function () return "MAGIC" end wrapped = f ("inner ()", mkmagic) @@ -682,9 +594,9 @@ specify std.debug: script = [[ _DEBUG = false local debug = require "std.debug_init" - local export = require "std.debug".export + local argscheck = require "std.debug".argscheck local function inner () return "MAGIC" end - local wrapped = export ("inner (any?)", inner) + local wrapped = argscheck ("inner (any?)", inner) os.exit (wrapped == inner and 0 or 1) ]] expect (luaproc (script)).to_succeed () From 361b507a3a5f343aa0c7fa3fcf23d682ef1ef6be Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 18:48:39 +0100 Subject: [PATCH 404/703] doc: improve LDoc usage examples in std.debug. * lib/std/debug (DEPRECATIONMSG, DEPRECATED): Improve LDoc usage examples. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 517f7a6..b2c013e 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -56,7 +56,6 @@ local M --- Extend `debug.setfenv` to unwrap functables correctly. --- @function setfenv -- @tparam function|functable fn target function -- @tparam table env new function environment -- @treturn function *fn* @@ -91,7 +90,6 @@ end --- Extend `debug.getfenv` to unwrap functables correctly. --- @function getfenv -- @tparam int|function|functable fn target function, or stack level -- @treturn table environment of *fn* local getfenv = getfenv or function (fn) @@ -191,7 +189,6 @@ end -- Equivalent to luaL_argerror in the Lua C API. This function does not -- return. The `level` argument behaves just like the core `error` -- function. --- @function argerror -- @string name function to callout in error message -- @int i argument number -- @string[opt] extramsg additional text to append to message inside parentheses @@ -537,7 +534,6 @@ if _ARGCHECK then -- Normally, you should not need to use the `level` parameter, as the -- default is to blame the caller of the function using `argcheck` in -- error messages; which is almost certainly what you want. - -- @function argcheck -- @string name function to blame in error message -- @int i argument number to blame in error message -- @string expected specification for acceptable argument types @@ -715,7 +711,7 @@ end -- @int level call stack level to blame for the error -- @treturn string deprecation warning message, or empty string -- @usage --- io.stderr:write ("42", "multi-argument 'module.fname", 2) +-- io.stderr:write (DEPRECATIONMSG ("42", "multi-argument 'module.fname'", 2)) local function DEPRECATIONMSG (version, name, extramsg, level) if level == nil then level, extramsg = extramsg, nil end extramsg = extramsg or "and will be removed entirely in a future release" @@ -737,7 +733,8 @@ end -- @string[opt] extramsg additional warning text -- @func fn deprecated function -- @return a function to show the warning on first call, and hand off to *fn* --- @usage funcname = deprecate (function (...) ... end, "funcname") +-- @usage +-- M.op = DEPRECATED ("41", "'std.functional.op'", std.operator) local function DEPRECATED (version, name, extramsg, fn) if fn == nil then fn, extramsg = extramsg, nil end From bdf756556e839c181df93c5b2155a69dc0ac9cac Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 20:51:48 +0100 Subject: [PATCH 405/703] std: commit missed change to lib/std.lua.in. * lib/std.lua.in (X): Update export call to argscheck. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index d6a40d1..29e7e95 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -84,7 +84,7 @@ end -- @field version release version string local function X (decl, fn) - return require "std.debug".export ("std." .. decl, fn) + return require "std.debug".argscheck ("std." .. decl, fn) end M = { From e1c6da7d4c1e842938ff25c73d4581574eba1f6a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 21:23:00 +0100 Subject: [PATCH 406/703] refactor: modernize std/debug.lua. * lib/std/debug.lua: Reorder declarations and LDocs to match latest style. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 474 +++++++++++++++++++++++----------------------- 1 file changed, 242 insertions(+), 232 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index b2c013e..69918c4 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -41,18 +41,46 @@ local M ---- Control std.debug function behaviour. --- To activate debugging set _DEBUG either to any true value --- (equivalent to {level = 1}), or as documented below. --- @class table --- @name _DEBUG --- @tfield[opt=true] boolean argcheck honor argcheck and argscheck calls --- @tfield[opt=false] boolean call do call trace debugging --- @field[opt=nil] compat if `false`, always complain whenever a deprecated --- api is called; if `nil` complain on first use of each deprecated api; --- any other value disables deprecation warnings altogether --- @tfield[opt=1] int level debugging level --- @usage _DEBUG = { argcheck = false, level = 9 } +--- Determine whether *key* will show a deprecation warning on next access. +local compat = {} + +local function setcompat (key) + compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat == nil) or _DEBUG == true +end + + +local function getcompat (key) + if compat[key] == nil then + -- Whether to warn on first access. + compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat) or _DEBUG == false + end + return compat[key] +end + + +local function DEPRECATIONMSG (version, name, extramsg, level) + if level == nil then level, extramsg = extramsg, nil end + extramsg = extramsg or "and will be removed entirely in a future release" + + local _, where = pcall (function () error ("", level + 3) end) + if not getcompat (name) then + setcompat (name) + return (where .. string.format ("%s was deprecated in release %s, %s.\n", + name, tostring (version), extramsg)) + end + + return "" +end + + +local function DEPRECATED (version, name, extramsg, fn) + if fn == nil then fn, extramsg = extramsg, nil end + + return function (...) + io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) + return fn (...) + end +end --- Extend `debug.setfenv` to unwrap functables correctly. @@ -110,94 +138,6 @@ local getfenv = getfenv or function (fn) end ---- Print a debugging message to `io.stderr`. --- Display arguments passed through `std.tostring` and separated by tab --- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` --- is a number greater than or equal to *n*. If `_DEBUG` is false or --- nil, nothing is written. --- @int[opt=1] n debugging level, smaller is higher priority --- @param ... objects to print (as for print) --- @usage --- local _DEBUG = require "std.debug_init"._DEBUG --- _DEBUG.level = 3 --- say (2, "_DEBUG table contents:", _DEBUG) -local function say (n, ...) - local level = 1 - local arg = {n, ...} - if type (arg[1]) == "number" then - level = arg[1] - table.remove (arg, 1) - end - if _DEBUG and - ((type (_DEBUG) == "table" and type (_DEBUG.level) == "number" and - _DEBUG.level >= level) - or level <= 1) then - local t = {} - for k, v in pairs (arg) do t[k] = tostring (v) end - io.stderr:write (table.concat (t, "\t") .. "\n") - end -end - - ---- Trace function calls. --- Use as debug.sethook (trace, "cr"), which is done automatically --- when `_DEBUG.call` is set. --- Based on test/trace-calls.lua from the Lua distribution. --- @function trace --- @string event event causing the call --- @usage --- _DEBUG = { call = true } --- local debug = require "std.debug" - -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 = math.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 - io.stderr:write (s .. "\n") -end - --- Set hooks according to _DEBUG -if type (_DEBUG) == "table" and _DEBUG.call then - debug.sethook (trace, "cr") -end - - ---- Raise a bad argument error. --- Equivalent to luaL_argerror in the Lua C API. This function does not --- return. The `level` argument behaves just like the core `error` --- function. --- @string name function to callout in error message --- @int i argument number --- @string[opt] extramsg additional text to append to message inside parentheses --- @int[opt=1] level call stack level to blame for the error --- @usage --- local function slurp (file) --- local h, err = input_handle (file) --- if h == nil then argerror ("std.io.slurp", 1, err, 2) end --- ... local function argerror (name, i, extramsg, level) level = level or 1 local s = string.format ("bad argument #%d to '%s'", i, name) @@ -208,11 +148,6 @@ local function argerror (name, i, extramsg, level) end ---- Argument list length. --- Like #table, but does not stop at the first nil value. --- @tparam table t a table --- @treturn int largest integer key in *t* --- @usage tmax = arglen (t) local function arglen (t) local len = 0 for k in pairs (t) do @@ -222,11 +157,6 @@ local function arglen (t) end ---- Format a standard "too many arguments" error message. --- @string name function name --- @number expect maximum number of arguments accepted --- @number actual number of arguments received --- @treturn string standard "too many arguments" error message local function toomanyargmsg (name, expect, actual) local fmt = "too many arguments to '%s' (no more than %d expected, got %d)" return string.format (fmt, name, expect, actual) @@ -498,51 +428,6 @@ if _ARGCHECK then end - --- Check the type of an argument against expected types. - -- Equivalent to luaL_argcheck in the Lua C API. - -- - -- Call `argerror` if there is a type mismatch. - -- - -- Argument `actual` must match one of the types from in `expected`, each - -- of which can be the name of a primitive Lua type, a stdlib object type, - -- or one of the special options below: - -- - -- #table accept any non-empty table - -- any accept any non-nil argument type - -- file accept an open file object - -- function accept a function, or object with a __call metamethod - -- int accept an integer valued number - -- list accept a table where all keys are a contiguous 1-based integer range - -- #list accept any non-empty list - -- object accept any std.Object derived type - -- :foo accept only the exact string ":foo", works for any :-prefixed string - -- - -- The `:foo` format allows for type-checking of self-documenting - -- boolean-like constant string parameters predicated on `nil` versus - -- `:option` instead of `false` versus `true`. Or you could support - -- both: - -- - -- argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa) - -- - -- A very common pattern is to have a list of possible types including - -- "nil" when the argument is optional. Rather than writing long-hand - -- as above, append a question mark to at least one of the list types - -- and omit the explicit "nil" entry: - -- - -- argcheck ("table.copy", 2, "boolean|:nometa?", predicate) - -- - -- Normally, you should not need to use the `level` parameter, as the - -- default is to blame the caller of the function using `argcheck` in - -- error messages; which is almost certainly what you want. - -- @string name function to blame in error message - -- @int i argument number to blame in error message - -- @string expected specification for acceptable argument types - -- @param actual argument passed - -- @int[opt=2] level call stack level to blame for the error - -- @usage - -- local function case (with, branches) - -- argcheck ("std.functional.case", 2, "#table", branches) - -- ... function argcheck (name, i, expected, actual, level) level = level or 2 expected = normalize (split (expected, "|")) @@ -573,15 +458,6 @@ if _ARGCHECK then end - --- Wrap a function definition with argument type and arity checking. - -- In addition to checking that each argument type matches the corresponding - -- element in the *types* table with `argcheck`, if the final element of - -- *types* ends with an asterisk, remaining unchecked arguments are checked - -- against that type. - -- @string decl function type declaration string - -- @func inner function to wrap with argument checking - -- @usage - -- M.square = argscheck ("util.square (number)", function (n) return n * n end) function argscheck (decl, inner) -- Parse "fname (argtype, argtype, argtype...)". local fname, types = decl:match "([%w_][%.%d%w_]*)%s+%((.*)%)" @@ -675,88 +551,207 @@ else end --- Whether to show a deprecation warning the next time a give key is set. -local compat = {} - - ---- Determine whether *key* will show a deprecation warning on next access. --- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; --- if `_DEBUG.compat` is false, warn every time *fn* is called; --- otherwise don't write any warnings, and run *fn* normally. --- @param key unique identifier for a deprecated API. -local function setcompat (key) - compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat == nil) or _DEBUG == true -end - - ---- Get the deprecation warning status for *key*. --- @param key unique identifier for a deprecated API. --- @treturn boolean whether to show a deprecation warning. -local function getcompat (key) - if compat[key] == nil then - -- Whether to warn on first access. - compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat) or _DEBUG == false +local function say (n, ...) + local level = 1 + local arg = {n, ...} + if type (arg[1]) == "number" then + level = arg[1] + table.remove (arg, 1) + end + if _DEBUG and + ((type (_DEBUG) == "table" and type (_DEBUG.level) == "number" and + _DEBUG.level >= level) + or level <= 1) then + local t = {} + for k, v in pairs (arg) do t[k] = tostring (v) end + io.stderr:write (table.concat (t, "\t") .. "\n") end - return compat[key] end ---- Format a deprecation warning message. --- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; --- if `_DEBUG.compat` is false, warn every time *fn* is called; --- otherwise don't write any warnings, and run *fn* normally. --- @string version first deprecation release version --- @string name function name for automatic warning message --- @string[opt] extramsg additional warning text --- @int level call stack level to blame for the error --- @treturn string deprecation warning message, or empty string --- @usage --- io.stderr:write (DEPRECATIONMSG ("42", "multi-argument 'module.fname'", 2)) -local function DEPRECATIONMSG (version, name, extramsg, level) - if level == nil then level, extramsg = extramsg, nil end - extramsg = extramsg or "and will be removed entirely in a future release" +local level = 0 - local _, where = pcall (function () error ("", level + 3) end) - if not getcompat (name) then - setcompat (name) - return (where .. string.format ("%s was deprecated in release %s, %s.\n", - name, tostring (version), extramsg)) +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 - - return "" + t = debug.getinfo (2) + if event == "call" then + level = level + 1 + else + level = math.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 + io.stderr:write (s .. "\n") end - ---- Write a deprecation warning to stderr. --- @string version first deprecation release version --- @string name function name for automatic warning message --- @string[opt] extramsg additional warning text --- @func fn deprecated function --- @return a function to show the warning on first call, and hand off to *fn* --- @usage --- M.op = DEPRECATED ("41", "'std.functional.op'", std.operator) -local function DEPRECATED (version, name, extramsg, fn) - if fn == nil then fn, extramsg = extramsg, nil end - - return function (...) - io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) - return fn (...) - end +-- Set hooks according to _DEBUG +if type (_DEBUG) == "table" and _DEBUG.call then + debug.sethook (trace, "cr") end ---- @export M = { - DEPRECATED = DEPRECATED, + --- Write a deprecation warning to stderr. + -- @function DEPRECATED + -- @string version first deprecation release version + -- @string name function name for automatic warning message + -- @string[opt] extramsg additional warning text + -- @func fn deprecated function + -- @return a function to show the warning on first call, and hand off to *fn* + -- @usage + -- M.op = DEPRECATED ("41", "'std.functional.op'", std.operator) + DEPRECATED = DEPRECATED, + + --- Format a deprecation warning message. + -- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; + -- if `_DEBUG.compat` is false, warn every time *fn* is called; + -- otherwise don't write any warnings, and run *fn* normally. + -- @function DEPRECATIONMSG + -- @string version first deprecation release version + -- @string name function name for automatic warning message + -- @string[opt] extramsg additional warning text + -- @int level call stack level to blame for the error + -- @treturn string deprecation warning message, or empty string + -- @usage + -- io.stderr:write (DEPRECATIONMSG ("42", "multi-argument 'module.fname'", 2)) DEPRECATIONMSG = DEPRECATIONMSG, - argcheck = argcheck, - argerror = argerror, - arglen = arglen, - argscheck = argscheck, - say = say, - toomanyargmsg = toomanyargmsg, - trace = trace, + + --- Check the type of an argument against expected types. + -- Equivalent to luaL_argcheck in the Lua C API. + -- + -- Call `argerror` if there is a type mismatch. + -- + -- Argument `actual` must match one of the types from in `expected`, each + -- of which can be the name of a primitive Lua type, a stdlib object type, + -- or one of the special options below: + -- + -- #table accept any non-empty table + -- any accept any non-nil argument type + -- file accept an open file object + -- function accept a function, or object with a __call metamethod + -- int accept an integer valued number + -- list accept a table where all keys are a contiguous 1-based integer range + -- #list accept any non-empty list + -- object accept any std.Object derived type + -- :foo accept only the exact string ":foo", works for any :-prefixed string + -- + -- The `:foo` format allows for type-checking of self-documenting + -- boolean-like constant string parameters predicated on `nil` versus + -- `:option` instead of `false` versus `true`. Or you could support + -- both: + -- + -- argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa) + -- + -- A very common pattern is to have a list of possible types including + -- "nil" when the argument is optional. Rather than writing long-hand + -- as above, append a question mark to at least one of the list types + -- and omit the explicit "nil" entry: + -- + -- argcheck ("table.copy", 2, "boolean|:nometa?", predicate) + -- + -- Normally, you should not need to use the `level` parameter, as the + -- default is to blame the caller of the function using `argcheck` in + -- error messages; which is almost certainly what you want. + -- @function argcheck + -- @string name function to blame in error message + -- @int i argument number to blame in error message + -- @string expected specification for acceptable argument types + -- @param actual argument passed + -- @int[opt=2] level call stack level to blame for the error + -- @usage + -- local function case (with, branches) + -- argcheck ("std.functional.case", 2, "#table", branches) + -- ... + argcheck = argcheck, + + --- Raise a bad argument error. + -- Equivalent to luaL_argerror in the Lua C API. This function does not + -- return. The `level` argument behaves just like the core `error` + -- function. + -- @function argerror + -- @string name function to callout in error message + -- @int i argument number + -- @string[opt] extramsg additional text to append to message inside parentheses + -- @int[opt=1] level call stack level to blame for the error + -- @usage + -- local function slurp (file) + -- local h, err = input_handle (file) + -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end + -- ... + argerror = argerror, + + --- Argument list length. + -- Like #table, but does not stop at the first nil value. + -- @function arglen + -- @tparam table t a table + -- @treturn int largest integer key in *t* + -- @usage tmax = arglen {...} + arglen = arglen, + + --- Wrap a function definition with argument type and arity checking. + -- In addition to checking that each argument type matches the corresponding + -- element in the *types* table with `argcheck`, if the final element of + -- *types* ends with an asterisk, remaining unchecked arguments are checked + -- against that type. + -- @function argscheck + -- @string decl function type declaration string + -- @func inner function to wrap with argument checking + -- @usage + -- M.square = argscheck ("util.square (number)", function (n) return n * n end) + argscheck = argscheck, + + --- Print a debugging message to `io.stderr`. + -- Display arguments passed through `std.tostring` and separated by tab + -- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` + -- is a number greater than or equal to *n*. If `_DEBUG` 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_init"._DEBUG + -- _DEBUG.level = 3 + -- say (2, "_DEBUG table contents:", _DEBUG) + say = say, + + --- Format a standard "too many arguments" error message. + -- @function toomanyargmsg + -- @string name function name + -- @number expect maximum number of arguments accepted + -- @number actual number of arguments received + -- @treturn string standard "too many arguments" error message + -- @usage + -- if arglen {...} > 1 then + -- io.stderr:write ("module.fname", 7, arglen {...}) + -- ... + toomanyargmsg = toomanyargmsg, + + --- Trace function calls. + -- Use as debug.sethook (trace, "cr"), which is done automatically + -- when `_DEBUG.call` is set. + -- Based on test/trace-calls.lua from the Lua distribution. + -- @function trace + -- @string event event causing the call + -- @usage + -- _DEBUG = { call = true } + -- local debug = require "std.debug" + trace = trace, } @@ -777,3 +772,18 @@ local metatable = { } return setmetatable (M, metatable) + + + +--- Control std.debug function behaviour. +-- To activate debugging set _DEBUG either to any true value +-- (equivalent to {level = 1}), or as documented below. +-- @class table +-- @name _DEBUG +-- @tfield[opt=true] boolean argcheck honor argcheck and argscheck calls +-- @tfield[opt=false] boolean call do call trace debugging +-- @field[opt=nil] compat if `false`, always complain whenever a deprecated +-- api is called; if `nil` complain on first use of each deprecated api; +-- any other value disables deprecation warnings altogether +-- @tfield[opt=1] int level debugging level +-- @usage _DEBUG = { argcheck = false, level = 9 } From 5e54dd6cd2c584f763561588c4b7d86d347f809d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 22:13:24 +0100 Subject: [PATCH 407/703] functional: deprecate functional.op properly. * specs/functional_spec.yaml (op): Specify deprecation warnings when using old functional.op API. * lib/std/functional.lua (M.op): Deprecate old APIs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 11 +- lib/std/functional.lua | 21 +++- specs/functional_spec.yaml | 207 +++++++++++++++++++++++++++++++++---- 3 files changed, 212 insertions(+), 27 deletions(-) diff --git a/NEWS b/NEWS index 06c07fe..4af7be5 100644 --- a/NEWS +++ b/NEWS @@ -16,10 +16,10 @@ Stdlib NEWS - User visible changes - 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`, - `debug.argscheck` and changes `debug.export` to return its function - argument unwrapped, for production code. Similarly `_DEBUG = false` - deactivates these functions in the same way. + - 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.vector` object, for clean and fast queue-like or stack-like container management. When alien is installed, and element types @@ -156,6 +156,9 @@ Stdlib NEWS - User visible changes - `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 name now gives a deprecation warning. + - `list.depair` and `list.enpair` have been moved to `table.depair` and `table.enpair`, the old names now give deprecation warnings. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index fcc2add..b67bf09 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -525,9 +525,6 @@ local M = { } -M.op = operator -- for backwards compatibility - - --[[ ============= ]]-- --[[ Deprecations. ]]-- @@ -545,6 +542,24 @@ M.fold = DEPRECATED ("41", "'std.functional.fold'", "use 'std.functional.reduce' instead", reduce) +local function DEPRECATEOP (t, k) + return DEPRECATED ("41", "'std.functional.op[" .. k .. "]'", + "use 'std.functional.operator[" .. k .. "]' instead", t[k]) +end + +M.op = { + ["[]"] = DEPRECATEOP (operator, "[]"), + ["+"] = DEPRECATEOP (operator, "+"), + ["-"] = DEPRECATEOP (operator, "-"), + ["*"] = DEPRECATEOP (operator, "*"), + ["/"] = DEPRECATEOP (operator, "/"), + ["and"] = DEPRECATEOP (operator, "and"), + ["or"] = DEPRECATEOP (operator, "or"), + ["not"] = DEPRECATEOP (operator, "not"), + ["=="] = DEPRECATEOP (operator, "=="), + ["~="] = DEPRECATEOP (operator, "~="), +} + return M diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index f22604d..e811b33 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -266,6 +266,7 @@ specify std.functional: - describe fold: - before: + op = require "std.operator" f = M.fold - it writes a deprecation warning on first call: @@ -275,21 +276,22 @@ specify std.functional: not_to_contain_error "was deprecated" - it works with an empty table: - expect (f (M.op["+"], 2, ipairs, {})).to_be (2) + expect (f (op["+"], 2, ipairs, {})).to_be (2) - it calls a binary function over single return value iterator results: - expect (f (M.op["+"], 2, base.ielems, {3})). + expect (f (op["+"], 2, base.ielems, {3})). to_be (2 + 3) - expect (f (M.op["*"], 2, base.ielems, {3, 4})). + expect (f (op["*"], 2, base.ielems, {3, 4})). to_be (2 * 3 * 4) - it calls a binary function over key:value iterator results: - expect (f (M.op["+"], 2, ipairs, {3})).to_be (2 + 3) - expect (f (M.op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) + expect (f (op["+"], 2, ipairs, {3})).to_be (2 + 3) + expect (f (op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) - it folds elements from left to right: - expect (f (M.op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) - describe foldl: - before: + op = require "std.operator" f, badarg = init (M, this_module, "foldl") - it diagnoses missing arguments: @@ -303,17 +305,18 @@ specify std.functional: expect (f (f, 42, {}, false)).to_raise (badarg (4)) - it works with an empty table: - expect (f (M.op["+"], 10000, {})).to_be (10000) + expect (f (op["+"], 10000, {})).to_be (10000) - it folds a binary function through a table: - expect (f (M.op["+"], 10000, {1, 10, 100})).to_be (10111) + expect (f (op["+"], 10000, {1, 10, 100})).to_be (10111) - it folds from left to right: - expect (f (M.op["^"], 2, {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op["^"], 2, {3, 4})).to_be ((2 ^ 3) ^ 4) - it supports eliding init argument: - expect (f (M.op["^"], {2, 3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op["^"], {2, 3, 4})).to_be ((2 ^ 3) ^ 4) - describe foldr: - before: + op = require "std.operator" f, badarg = init (M, this_module, "foldr") - it diagnoses missing arguments: @@ -327,13 +330,13 @@ specify std.functional: expect (f (f, 42, {}, false)).to_raise (badarg (4)) - it works with an empty table: - expect (f (M.op["+"], 1, {})).to_be (1) + expect (f (op["+"], 1, {})).to_be (1) - it folds a binary function through a table: - expect (f (M.op["+"], {10000, 100, 10, 1})).to_be (10111) + expect (f (op["+"], {10000, 100, 10, 1})).to_be (10111) - it folds from right to left: - expect (f (M.op["/"], 10, {10000, 100})).to_be (10000 / (100 / 10)) + expect (f (op["/"], 10, {10000, 100})).to_be (10000 / (100 / 10)) - it supports eliding init argument: - expect (f (M.op["/"], {10000, 100, 10})).to_be (10000 / (100 / 10)) + expect (f (op["/"], {10000, 100, 10})).to_be (10000 / (100 / 10)) - describe id: @@ -513,8 +516,172 @@ specify std.functional: expect (f (1, "two", false)).to_be (nil) +- describe op: + - context with []: + - before: + f = M.op["[]"] + + - it writes a deprecation warning on first call: + expect (capture (f, {{2}, 1})). + to_contain_error "was deprecated" + expect (capture (f, {{2}, 1})). + not_to_contain_error "was deprecated" + + - it dereferences a table: + expect (f ({}, 1)).to_be (nil) + expect (f ({"foo", "bar"}, 1)).to_be "foo" + expect (f ({foo = "bar"}, "foo")).to_be "bar" + + - context with +: + - before: + f = M.op["+"] + + - it writes a deprecation warning on first call: + expect (capture (f, {2, 1})). + to_contain_error "was deprecated" + expect (capture (f, {2, 1})). + not_to_contain_error "was deprecated" + + - it returns the sum of its arguments: + expect (f (99, 2)).to_be (99 + 2) + + - context with -: + - before: + f = M.op["-"] + + - it writes a deprecation warning on first call: + expect (capture (f, {2, 1})). + to_contain_error "was deprecated" + expect (capture (f, {2, 1})). + not_to_contain_error "was deprecated" + + - it returns the difference of its arguments: + expect (f (99, 2)).to_be (99 - 2) + + - context with *: + - before: + f = M.op["*"] + + - it writes a deprecation warning on first call: + expect (capture (f, {2, 1})). + to_contain_error "was deprecated" + expect (capture (f, {2, 1})). + not_to_contain_error "was deprecated" + + - it returns the product of its arguments: + expect (f (99, 2)).to_be (99 * 2) + + - context with /: + - before: + f = M.op["/"] + + - it writes a deprecation warning on first call: + expect (capture (f, {2, 1})). + to_contain_error "was deprecated" + expect (capture (f, {2, 1})). + not_to_contain_error "was deprecated" + + - it returns the quotient of its arguments: + expect (f (99, 2)).to_be (99 / 2) + + - context with and: + - before: + f = M.op["and"] + + - it writes a deprecation warning on first call: + expect (capture (f, {true, false})). + to_contain_error "was deprecated" + expect (capture (f, {true, false})). + not_to_contain_error "was deprecated" + + - it returns the logical and of its arguments: + expect (f (false, false)).to_be (false) + expect (f (false, true)).to_be (false) + expect (f (true, false)).to_be (false) + expect (f (true, true)).to_be (true) + - it supports truthy and falsey arguments: + expect (f ()).to_be (nil) + expect (f (0)).to_be (nil) + expect (f (nil, 0)).to_be (nil) + expect (f (0, "false")).to_be ("false") + + - context with or: + - before: + f = M.op["or"] + + - it writes a deprecation warning on first call: + expect (capture (f, {true, false})). + to_contain_error "was deprecated" + expect (capture (f, {true, false})). + not_to_contain_error "was deprecated" + + - it returns the logical or of its arguments: + expect (f (false, false)).to_be (false) + expect (f (false, true)).to_be (true) + expect (f (true, false)).to_be (true) + expect (f (true, true)).to_be (true) + - it supports truthy and falsey arguments: + expect (f ()).to_be (nil) + expect (f (0)).to_be (0) + expect (f (nil, 0)).to_be (0) + expect (f (0, "false")).to_be (0) + + - context with not: + - before: + f = M.op["not"] + + - it writes a deprecation warning on first call: + expect (capture (f, {true})). + to_contain_error "was deprecated" + expect (capture (f, {true})). + not_to_contain_error "was deprecated" + + - it returns the logical not of its argument: + expect (f (false)).to_be (true) + expect (f (true)).to_be (false) + - it supports truthy and falsey arguments: + expect (f ()).to_be (true) + expect (f (0)).to_be (false) + + - context with ==: + - before: + f = M.op["=="] + + - it writes a deprecation warning on first call: + expect (capture (f, {2, 1})). + to_contain_error "was deprecated" + expect (capture (f, {2, 1})). + not_to_contain_error "was deprecated" + + - it returns true if the arguments are equal: + expect (f ()).to_be (true) + expect (f ("foo", "foo")).to_be (true) + - it returns false if the arguments are unequal: + expect (f (1)).to_be (false) + expect (f ("foo", "bar")).to_be (false) + + - context with ~=: + - before: + f = M.op["~="] + + - it writes a deprecation warning on first call: + expect (capture (f, {2, 1})). + to_contain_error "was deprecated" + expect (capture (f, {2, 1})). + not_to_contain_error "was deprecated" + + - it returns false if the arguments are equal: + expect (f (1, 1)).to_be (false) + expect (f ("foo", "foo")).to_be (false) + - it returns true if the arguments are unequal: + expect (f (1, 2)).to_be (true) + expect (f ("foo", "bar")).to_be (true) + expect (f ({}, {})).to_be (true) + + - describe reduce: - before: + op = require "std.operator" f, badarg = init (M, this_module, "reduce") - it diagnoses missing arguments: @@ -526,17 +693,17 @@ specify std.functional: expect (f (f, 1, false)).to_raise (badarg (3, "function", "boolean")) - it works with an empty table: - expect (f (M.op["+"], 2, ipairs, {})).to_be (2) + expect (f (op["+"], 2, ipairs, {})).to_be (2) - it calls a binary function over single return value iterator results: - expect (f (M.op["+"], 2, base.ielems, {3})). + expect (f (op["+"], 2, base.ielems, {3})). to_be (2 + 3) - expect (f (M.op["*"], 2, base.ielems, {3, 4})). + expect (f (op["*"], 2, base.ielems, {3, 4})). to_be (2 * 3 * 4) - it calls a binary function over key:value iterator results: - expect (f (M.op["+"], 2, ipairs, {3})).to_be (2 + 3) - expect (f (M.op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) + expect (f (op["+"], 2, ipairs, {3})).to_be (2 + 3) + expect (f (op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) - it reduces elements from left to right: - expect (f (M.op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) - describe zip: From e7d3d9c0039afd65dcd45a4d75f29d91e2518be1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Aug 2014 22:52:40 +0100 Subject: [PATCH 408/703] operator: use non-RSI inducing operator function names. * lib/std/operator.lua ([".."], ["[]"], ["{}"], ["#"], ["+"]) (["-"], ["*"], ["/"], ["%"], ["^"], ["=="], ["~="], ["<"], ["<="]) ([">"], [">="]): Rename from these... (concat, deref, cons, length, sum, diff, prod, quot, mod, pow, eq) (neq, lt, lte, gt, gte): ...to these. (['""']): Remove. Just pass the tostring function. (["~"]): Remove. Just pass string.find. Adjust all callers. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 14 +++--- lib/std/functional.lua | 26 +++++------ lib/std/operator.lua | 70 ++++++++++++++--------------- lib/std/table.lua | 2 +- lib/std/tree.lua | 2 +- specs/functional_spec.yaml | 40 ++++++++--------- specs/list_spec.yaml | 52 +++++++++++----------- specs/operator_spec.yaml | 91 ++++++++++++++++---------------------- 8 files changed, 138 insertions(+), 159 deletions(-) diff --git a/NEWS b/NEWS index 4af7be5..5a7d8ff 100644 --- a/NEWS +++ b/NEWS @@ -21,16 +21,18 @@ Stdlib NEWS - User visible changes for production code. Similarly `_DEBUG = false` deactivates these functions in the same way. + - New `std.operator` module, with easier to type operator names (`deref`, + `diff`, `eq`, `neq`, `prod`, `quot`, and `sum`), plus brand new + functional operators for concatenation `concat`, table construction + `cons`, list length `length`; plus new mathematical operators `mod`, + and `pow`; and relational operators `lt`, `lte`, `gt` and `gte`. + The `length` operator respects the `__len` metamethod, if any, even on + Lua 5.1. + - New `std.vector` object, for clean and fast queue-like or stack-like container management. When alien is installed, and element types are compatible, uses alien.buffers for efficient element management. - - New `std.operator` module, with new functional operators for - concatenation `..`, tablification `{}`, stringification `""`, length - `#` and matching `~`, plus new mathematical operators `%` and `^`, and - relational operators, `<`, `<=`, `>` and `>=`. The `#` operator - respects the `__len` metamethod, if any, even on Lua 5.1. - - `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 diff --git a/lib/std/functional.lua b/lib/std/functional.lua index b67bf09..8598aee 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -542,22 +542,22 @@ M.fold = DEPRECATED ("41", "'std.functional.fold'", "use 'std.functional.reduce' instead", reduce) -local function DEPRECATEOP (t, k) - return DEPRECATED ("41", "'std.functional.op[" .. k .. "]'", - "use 'std.functional.operator[" .. k .. "]' instead", t[k]) +local function DEPRECATEOP (t, old, new) + return DEPRECATED ("41", "'std.functional.op[" .. old .. "]'", + "use 'std.functional.operator." .. new .. "' instead", t[new]) end M.op = { - ["[]"] = DEPRECATEOP (operator, "[]"), - ["+"] = DEPRECATEOP (operator, "+"), - ["-"] = DEPRECATEOP (operator, "-"), - ["*"] = DEPRECATEOP (operator, "*"), - ["/"] = DEPRECATEOP (operator, "/"), - ["and"] = DEPRECATEOP (operator, "and"), - ["or"] = DEPRECATEOP (operator, "or"), - ["not"] = DEPRECATEOP (operator, "not"), - ["=="] = DEPRECATEOP (operator, "=="), - ["~="] = DEPRECATEOP (operator, "~="), + ["[]"] = DEPRECATEOP (operator, "[]", "deref"), + ["+"] = DEPRECATEOP (operator, "+", "sum"), + ["-"] = DEPRECATEOP (operator, "-", "diff"), + ["*"] = DEPRECATEOP (operator, "*", "prod"), + ["/"] = DEPRECATEOP (operator, "/", "quot"), + ["and"] = DEPRECATEOP (operator, "and", "and"), + ["or"] = DEPRECATEOP (operator, "or", "or"), + ["not"] = DEPRECATEOP (operator, "not", "not"), + ["=="] = DEPRECATEOP (operator, "==", "eq"), + ["~="] = DEPRECATEOP (operator, "~=", "neq"), } return M diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 9e3419a..1a281f3 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -10,52 +10,46 @@ local base = require "std.base" --- Functional forms of Lua operators. -- --- Defined here so that other modules can write to it. --- --- 1. `..`: concatenation --- 1. `[]`: dereference a table --- 1. `{}`: tablification --- 1. `""`: stringification --- 1. `~`: string matching --- 1. `#`: table or string length --- 1. `+`: addition --- 1. `-`: subtraction --- 1. `*`: multiplication --- 1. `/`: division --- 1. `%`: modulo --- 1. `^`: exponentiation +-- 1. `concat`: concatenation +-- 1. `deref`: dereference a table +-- 1. `cons`: tablification +-- 1. `length`: table or string length +-- 1. `sum`: addition +-- 1. `diff`: subtraction +-- 1. `prod`: multiplication +-- 1. `quot`: division +-- 1. `mod`: modulo +-- 1. `pow`: exponentiation -- 1. `and`: logical and -- 1. `or`: logical or -- 1. `not`: logical not --- 1. `==`: equality --- 1. `~=`: inequality --- 1. `<`: less than --- 1. `<=`: less than or equal --- 1. `>`: greater than --- 1. `>=`: greater than or equal +-- 1. `eq`: equality +-- 1. `neq`: inequality +-- 1. `lt`: less than +-- 1. `lte`: less than or equal +-- 1. `gt`: greater than +-- 1. `gte`: greater than or equal -- @table std.operator --- return { - [".."] = function (a, b) return tostring (a) .. tostring (b) end, - ["[]"] = function (t, s) return t and t[s] or nil end, - ["{}"] = function (...) return {...} end, - ['""'] = function (x) return tostring (x) end, - ["~"] = function (s, p) return string.find (s, p) end, - ["#"] = base.len, - ["+"] = function (a, b) return a + b end, - ["-"] = function (a, b) return a - b end, - ["*"] = function (a, b) return a * b end, - ["/"] = function (a, b) return a / b end, - ["%"] = function (a, b) return a % b end, - ["^"] = function (a, b) return a ^ b end, + concat = function (a, b) return tostring (a) .. tostring (b) end, + deref = function (t, s) return t and t[s] or nil end, + cons = function (...) return {...} end, + length = base.len, + sum = function (a, b) return a + b end, + diff = function (a, b) return a - b end, + prod = function (a, b) return a * b end, + quot = function (a, b) return a / b end, + mod = function (a, b) return a % b end, + pow = function (a, b) return a ^ b end, ["and"] = function (a, b) return a and b end, ["or"] = function (a, b) return a or b end, ["not"] = function (a) return not a end, - ["=="] = function (a, b) return a == b end, - ["~="] = function (a, b) return a ~= b end, - ["<"] = function (a, b) return a < b end, - ["<="] = function (a, b) return a <= b end, - [">"] = function (a, b) return a > b end, - [">="] = function (a, b) return a >= b end, + eq = function (a, b) return a == b end, + neq = function (a, b) return a ~= b end, + lt = function (a, b) return a < b end, + lte = function (a, b) return a <= b end, + gt = function (a, b) return a > b end, + gte = function (a, b) return a >= b end, } diff --git a/lib/std/table.lua b/lib/std/table.lua index ab9b90e..6b21427 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -361,7 +361,7 @@ M = { --- Make table.sort return its result. -- @tparam table t unsorted table - -- @tparam[opt=std.operator["<"]] comparator c ordering function callback + -- @tparam[opt=std.operator.lt] comparator c ordering function callback -- lua `<` operator -- @return *t* with keys sorted accordind to *c* -- @usage table.concat (sort (object)) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 8678142..cb633f0 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -227,7 +227,7 @@ Tree = Container { -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? __index = function (self, i) if prototype (i) == "table" then - return reduce (operator["[]"], self, ielems, i) + return reduce (operator.deref, self, ielems, i) else return rawget (self, i) end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index e811b33..8ebec3e 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -276,17 +276,17 @@ specify std.functional: not_to_contain_error "was deprecated" - it works with an empty table: - expect (f (op["+"], 2, ipairs, {})).to_be (2) + expect (f (op.sum, 2, ipairs, {})).to_be (2) - it calls a binary function over single return value iterator results: - expect (f (op["+"], 2, base.ielems, {3})). + expect (f (op.sum, 2, base.ielems, {3})). to_be (2 + 3) - expect (f (op["*"], 2, base.ielems, {3, 4})). + expect (f (op.prod, 2, base.ielems, {3, 4})). to_be (2 * 3 * 4) - it calls a binary function over key:value iterator results: - expect (f (op["+"], 2, ipairs, {3})).to_be (2 + 3) - expect (f (op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) + expect (f (op.sum, 2, ipairs, {3})).to_be (2 + 3) + expect (f (op.prod, 2, ipairs, {3, 4})).to_be (2 * 3 * 4) - it folds elements from left to right: - expect (f (op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op.pow, 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) - describe foldl: @@ -305,13 +305,13 @@ specify std.functional: expect (f (f, 42, {}, false)).to_raise (badarg (4)) - it works with an empty table: - expect (f (op["+"], 10000, {})).to_be (10000) + expect (f (op.sum, 10000, {})).to_be (10000) - it folds a binary function through a table: - expect (f (op["+"], 10000, {1, 10, 100})).to_be (10111) + expect (f (op.sum, 10000, {1, 10, 100})).to_be (10111) - it folds from left to right: - expect (f (op["^"], 2, {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op.pow, 2, {3, 4})).to_be ((2 ^ 3) ^ 4) - it supports eliding init argument: - expect (f (op["^"], {2, 3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op.pow, {2, 3, 4})).to_be ((2 ^ 3) ^ 4) - describe foldr: @@ -330,13 +330,13 @@ specify std.functional: expect (f (f, 42, {}, false)).to_raise (badarg (4)) - it works with an empty table: - expect (f (op["+"], 1, {})).to_be (1) + expect (f (op.sum, 1, {})).to_be (1) - it folds a binary function through a table: - expect (f (op["+"], {10000, 100, 10, 1})).to_be (10111) + expect (f (op.sum, {10000, 100, 10, 1})).to_be (10111) - it folds from right to left: - expect (f (op["/"], 10, {10000, 100})).to_be (10000 / (100 / 10)) + expect (f (op.quot, 10, {10000, 100})).to_be (10000 / (100 / 10)) - it supports eliding init argument: - expect (f (op["/"], {10000, 100, 10})).to_be (10000 / (100 / 10)) + expect (f (op.quot, {10000, 100, 10})).to_be (10000 / (100 / 10)) - describe id: @@ -693,17 +693,17 @@ specify std.functional: expect (f (f, 1, false)).to_raise (badarg (3, "function", "boolean")) - it works with an empty table: - expect (f (op["+"], 2, ipairs, {})).to_be (2) + expect (f (op.sum, 2, ipairs, {})).to_be (2) - it calls a binary function over single return value iterator results: - expect (f (op["+"], 2, base.ielems, {3})). + expect (f (op.sum, 2, base.ielems, {3})). to_be (2 + 3) - expect (f (op["*"], 2, base.ielems, {3, 4})). + expect (f (op.prod, 2, base.ielems, {3, 4})). to_be (2 * 3 * 4) - it calls a binary function over key:value iterator results: - expect (f (op["+"], 2, ipairs, {3})).to_be (2 + 3) - expect (f (op["*"], 2, ipairs, {3, 4})).to_be (2 * 3 * 4) + expect (f (op.sum, 2, ipairs, {3})).to_be (2 + 3) + expect (f (op.prod, 2, ipairs, {3, 4})).to_be (2 * 3 * 4) - it reduces elements from left to right: - expect (f (op["^"], 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op.pow, 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) - describe zip: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index fc6353a..3c1037a 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -493,45 +493,45 @@ specify std.list: f = M.foldl - it writes a deprecation warning on first call: - expect (capture (f, {op["+"], 1, l})). + expect (capture (f, {op.sum, 1, l})). to_contain_error "was deprecated" - expect (capture (f, {op["+"], 1, l})). + expect (capture (f, {op.sum, 1, l})). not_to_contain_error "was deprecated" - context with a table: - it works with an empty table: - expect (f (op["+"], 10000, {})).to_be (10000) + expect (f (op.sum, 10000, {})).to_be (10000) - it folds a binary function through a table: - expect (f (op["+"], 10000, {1, 10, 100})).to_be (10111) + expect (f (op.sum, 10000, {1, 10, 100})).to_be (10111) - it folds from left to right: - expect (f (op["^"], 2, {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op.pow, 2, {3, 4})).to_be ((2 ^ 3) ^ 4) - context with a List: - it works with an empty List: - expect (f (op["+"], 10000, List {})).to_be (10000) + expect (f (op.sum, 10000, List {})).to_be (10000) - it folds a binary function through a List: - expect (f (op["+"], 10000, List {1, 10, 100})). + expect (f (op.sum, 10000, List {1, 10, 100})). to_be (10111) - it folds from left to right: - expect (f (op["^"], 2, List {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op.pow, 2, List {3, 4})).to_be ((2 ^ 3) ^ 4) - context as an object method: - before: f = l.foldl - it writes a deprecation warning on first call: - expect (capture (f, {l, op["+"], 1})). + expect (capture (f, {l, op.sum, 1})). to_contain_error "was deprecated" - expect (capture (f, {l, op["+"], 1})). + expect (capture (f, {l, op.sum, 1})). not_to_contain_error "was deprecated" - it works with an empty List: l = List {} - expect (f (l, op["+"], 2)).to_be (2) + expect (f (l, op.sum, 2)).to_be (2) - it folds a binary function through a List: - expect (f (l, op["+"], 2)).to_be (9) + expect (f (l, op.sum, 2)).to_be (9) - it folds from left to right: - expect (f (l, op["^"], 2)).to_be ((2 ^ 3) ^ 4) + expect (f (l, op.pow, 2)).to_be ((2 ^ 3) ^ 4) - describe foldr: @@ -544,27 +544,27 @@ specify std.list: f = M.foldr - it writes a deprecation warning on first call: - expect (capture (f, {op["+"], 1, {10}})). + expect (capture (f, {op.sum, 1, {10}})). to_contain_error "was deprecated" - expect (capture (f, {op["+"], 1, {10}})). + expect (capture (f, {op.sum, 1, {10}})). not_to_contain_error "was deprecated" - context with a table: - it works with an empty table: - expect (f (op["+"], 10000, {})).to_be (10000) + expect (f (op.sum, 10000, {})).to_be (10000) - it folds a binary function through a table: - expect (f (op["+"], 10000, {1, 10, 100})).to_be (10111) + expect (f (op.sum, 10000, {1, 10, 100})).to_be (10111) - it folds from right to left: - expect (f (op["/"], 10, {10000, 100})).to_be (10000 / (100 / 10)) + expect (f (op.quot, 10, {10000, 100})).to_be (10000 / (100 / 10)) - context with a List: - it works with an empty List: - expect (f (op["+"], 10000, List {})).to_be (10000) + expect (f (op.sum, 10000, List {})).to_be (10000) - it folds a binary function through a List: - expect (f (op["+"], 10000, List {1, 10, 100})). + expect (f (op.sum, 10000, List {1, 10, 100})). to_be (10111) - it folds from right to left: - expect (f (op["/"], 10, List {10000, 100})). + expect (f (op.quot, 10, List {10000, 100})). to_be (10000 / (100 / 10)) - context as an object method: @@ -572,18 +572,18 @@ specify std.list: f = l.foldr - it writes a deprecation warning on first call: - expect (capture (f, {l, op["+"], 1})). + expect (capture (f, {l, op.sum, 1})). to_contain_error "was deprecated" - expect (capture (f, {l, op["+"], 1})). + expect (capture (f, {l, op.sum, 1})). not_to_contain_error "was deprecated" - it works with an empty List: l = List {} - expect (f (l, op["+"], 10)).to_be (10) + expect (f (l, op.sum, 10)).to_be (10) - it folds a binary function through a List: - expect (f (l, op["+"], 10)).to_be (10110) + expect (f (l, op.sum, 10)).to_be (10110) - it folds from right to left: - expect (f (l, op["/"], 10)).to_be (10000 / (100 / 10)) + expect (f (l, op.quot, 10)).to_be (10000 / (100 / 10)) - describe index_key: diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index 30794cb..b612bd9 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -17,9 +17,9 @@ specify std.operator: to_equal {} -- describe ..: +- describe concat: - before: - f = M[".."] + f = M.concat - it stringifies its arguments: expect (f (1, "")).to_be "1" @@ -27,18 +27,18 @@ specify std.operator: - it concatenates its arguments: expect (f (1, 2)).to_be "12" -- describe []: +- describe deref: - before: - f = M["[]"] + f = M.deref - it dereferences a table: expect (f ({}, 1)).to_be (nil) expect (f ({"foo", "bar"}, 1)).to_be "foo" expect (f ({foo = "bar"}, "foo")).to_be "bar" -- describe {}: +- describe cons: - before: - f = M["{}"] + f = M.cons - it packs its arguments into a table: expect (f ()).to_equal {} @@ -46,26 +46,9 @@ specify std.operator: expect (f ("foo", "bar")).to_equal {"foo", "bar"} expect (f ("a", "b", "c", 1, 2, 3)).to_equal {"a", "b", "c", 1, 2, 3} -- describe "": +- describe length: - before: - f = M['""'] - - - it stringifies its argument: - expect (f ()).to_be "nil" - expect (f (42)).to_be "42" - expect (f ("foo")).to_be "foo" - -- describe ~: - - before: - f = M["~"] - - - it finds a pattern match in a string: - haystack = "foo bar baz" - expect (haystack:sub (f (haystack, "ba."))).to_be "bar" - -- 'describe #': - - before: - f = M["#"] + f = M.length - it returns the length of a string: expect (f "1234567890").to_be (10) @@ -74,44 +57,44 @@ specify std.operator: - it uses the __len metamethod: expect (f (setmetatable ({}, { __len = function () return 42 end }))).to_be (42) -- describe +: +- describe sum: - before: - f = M["+"] + f = M.sum - it returns the sum of its arguments: expect (f (99, 2)).to_be (99 + 2) -- describe -: +- describe diff: - before: - f = M["-"] + f = M.diff - it returns the difference of its arguments: expect (f (99, 2)).to_be (99 - 2) -- describe *: +- describe prod: - before: - f = M["*"] + f = M.prod - it returns the product of its arguments: expect (f (99, 2)).to_be (99 * 2) -- describe /: +- describe quot: - before: - f = M["/"] + f = M.quot - it returns the quotient of its arguments: expect (f (99, 2)).to_be (99 / 2) -- describe %: +- describe mod: - before: - f = M["%"] + f = M.mod - it returns the modulus of its arguments: expect (f (99, 2)).to_be (99 % 2) -- describe ^: +- describe pow: - before: - f = M["^"] + f = M.pow - it returns the power of its arguments: expect (f (99, 2)).to_be (math.pow (99, 2)) @@ -157,9 +140,9 @@ specify std.operator: expect (f ()).to_be (true) expect (f (0)).to_be (false) -- describe ==: +- describe eq: - before: - f = M["=="] + f = M.eq - it returns true if the arguments are equal: expect (f ()).to_be (true) @@ -168,21 +151,21 @@ specify std.operator: expect (f (1)).to_be (false) expect (f ("foo", "bar")).to_be (false) -- describe ~=: +- describe neq: - before: - f = M["=="] + f = M.neq - it returns false if the arguments are equal: - expect (f (1)).to_be (false) - expect (f ("foo", "bar")).to_be (false) - expect (f ({}, {})).to_be (false) + expect (f (1, 1)).to_be (false) + expect (f ("foo", "foo")).to_be (false) - it returns true if the arguments are unequal: - expect (f ()).to_be (true) - expect (f ("foo", "foo")).to_be (true) + expect (f (1)).to_be (true) + expect (f ("foo", "bar")).to_be (true) + expect (f ({}, {})).to_be (true) -- describe <: +- describe lt: - before: - f = M["<"] + f = M.lt - it returns true if the arguments are in ascending order: expect (f (1, 2)).to_be (true) @@ -198,9 +181,9 @@ specify std.operator: expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (false) expect (f (List {1, 2, 4}, List {1, 2, 3})).to_be (false) -- describe <=: +- describe lte: - before: - f = M["<="] + f = M.lte - it returns true if the arguments are not in descending order: expect (f (1, 2)).to_be (true) @@ -216,9 +199,9 @@ specify std.operator: expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (true) expect (f (List {1, 2, 4}, List {1, 2, 3})).to_be (false) -- describe >: +- describe gt: - before: - f = M[">"] + f = M.gt - it returns true if the arguments are in descending order: expect (f (2, 1)).to_be (true) @@ -234,9 +217,9 @@ specify std.operator: expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (false) expect (f (List {1, 2, 3}, List {1, 2, 4})).to_be (false) -- describe >=: +- describe gte: - before: - f = M[">="] + f = M.gte - it returns true if the arguments are not in ascending order: expect (f (2, 1)).to_be (true) From e04256458588fad288e1b75c1107e70ad802ce2c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 14:03:54 +0100 Subject: [PATCH 409/703] refactor: remove arglen, duplicates table.maxn functionality. * specs/debug_spec.yaml (arglen): Remove specifications. * lib/std/debug.lua (arglen): Remove. * lib/std/container.lua (M.__call), lib/std/debug.lua (match) (argcheck): Change all callers to use table.maxn instead. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 5 +++-- lib/std/debug.lua | 26 +++++--------------------- specs/debug_spec.yaml | 16 +--------------- 3 files changed, 9 insertions(+), 38 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index cc210eb..66944a0 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -73,6 +73,7 @@ local debug = require "std.debug" local ipairs, pairs = base.ipairs, base.pairs local prototype = base.prototype local argcheck = debug.argcheck +local maxn = table.maxn @@ -242,7 +243,7 @@ local M = { if _ARGCHECK then - local arglen, toomanyargmsg = debug.arglen, debug.toomanyargmsg + local toomanyargmsg = debug.toomanyargmsg M.__call = function (self, x, ...) local mt = getmetatable (self) @@ -255,7 +256,7 @@ if _ARGCHECK then -- it just refers back to the object being called: `Container {"x"}. argcheck (name, 1, "table", x) if next (argt) then - error (toomanyargmsg (name, 1, 1 + arglen (argt)), 2) + error (toomanyargmsg (name, 1, 1 + maxn (argt)), 2) end end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 69918c4..0b7d374 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -35,6 +35,7 @@ local base = require "std.base" local _ARGCHECK = debug_init._ARGCHECK local _DEBUG = debug_init._DEBUG local callable = base.callable +local maxn = table.maxn local split, tostring = base.split, base.tostring local M @@ -148,15 +149,6 @@ local function argerror (name, i, extramsg, level) end -local function arglen (t) - local len = 0 - for k in pairs (t) do - if type (k) == "number" and k > len then len = k end - end - return len -end - - local function toomanyargmsg (name, expect, actual) local fmt = "too many arguments to '%s' (no more than %d expected, got %d)" return string.format (fmt, name, expect, actual) @@ -297,7 +289,7 @@ if _ARGCHECK then -- @tparam boolean allargs whether to match all arguments -- @treturn int|nil position of first mismatch in *types* local function match (types, args, allargs) - local typec, argc = #types, arglen (args) + local typec, argc = #types, maxn (args) for i = 1, typec do local ok = pcall (argcheck, "pcall", i, types[i], args[i]) if not ok then return i end @@ -485,7 +477,7 @@ if _ARGCHECK then return function (...) local args = {...} - local argc, bestmismatch, at = arglen (args), 0, 0 + local argc, bestmismatch, at = maxn (args), 0, 0 for i, types in ipairs (type_specs) do local mismatch = match (types, args, max == math.huge) @@ -696,14 +688,6 @@ M = { -- ... argerror = argerror, - --- Argument list length. - -- Like #table, but does not stop at the first nil value. - -- @function arglen - -- @tparam table t a table - -- @treturn int largest integer key in *t* - -- @usage tmax = arglen {...} - arglen = arglen, - --- Wrap a function definition with argument type and arity checking. -- In addition to checking that each argument type matches the corresponding -- element in the *types* table with `argcheck`, if the final element of @@ -737,8 +721,8 @@ M = { -- @number actual number of arguments received -- @treturn string standard "too many arguments" error message -- @usage - -- if arglen {...} > 1 then - -- io.stderr:write ("module.fname", 7, arglen {...}) + -- if table.maxn {...} > 1 then + -- io.stderr:write ("module.fname", 7, table.maxn {...}) -- ... toomanyargmsg = toomanyargmsg, diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 5d2744d..8190ca4 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,7 +4,7 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "arglen", "argscheck", "say", "toomanyargmsg", "trace" } + "argscheck", "say", "toomanyargmsg", "trace" } M = require (this_module) @@ -174,20 +174,6 @@ specify std.debug: expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" -- describe arglen: - - before: - f = M.arglen - - - it returns the length of a table: - expect (f {1, 2, 5}).to_be (3) - - it works with an empty table: - expect (f {}).to_be (0) - - it ignores the hash part of a table: - expect (f {1, x=2, y=3, 4, 5}).to_be (3) - - it counts nil values in the array part of a table: - expect (f {1, nil, 2, 5}).to_be (4) - - - describe argcheck: - before: | Object = require 'std.object' From 4ca1df881598cea9e0f23f15c2e429d07d56a0a7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 16:21:45 +0100 Subject: [PATCH 410/703] refactor: assorted simplifications to std.table. * lib/std/table.lua (merge_allfields): Use `nil` for unspecified `map` argument, and when `nil` use a faster inner loop for copying. (merge_namedfields): Use `nil` for unspecified `keys` argument. (clone): Unroll into export table. (depair, keys): Use ipairs and dummy variable, rather than ielems. (pack): Remove duplicate definition. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index 6b21427..5d8c2de 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -16,52 +16,45 @@ local debug = require "std.debug" local collect = base.collect local leaves = base.leaves -local ielems, ipairs, pairs = base.ielems, base.ipairs, base.pairs +local ipairs, pairs = base.ipairs, base.pairs local M local function merge_allfields (t, u, map, nometa) - map = map or {} if type (map) ~= "table" then - map, nometa = {}, map + map, nometa = nil, map end if not nometa then setmetatable (t, getmetatable (u)) end - for k, v in pairs (u) do - t[map[k] or k] = v + 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) - keys = keys or {} if type (keys) ~= "table" then - keys, nometa = {}, keys + keys, nometa = nil, keys end if not nometa then setmetatable (t, getmetatable (u)) end - for _, k in ipairs (keys) do - t[k] = u[k] - end + for _, k in pairs (keys or {}) do t[k] = u[k] end return t end -local function clone (...) - return merge_allfields ({}, ...) -end - - local function depair (ls) local t = {} - for v in ielems (ls) do + for _, v in ipairs (ls) do t[v[1]] = v[2] end return t @@ -93,7 +86,7 @@ end local function keys (t) local l = {} - for k, _ in pairs (t) do + for k in pairs (t) do l[#l + 1] = k end return l @@ -109,11 +102,6 @@ local function new (x, t) end -local function pack (...) - return {...} -end - - local function project (fkey, tt) local r = {} for _, t in ipairs (tt) do @@ -233,7 +221,8 @@ M = { -- @see clone_select -- @usage -- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") - clone = X ("clone (table, [table], boolean|:nometa?)", clone), + clone = X ("clone (table, [table], boolean|:nometa?)", + function (...) return merge_allfields ({}, ...) end), --- Make a partial clone of a table. -- @@ -400,7 +389,7 @@ local DEPRECATED = debug.DEPRECATED M.clone_rename = DEPRECATED ("39", "'std.table.clone_rename'", "use the new `map` argument to 'std.table.clone' instead", function (map, t) - local r = clone (t) + local r = merge_allfields ({}, t) for i, v in pairs (map) do r[v] = t[i] r[i] = nil From 7144c28c033a39fddc43b244443142c07e4518b7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 18:40:58 +0100 Subject: [PATCH 411/703] table: new insert method. * specs/table_spec.yaml (insert, len): Specify behaviours. * lib/std/base.lua (insert, last): New functions that respect `__len` when calculating table length. (len): Use callable to extract __len metamethod. * HACKING: New file to document coding style and design choices. * lib/std/base.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/io.lua, lib/std/list.lua, lib/std/optparse.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua, lib/std/vector.lua: Follow HACKING rules for use of len and insert. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- HACKING | 10 ++++++ NEWS | 6 ++++ lib/std/base.lua | 78 +++++++++++++++++++++++++++---------------- lib/std/container.lua | 13 ++++---- lib/std/debug.lua | 33 +++++++++--------- lib/std/io.lua | 7 ++-- lib/std/list.lua | 31 ++++++++--------- lib/std/optparse.lua | 45 +++++++++++++------------ lib/std/strbuf.lua | 8 +++-- lib/std/string.lua | 11 +++--- lib/std/table.lua | 42 +++++++++++++++++++++-- lib/std/tree.lua | 5 +-- lib/std/vector.lua | 18 +++++----- specs/table_spec.yaml | 4 +-- 14 files changed, 197 insertions(+), 114 deletions(-) create mode 100644 HACKING diff --git a/HACKING b/HACKING new file mode 100644 index 0000000..139cc30 --- /dev/null +++ b/HACKING @@ -0,0 +1,10 @@ + - 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. diff --git a/NEWS b/NEWS index 5a7d8ff..39cc08d 100644 --- a/NEWS +++ b/NEWS @@ -134,6 +134,12 @@ Stdlib NEWS - User visible changes - New `std.ripairs` function for returning index & value pairs in reverse order, while respecting `__len`. + - 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` returns its result, and uses `table.len` to + calculate default *pos* parameter. + ** Deprecations: - Deprecated APIs are kept for a minimum of 1 year following the first diff --git a/lib/std/base.lua b/lib/std/base.lua index f942323..a147a75 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,13 +23,28 @@ ]] +local function callable (x) + if type (x) == "function" then + return x + else + x = (getmetatable (x) or {}).__call + if type (x) == "function" then + return x + end + end +end + + local function len (t) -- Lua < 5.2 doesn't call `__len` automatically! local m = (getmetatable (t) or {}).__len - return m and m (t) or #t + return callable (m) and m (t) or #t end +local function last (t) return t[len (t)] end + + local function copy (t) local new = {} for k, v in pairs (t) do new[k] = v end @@ -140,7 +155,8 @@ end local function compare (l, m) - for i = 1, math.min (#l, #m) do + local lenl, lenm = len (l), len (m) + for i = 1, math.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] @@ -151,9 +167,9 @@ local function compare (l, m) return 1 end end - if #l < #m then + if lenl < lenm then return -1 - elseif #l > #m then + elseif lenl > lenm then return 1 end return 0 @@ -165,16 +181,30 @@ local function eval (s) end +local _insert = table.insert + +local function insert (t, pos, v) + if v == nil then pos, v = len (t) + 1, pos end + _insert (t, pos, v) + return t +end + + local function split (s, sep) - sep = sep or "%s+" - local b, len, t, patt = 0, #s, {}, "(.-)" .. sep - if sep == "" then patt = "(.)"; t[#t + 1] = "" end - while b <= len do + local r, patt = {} + if sep == "" then + patt = "(.)" + insert (r, "") + else + patt = "(.-)" .. (sep or "%s+") + end + local b, lens = 0, len (s) + while b <= lens do local e, n, m = string.find (s, patt, b + 1) - t[#t + 1] = m or s:sub (b + 1, len) - b = n or len + 1 + insert (r, m or s:sub (b + 1, lens)) + b = n or lens + 1 end - return t + return r end @@ -232,8 +262,8 @@ local function render (x, open, close, elem, pair, sep, roots) if type (x) ~= "table" or type ((getmetatable (x) or {}).__tostring) == "function" then return elem (x) else - local s = {} - s[#s + 1] = open (x) + local r = {} + r[#r + 1] = open (x) roots[x] = elem (x) -- create a sorted list of keys @@ -245,11 +275,11 @@ local function render (x, open, close, elem, pair, sep, roots) local i, v = nil, nil for _, j in ipairs (ord) do local w = x[j] - s[#s + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) + r[#r + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) i, v = j, w end - s[#s + 1] = sep (x, i, v, nil, nil) .. close (x) - return table.concat (s) + r[#r + 1] = sep (x, i, v, nil, nil) .. close (x) + return table.concat (r) end end @@ -272,18 +302,6 @@ local function prototype (o) end -local function callable (x) - if type (x) == "function" then - return x - else - x = (getmetatable (x) or {}).__call - if type (x) == "function" then - return x - end - end -end - - local function collect (ifn, ...) local argt = {...} if not callable (ifn) then @@ -314,7 +332,6 @@ end return { copy = copy, - len = len, -- std.lua -- assert = assert, @@ -347,6 +364,9 @@ return { -- table.lua -- getmetamethod = getmetamethod, + insert = insert, + last = last, + len = len, -- tree.lua -- leaves = leaves, diff --git a/lib/std/container.lua b/lib/std/container.lua index 66944a0..a69d980 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -71,6 +71,7 @@ local base = require "std.base" local debug = require "std.debug" local ipairs, pairs = base.ipairs, base.pairs +local insert, len = base.insert, base.len local prototype = base.prototype local argcheck = debug.argcheck local maxn = table.maxn @@ -159,7 +160,7 @@ local function mapfields (obj, src, map) local kind = type (key) if kind == "string" and key:sub (1, 1) == "_" then dst = mt - elseif kind == "number" and #dst + 1 < key then + elseif kind == "number" and len (dst) + 1 < key then -- When map is given, but has fewer entries than src, stop copying -- fields when map is exhausted. break @@ -280,24 +281,24 @@ function M.__tostring (self) local array = instantiate (totable (self)) local other = instantiate (array) local s = "" - if #other > 0 then + if len (other) > 0 then for i in ipairs (other) do other[i] = nil end end for k in pairs (other) do array[k] = nil end for i, v in ipairs (array) do array[i] = tostring (v) end local keys, dict = {}, {} - for k in pairs (other) do keys[#keys + 1] = k end + for k in pairs (other) do insert (keys, k) end table.sort (keys, function (a, b) return tostring (a) < tostring (b) end) for _, k in ipairs (keys) do - dict[#dict + 1] = tostring (k) .. "=" .. tostring (other[k]) + insert (dict, tostring (k) .. "=" .. tostring (other[k])) end - if #array > 0 then + if len (array) > 0 then s = s .. table.concat (array, ", ") if next (dict) ~= nil then s = s .. "; " end end - if #dict > 0 then + if len (dict) > 0 then s = s .. table.concat (dict, ", ") end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 0b7d374..1cbec69 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -32,11 +32,12 @@ local debug_init = require "std.debug_init" local base = require "std.base" -local _ARGCHECK = debug_init._ARGCHECK -local _DEBUG = debug_init._DEBUG -local callable = base.callable -local maxn = table.maxn +local _ARGCHECK = debug_init._ARGCHECK +local _DEBUG = debug_init._DEBUG +local maxn = table.maxn local split, tostring = base.split, base.tostring +local insert, last, len = base.insert, base.last, base.len +local ipairs, pairs = base.ipairs, base.pairs local M @@ -166,7 +167,7 @@ if _ARGCHECK then -- @treturn string string of elements from alternatives delimited by ", " -- and " or " local function concat (alternatives) - if #alternatives > 1 then + if len (alternatives) > 1 then local t = copy (alternatives) local top = table.remove (t) t[#t] = t[#t] .. " or " .. top @@ -244,25 +245,25 @@ if _ARGCHECK then for i, v in ipairs (types) do -- Remove sentinels before appending `v` to each list. for _, v in ipairs (p) do - if v[#v] == sentinel then table.remove (v) end + if last (v) == sentinel then table.remove (v) end end local opt = v:match "%[(.+)%]" if opt == nil then -- Append non-optional type-spec to each permutation. - for b = 1, #p do table.insert (p[b], v) end + for b = 1, len (p) do insert (p[b], v) end else -- Duplicate all existing permutations, and add optional type-spec -- to the unduplicated permutations. - local o = #p + local o = len (p) for b = 1, o do p[b + o] = copy (p[b]) - table.insert (p[b], opt) + insert (p[b], opt) end -- Leave a marker for optional argument in final position. for _, v in ipairs (p) do - table.insert (v, sentinel) + insert (v, sentinel) end end end @@ -289,7 +290,7 @@ if _ARGCHECK then -- @tparam boolean allargs whether to match all arguments -- @treturn int|nil position of first mismatch in *types* local function match (types, args, allargs) - local typec, argc = #types, maxn (args) + local typec, argc = len (types), maxn (args) for i = 1, typec do local ok = pcall (argcheck, "pcall", i, types[i], args[i]) if not ok then return i end @@ -391,7 +392,7 @@ if _ARGCHECK then elseif check == "list" or check == "#list" then if actualtype == "table" or actualtype == "List" then - local len, count = #actual, 0 + local len, count = len (actual), 0 local i = next (actual) repeat if i ~= nil then count = count + 1 end @@ -464,16 +465,16 @@ if _ARGCHECK then -- If the final element of types ends with "*", then set max to a -- sentinel value to denote type-checking of *all* remaining -- unchecked arguments against that type-spec is required. - local max, fin = #types, (types[#types] or ""):match "^(.+)%*$" + local max, fin = len (types), (last (types) or ""):match "^(.+)%*$" if fin then max = math.huge - types[#types] = fin + types[len (types)] = fin end -- For optional arguments wrapped in square brackets, make sure -- type-specs allow for passing or omitting an argument of that -- type. - local typec, type_specs = #types, permutations (types) + local typec, type_specs = len (types), permutations (types) return function (...) local args = {...} @@ -498,7 +499,7 @@ if _ARGCHECK then local tables = {} for i, types in ipairs (type_specs) do if types[bestmismatch] then - tables[#tables + 1] = types[bestmismatch] + insert (tables, types[bestmismatch]) end end expected = merge (unpack (tables)) diff --git a/lib/std/io.lua b/lib/std/io.lua index b66f420..cbccecf 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -14,8 +14,9 @@ local base = require "std.base" local debug = require "std.debug" -local ipairs, pairs = base.ipairs, base.pairs local argerror = debug.argerror +local ipairs, pairs = base.ipairs, base.pairs +local insert, len = base.insert, base.len local leaves = base.leaves local split = base.split @@ -84,8 +85,8 @@ end local function process_files (fn) -- N.B. "arg" below refers to the global array of command-line args - if #arg == 0 then - arg[#arg + 1] = "-" + if len (arg) == 0 then + insert (arg, "-") end for i, v in ipairs (arg) do if v == "-" then diff --git a/lib/std/list.lua b/lib/std/list.lua index 1a5555c..762ea26 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -33,7 +33,8 @@ local object = require "std.object" local Object = object {} -local ielems, ipairs, pairs = base.ielems, base.ipairs, base.pairs +local ipairs, pairs = base.ipairs, base.pairs +local len = base.len local compare = base.compare local prototype = base.prototype @@ -49,8 +50,8 @@ end local function concat (l, ...) local r = List {} - for e in ielems {l, ...} do - for v in ielems (e) do + for _, e in ipairs {l, ...} do + for _, v in ipairs (e) do r[#r + 1] = v end end @@ -69,14 +70,14 @@ end local function sub (l, from, to) local r = List {} - local len = #l + local lenl = len (l) from = from or 1 - to = to or len + to = to or lenl if from < 0 then - from = from + len + 1 + from = from + lenl + 1 end if to < 0 then - to = to + len + 1 + to = to + lenl + 1 end for i = from, to do r[#r + 1] = l[i] @@ -162,7 +163,7 @@ local DEPRECATED = debug.DEPRECATED local function depair (ls) local t = {} - for v in ielems (ls) do + for _, v in ipairs (ls) do t[v[1]] = v[2] end return t @@ -180,7 +181,7 @@ end local function filter (pfn, l) local r = List {} - for e in base.ielems (l) do + for _, e in ipairs (l) do if pfn (e) then r[#r + 1] = e end @@ -245,7 +246,7 @@ end local function map (fn, l) local r = List {} - for e in base.ielems (l) do + for _, e in ipairs (l) do local v = fn (e) if v ~= nil then r[#r + 1] = v @@ -288,10 +289,10 @@ local function shape (s, l) end end if zero then - s[zero] = math.ceil (#l / size) + s[zero] = math.ceil (len (l) / size) end local function fill (i, d) - if d > #s then + if d > len (s) then return l[i], i + 1 else local r = List {} @@ -308,11 +309,11 @@ end local function transpose (ls) - local rs, len, dims = List {}, base.len (ls), map (base.len, ls) - if #dims > 0 then + local rs, lenls, dims = List {}, len (ls), map (len, ls) + if len (dims) > 0 then for i = 1, math.max (unpack (dims)) do rs[i] = List {} - for j = 1, len do + for j = 1, lenls do rs[i][j] = ls[j][i] end end diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index c0fd491..e46bbe3 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -77,6 +77,7 @@ local base = require "std.base" local ipairs, pairs = base.ipairs, base.pairs +local insert, last, len = base.insert, base.last, base.len local OptionParser -- forward declaration @@ -119,7 +120,7 @@ local optional, required local function normalise (self, arglist) local normal = {} local i = 0 - while i < #arglist do + while i < len (arglist) do i = i + 1 local opt = arglist[i] @@ -131,8 +132,8 @@ local function normalise (self, arglist) -- Only split recognised long options. if self[optname] then - normal[#normal + 1] = optname - normal[#normal + 1] = opt:sub (x + 1) + insert (normal, optname) + insert (normal, opt:sub (x + 1)) else x = nil end @@ -140,7 +141,7 @@ local function normalise (self, arglist) if x == nil then -- No '=', or substring before '=' is not a known option name. - normal[#normal + 1] = opt + insert (normal, opt) end elseif opt:sub (1, 1) == "-" and string.len (opt) > 2 then @@ -172,9 +173,9 @@ local function normalise (self, arglist) until opt == nil -- Append split options to normalised list - for _, v in ipairs (split) do normal[#normal + 1] = v end + for _, v in ipairs (split) do insert (normal, v) end else - normal[#normal + 1] = opt + insert (normal, opt) end end @@ -193,7 +194,7 @@ local function set (self, opt, value) local opts = self.opts[key] if type (opts) == "table" then - opts[#opts + 1] = value + insert (opts, value) elseif opts ~= nil then self.opts[key] = { opts, value } else @@ -227,7 +228,7 @@ end -- argument, or a default value if encountered without an optarg -- @treturn int index of next element of `arglist` to process function optional (self, arglist, i, value) - if i + 1 <= #arglist and arglist[i + 1]:sub (1, 1) ~= "-" then + if i + 1 <= len (arglist) and arglist[i + 1]:sub (1, 1) ~= "-" then return self:required (arglist, i, value) end @@ -272,7 +273,7 @@ end -- @treturn int index of next element of `arglist` to process function required (self, arglist, i, value) local opt = arglist[i] - if i + 1 > #arglist then + if i + 1 > len (arglist) then self:opterr ("option '" .. opt .. "' requires an argument") return i + 1 end @@ -303,10 +304,10 @@ end -- @int i index of last processed element of `arglist` -- @treturn int index of next element of `arglist` to process local function finished (self, arglist, i) - for opt = i + 1, #arglist do - self.unrecognised[#self.unrecognised + 1] = arglist[opt] + for opt = i + 1, len (arglist) do + insert (self.unrecognised, arglist[opt]) end - return 1 + #arglist + return 1 + len (arglist) end @@ -499,16 +500,16 @@ local function on (self, opts, handler, value) if opt:match ("^%-[^%-]+") ~= nil then -- '-xyz' => '-x -y -z' for i = 2, string.len (opt) do - normal[#normal + 1] = "-" .. opt:sub (i, i) + insert (normal, "-" .. opt:sub (i, i)) end else - normal[#normal + 1] = opt + insert (normal, opt) end end) end -- strip leading '-', and convert non-alphanums to '_' - local key = normal[#normal]:match ("^%-*(.*)$"):gsub ("%W", "_") + local key = last (normal):match ("^%-*(.*)$"):gsub ("%W", "_") for _, opt in ipairs (normal) do self[opt] = { key = key, handler = handler, value = value } @@ -560,16 +561,16 @@ local function parse (self, arglist, defaults) arglist = normalise (self, arglist) local i = 1 - while i > 0 and i <= #arglist do + while i > 0 and i <= len (arglist) do local opt = arglist[i] if self[opt] == nil then - self.unrecognised[#self.unrecognised + 1] = opt + insert (self.unrecognised, opt) i = i + 1 -- Following non-'-' prefixed argument is an optarg. - if i <= #arglist and arglist[i]:match "^[^%-]" then - self.unrecognised[#self.unrecognised + 1] = arglist[i] + if i <= len (arglist) and arglist[i]:match "^[^%-]" then + insert (self.unrecognised, arglist[i]) i = i + 1 end @@ -643,7 +644,7 @@ function OptionParser (spec) -- by a '-'. local specs = {} parser.helptext:gsub ("\n %s*(%-[^\n]+)", - function (spec) specs[#specs + 1] = spec end) + function (spec) insert (specs, spec) end) -- Register option handlers according to the help text. for _, spec in ipairs (specs) do @@ -679,7 +680,7 @@ function OptionParser (spec) local _, c = spec:gsub ("^%-([-%w]),?%s+(.*)$", function (opt, rest) if opt == "-" then opt = "--" end - options[#options + 1] = opt + insert (options, opt) spec = rest end) @@ -689,7 +690,7 @@ function OptionParser (spec) -- Consume long option. spec:gsub ("^%-%-([%-%w]+),?%s+(.*)$", function (opt, rest) - options[#options + 1] = opt + insert (options, opt) spec = rest end) end diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 9893676..1d33d5d 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -3,17 +3,19 @@ @classmod std.strbuf ]] - +local base = require "std.base" local object = require "std.object" + local Object = object {} +local insert = base.insert + --- Add a string to a buffer. -- @tparam string s string to add -- @treturn std.strbuf modified buffer local function concat (self, s) - self[#self + 1] = s - return self + return insert (self, s) end diff --git a/lib/std/string.lua b/lib/std/string.lua index 559d525..d22207d 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -18,6 +18,7 @@ local table = require "std.table" local StrBuf = strbuf {} local getmetamethod = base.getmetamethod +local insert, len = base.insert, base.len local pairs = base.pairs local render = base.render local totable = table.totable @@ -66,7 +67,7 @@ local function finds (s, p, i, ...) repeat from, to, r = tfind (s, p, i, ...) if from ~= nil then - l[#l + 1] = {from, to, capt = r} + insert (l, {from, to, capt = r}) i = to + 1 end until not from @@ -129,10 +130,10 @@ local function wrap (s, w, ind, ind1) assert (ind1 < w and ind < w, "the indents must be less than the line width") local r = StrBuf { string.rep (" ", ind1) } - local i, lstart, len = 1, ind1, #s - while i <= #s do + local i, lstart, lens = 1, ind1, len (s) + while i <= lens do local j = i + w - lstart - while #s[j] > 0 and s[j] ~= " " and j > i do + while len (s[j]) > 0 and s[j] ~= " " and j > i do j = j - 1 end local ni = j + 1 @@ -141,7 +142,7 @@ local function wrap (s, w, ind, ind1) end r:concat (s:sub (i, j)) i = ni - if i < #s then + if i < lens then r:concat ("\n" .. string.rep (" ", ind)) lstart = ind end diff --git a/lib/std/table.lua b/lib/std/table.lua index 5d8c2de..670fbe7 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -17,6 +17,7 @@ local debug = require "std.debug" local collect = base.collect local leaves = base.leaves local ipairs, pairs = base.ipairs, base.pairs +local insert, len = base.insert, base.len local M @@ -128,10 +129,10 @@ local function shape (dims, t) end end if zero then - dims[zero] = math.ceil (#t / size) + dims[zero] = math.ceil (len (t) / size) end local function fill (i, d) - if d > #dims then + if d > len (dims) then return t[i], i + 1 else local r = {} @@ -212,6 +213,7 @@ M = { --- Make a shallow copy of a table, including any metatable. -- -- To make deep copies, use @{tree.clone}. + -- @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 @@ -227,6 +229,7 @@ M = { --- 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 @@ -241,6 +244,7 @@ M = { --- Turn a list of pairs into a table. -- @todo Find a better name. + -- @function depair -- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` -- @see enpair @@ -248,36 +252,59 @@ M = { --- 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 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), --- Flatten a nested table into a list. + -- @function flatten -- @tparam table t a table -- @treturn table a list of all non-table elements of *t* flatten = X ("flatten (table)", flatten), + --- Enhance core *table.insert* to return its result. + -- If *pos* is not given, respect `__len` metamethod when calculating + -- default append. + -- @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* + insert = X ("insert (table, [int], any?)", base.insert), + --- Invert a table. + -- @function invert -- @tparam table t a table with `{k=v, ...}` -- @treturn table inverted table `{v=k, ...}` -- @usage values = invert (t) 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), + --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. + -- @function len + -- @tparam table t a table + -- @treturn int length of list part of *t* + -- @usage for i = 1, len (t) do process (t[i]) end + len = X ("len (table)", base.len), + --- Destructively merge another 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, ...}` @@ -291,6 +318,7 @@ M = { --- 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 @@ -304,6 +332,7 @@ M = { merge_namedfields), --- 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* @@ -311,11 +340,13 @@ M = { new = X ("new (any?, table?)", new), --- Turn a tuple into a list. + -- @function pack -- @param ... tuple -- @return list pack = function (...) return {...} end, --- 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* @@ -337,18 +368,21 @@ M = { -- -- @todo Use ileaves instead of flatten (needs a while instead of a -- for in fill function) + -- @function shape -- @tparam table dims table of dimensions `{d1, ..., dn}` -- @tparam table t a table of elements -- @return reshaped list shape = X ("shape (table, table)", shape), --- 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 count = size {foo = true, bar = true, baz = false} size = X ("size (table)", size), - --- Make table.sort return its result. + --- 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 -- lua `<` operator @@ -359,6 +393,7 @@ M = { --- Overwrite core methods with `std` enhanced versions. -- -- Replaces core `table.sort` with `std.table` version. + -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local table = require "std.table".monkey_patch () @@ -372,6 +407,7 @@ M = { totable = X ("totable (object|table|string)", totable), --- Make the list of values of a table. + -- @function values -- @tparam table t any table -- @treturn table list of values in *t* -- @see keys diff --git a/lib/std/tree.lua b/lib/std/tree.lua index cb633f0..4f45b4d 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -20,6 +20,7 @@ local Container = container {} local ielems, ipairs, base_leaves, pairs, prototype = base.ielems, base.ipairs, base.leaves, base.pairs, base.prototype +local last, len = base.last, base.len local reduce = func.reduce local Tree -- forward declaration @@ -241,13 +242,13 @@ Tree = Container { -- @param v value __newindex = function (self, i, v) if prototype (i) == "table" then - for n = 1, #i - 1 do + for n = 1, len (i) - 1 do if prototype (self[i[n]]) ~= "Tree" then rawset (self, i[n], Tree {}) end self = self[i[n]] end - rawset (self, i[#i], v) + rawset (self, last (i), v) else rawset (self, i, v) end diff --git a/lib/std/vector.lua b/lib/std/vector.lua index cc9df54..ed47454 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -42,8 +42,9 @@ local Container = container {} local typeof = type -local argcheck, pairs, prototype = - debug.argcheck, base.pairs, base.prototype +local argcheck = debug.argcheck +local pairs, prototype = base.pairs, base.prototype +local insert, len = base.insert, base.len --[[ ================= ]]-- @@ -184,7 +185,7 @@ local core_functions = { -- @usage added = avector:unshift (anelement) unshift = function (self, elem) self.length = self.length + 1 - table.insert (self.buffer, 1, elem) + insert (self.buffer, 1, elem) return elem end, } @@ -242,7 +243,7 @@ core_metatable = { -- of `type`, so we'll use Lua tables and core_metatable: local b = {} if typeof (init) == "table" then - for i = 1, #init do + for i = 1, len (init) do b[i] = init[i] end else @@ -266,11 +267,12 @@ core_metatable = { -- We have alien, and it knows how to manage elements of `type`, -- so we'll use an alien.buffer and alien_metatable: if typeof (init) == "table" then - obj.allocated = #init - obj.buffer = buffer (size * #init) - obj.length = #init + local initlen = len (init) + obj.allocated = initlen + obj.buffer = buffer (size * initlen) + obj.length = initlen - for i = 1, #init do + for i = 1, initlen do obj.buffer:set ((i - 1) * size + 1, init[i], type) end else diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index e485b22..7d8724e 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -4,8 +4,8 @@ before: | global_table = "_G" extend_base = { "clone", "clone_rename", "clone_select", "depair", - "empty", "enpair", "flatten", "invert", "keys", - "merge", "merge_select", "metamethod", + "empty", "enpair", "flatten", "insert", "invert", + "keys", "len", "merge", "merge_select", "metamethod", "monkey_patch", "new", "pack", "project", "ripairs", "shape", "size", "sort", "totable", "values" } From c52e0cfa8f247fd8d83b4990a0ec66f5a6b10998 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 19:26:15 +0100 Subject: [PATCH 412/703] table: add missing specs for table.insert, and correct argtypes. * specs/table_spec.yaml (insert): Specify behaviours. * lib/std/table.lua (insert): Don't double import. (M): Don't allow nil valued final argument. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 4 ++-- specs/table_spec.yaml | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index 670fbe7..a9cfd0c 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -17,7 +17,7 @@ local debug = require "std.debug" local collect = base.collect local leaves = base.leaves local ipairs, pairs = base.ipairs, base.pairs -local insert, len = base.insert, base.len +local len = base.len local M @@ -279,7 +279,7 @@ M = { -- @int[opt=len (t)] pos index at which to insert new element -- @param v value to insert into *t* -- @treturn table *t* - insert = X ("insert (table, [int], any?)", base.insert), + insert = X ("insert (table, [int], any)", base.insert), --- Invert a table. -- @function invert diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 7d8724e..ecf90bf 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -242,6 +242,42 @@ specify std.table: expect (f (t)).to_equal {"one", "two", "three", "four"} +- describe insert: + - before: + f, badarg = init (M, this_module, "insert") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + expect (f {}).to_raise (badarg (2, "int or any value")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + - it diagnoses out of bounds pos arguments: + expect (f ({}, 0, "x")).to_raise "position out of bounds" + expect (f ({}, 2, "x")).to_raise "position out of bounds" + expect (f ({1}, 5, "x")).to_raise "position out of bounds" + - it diagnoses too many arguments: + expect (f ({}, 1, 2, false)).to_raise (badarg (4)) + pending "#issue 76" + expect (f ({}, false, false)).to_raise (badarg (3)) + + - 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 } @@ -317,7 +353,7 @@ specify std.table: - it diagnoses too many arguments: | expect (f ({}, {}, {}, ":nometa", false)). to_raise (badarg (5)) - pending "issue #78" + pending "issue #76" expect (f ({}, {}, ":nometa", false)).to_raise (badarg (4)) - it does not create a whole new table: @@ -384,7 +420,7 @@ specify std.table: - it diagnoses too many arguments: | expect (f ({}, {}, {}, ":nometa", false)). to_raise (badarg (5)) - pending "issue #78" + pending "issue #76" expect (f ({}, {}, ":nometa", false)).to_raise (badarg (4)) - it does not create a whole new table: From 285231103c55aca371aa3e48170dfc1bcb2ccca8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 19:57:16 +0100 Subject: [PATCH 413/703] table: diagnose insert out of bounds arguments on all Lua. * specs/table_spec.yaml (insert): Adjust errors to include out of bounds position. * lib/std/debug.lua (argerror): Move implementation from here... * lib/std/base.lua (argerror): ...to here. (insert): Use it to diagnose out of bounds arguments. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 16 ++++++++++++++++ lib/std/debug.lua | 13 ++----------- specs/table_spec.yaml | 6 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index a147a75..2cd4650 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,6 +23,16 @@ ]] +local function argerror (name, i, extramsg, level) + level = level or 1 + local s = string.format ("bad argument #%d to '%s'", i, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) +end + + local function callable (x) if type (x) == "function" then return x @@ -185,6 +195,9 @@ 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 @@ -346,6 +359,9 @@ return { require = require, tostring = tostring, + -- debug.lua -- + argerror = argerror, + -- functional.lua -- callable = callable, collect = collect, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 1cbec69..8e45f57 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -34,7 +34,8 @@ local base = require "std.base" local _ARGCHECK = debug_init._ARGCHECK local _DEBUG = debug_init._DEBUG -local maxn = table.maxn +local maxn = table.maxn +local argerror = base.argerror local split, tostring = base.split, base.tostring local insert, last, len = base.insert, base.last, base.len local ipairs, pairs = base.ipairs, base.pairs @@ -140,16 +141,6 @@ local getfenv = getfenv or function (fn) end -local function argerror (name, i, extramsg, level) - level = level or 1 - local s = string.format ("bad argument #%d to '%s'", i, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) -end - - local function toomanyargmsg (name, expect, actual) local fmt = "too many arguments to '%s' (no more than %d expected, got %d)" return string.format (fmt, name, expect, actual) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index ecf90bf..386aa6c 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -252,9 +252,9 @@ specify std.table: - it diagnoses wrong argument types: expect (f (false)).to_raise (badarg (1, "table", "boolean")) - it diagnoses out of bounds pos arguments: - expect (f ({}, 0, "x")).to_raise "position out of bounds" - expect (f ({}, 2, "x")).to_raise "position out of bounds" - expect (f ({1}, 5, "x")).to_raise "position out of bounds" + 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" - it diagnoses too many arguments: expect (f ({}, 1, 2, false)).to_raise (badarg (4)) pending "#issue 76" From 87c52cf63cc1edfd06ab120f8e30467cc66d8cce Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 20:06:13 +0100 Subject: [PATCH 414/703] refactor: reorder function definitions in base.lua. * lib/std/base.lua: Rather than mostly random order, subject to interdepencies, put functions in asciibetical order as far as possible while avoiding forward declarations. * HACKING: Update. Signed-off-by: Gary V. Vaughan --- HACKING | 25 ++++ NEWS | 3 +- lib/std/base.lua | 302 +++++++++++++++++++++++----------------------- lib/std/table.lua | 3 +- 4 files changed, 183 insertions(+), 150 deletions(-) diff --git a/HACKING b/HACKING index 139cc30..b002f09 100644 --- a/HACKING +++ b/HACKING @@ -1,3 +1,16 @@ + - 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 + declarations. 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 @@ -8,3 +21,15 @@ 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 diff --git a/NEWS b/NEWS index 39cc08d..13d41d6 100644 --- a/NEWS +++ b/NEWS @@ -138,7 +138,8 @@ Stdlib NEWS - User visible changes the core `#` operation, but respecing `__len` even on Lua 5.1. - New `table.insert` returns its result, and uses `table.len` to - calculate default *pos* parameter. + calculate default *pos* parameter, as well as diagnosing out of bounds + *pos* parameters consistently on any supported version of Lua. ** Deprecations: diff --git a/lib/std/base.lua b/lib/std/base.lua index 2cd4650..b5cff18 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -33,97 +33,98 @@ local function argerror (name, i, extramsg, level) end -local function callable (x) - if type (x) == "function" then - return x - else - x = (getmetatable (x) or {}).__call - if type (x) == "function" then - return x - end - end -end - - -local function len (t) - -- Lua < 5.2 doesn't call `__len` automatically! - local m = (getmetatable (t) or {}).__len - return callable (m) and m (t) or #t +local function assert (expect, fmt, arg1, ...) + local msg = (arg1 ~= nil) and string.format (fmt, arg1, ...) or fmt or "" + return expect or error (msg, 2) end -local function last (t) return t[len (t)] end - - -local function copy (t) - local new = {} - for k, v in pairs (t) do new[k] = v end - return new +local function getmetamethod (x, n) + local _, m = pcall (function (x) + return getmetatable (x)[n] + end, + x) + if type (m) ~= "function" then + m = nil + end + return m 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 - coroutine.yield (n) - end - end - return coroutine.wrap (visit), tr +local function callable (x) + if type (x) == "function" then return x end + return getmetamethod (x, "__call") end -local _pairs = pairs - --- Respect __pairs metamethod, even in Lua 5.1. -local function pairs (t) - return ((getmetatable (t) or {}).__pairs or _pairs) (t) +-- Lua < 5.2 doesn't call `__len` automatically! +local function len (t) + local m = getmetamethod (t, "__len") + return m and m (t) or #t end --- Iterate over keys 1..#l, like Lua 5.3. local function ipairs (l) - local tlen = len (l) + local lenl = len (l) return function (l, n) n = n + 1 - if n <= tlen then + if n <= lenl then return n, l[n] end end, l, 0 end -local function ripairs (t) - return function (t, n) - n = n - 1 - if n > 0 then - return n, t[n] +local function collect (ifn, ...) + local argt = {...} + if not callable (ifn) then + ifn, argt = ipairs, {ifn, ...} + end + + local r = {} + for k, v in ifn (unpack (argt)) do + if v == nil then k, v = #r + 1, k end + r[k] = v + end + return r +end + + +local function compare (l, m) + local lenl, lenm = len (l), len (m) + for i = 1, math.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 - end, t, len (t) + 1 + 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 --- Be careful not to compact holes from `t` when reversing. -local function ireverse (t) - local r, tlen = {}, len (t) - for i = 1, tlen do r[tlen - i + 1] = t[i] end - return r +local _pairs = pairs + +-- Respect __pairs metamethod, even in Lua 5.1. +local function pairs (t) + return (getmetamethod (t, "__pairs") or _pairs) (t) end -local function getmetamethod (x, n) - local _, m = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if type (m) ~= "function" then - m = nil - end - return m +local function copy (t) + local r = {} + for k, v in pairs (t) do r[k] = v end + return r end @@ -149,45 +150,30 @@ end local function elems (t) - return wrapiterator ((getmetatable (t) or {}).__pairs or pairs, t) + return wrapiterator (pairs, t) end -local function ielems (l) - return wrapiterator (ipairs, l) +local function eval (s) + return loadstring ("return " .. s)() end -local function assert (expect, f, arg1, ...) - local msg = (arg1 ~= nil) and string.format (f, arg1, ...) or f or "" - return expect or error (msg, 2) -end - +-- Iterate over keys 1..#l, like Lua 5.3. +local function ipairs (l) + local tlen = len (l) -local function compare (l, m) - local lenl, lenm = len (l), len (m) - for i = 1, math.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 + return function (l, n) + n = n + 1 + if n <= tlen then + return n, l[n] end - end - if lenl < lenm then - return -1 - elseif lenl > lenm then - return 1 - end - return 0 + end, l, 0 end -local function eval (s) - return loadstring ("return " .. s)() +local function ielems (l) + return wrapiterator (ipairs, l) end @@ -203,43 +189,46 @@ local function insert (t, pos, v) end -local function split (s, sep) - local r, patt = {} - if sep == "" then - patt = "(.)" - insert (r, "") - else - patt = "(.-)" .. (sep or "%s+") - end - local b, lens = 0, len (s) - while b <= lens do - local e, n, m = string.find (s, patt, b + 1) - insert (r, m or s:sub (b + 1, lens)) - b = n or lens + 1 - end +-- Be careful not to compact holes from `t` when reversing. +local function ireverse (t) + local r, tlen = {}, len (t) + for i = 1, tlen do r[tlen - i + 1] = t[i] end return r end -local function vcompare (a, b) - return compare (split (a, "%."), split (b, "%.")) +local function last (t) return t[len (t)] 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 + coroutine.yield (n) + end + end + return coroutine.wrap (visit), tr end -local _require = require +local function prototype (o) + return (getmetatable (o) or {})._type or io.type (o) or type (o) +end -local function require (module, min, too_big, pattern) - local m = _require (module) - local v = (m.version or m._VERSION or ""):match (pattern or "([%.%d]+)%D*$") - 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) + +local function reduce (fn, d, ifn, ...) + local nextfn, state, k = ifn (...) + local t = {nextfn (state, k)} + + local r = d + while t[1] ~= nil do + r = fn (r, t[#t]) + t = {nextfn (state, t[1])} end - return m + return r end @@ -297,52 +286,69 @@ local function render (x, open, close, elem, pair, sep, roots) end -local _tostring = _G.tostring - -local function tostring (x) - return render (x, - function () return "{" end, - function () return "}" end, - _tostring, - function (_, _, _, is, vs) return is .."=".. vs end, - function (_, i, _, j) return i and j and "," or "" end) +local function ripairs (t) + return function (t, n) + n = n - 1 + if n > 0 then + return n, t[n] + end + end, t, len (t) + 1 end +local function split (s, sep) + local r, patt = {} + if sep == "" then + patt = "(.)" + insert (r, "") + else + patt = "(.-)" .. (sep or "%s+") + end + local b, lens = 0, len (s) + while b <= lens do + local e, n, m = string.find (s, patt, b + 1) + insert (r, m or s:sub (b + 1, lens)) + b = n or lens + 1 + end + return r +end + -local function prototype (o) - return (getmetatable (o) or {})._type or io.type (o) or type (o) +local function vcompare (a, b) + return compare (split (a, "%."), split (b, "%.")) end -local function collect (ifn, ...) - local argt = {...} - if not callable (ifn) then - ifn, argt = ipairs, {ifn, ...} - end +local _require = require - local r = {} - for k, v in ifn (unpack (argt)) do - if v == nil then k, v = #r + 1, k end - r[k] = v +local function require (module, min, too_big, pattern) + local m = _require (module) + local v = (m.version or m._VERSION or ""):match (pattern or "([%.%d]+)%D*$") + if min then + assert (vcompare (v, min) >= 0, "require '" .. module .. + "' with at least version " .. min .. ", but found version " .. v) end - return r + 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 -local function reduce (fn, d, ifn, ...) - local nextfn, state, k = ifn (...) - local t = {nextfn (state, k)} +local _tostring = _G.tostring - local r = d - while t[1] ~= nil do - r = fn (r, t[#t]) - t = {nextfn (state, t[1])} - end - return r +local function tostring (x) + return render (x, + function () return "{" end, + function () return "}" end, + _tostring, + function (_, _, _, is, vs) return is .."=".. vs end, + function (_, i, _, j) return i and j and "," or "" end) end + return { copy = copy, diff --git a/lib/std/table.lua b/lib/std/table.lua index a9cfd0c..f3b03c1 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -273,7 +273,8 @@ M = { --- Enhance core *table.insert* to return its result. -- If *pos* is not given, respect `__len` metamethod when calculating - -- default append. + -- 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 From 3fd10afffc5d356e1c26119d2396406d93a41fed Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 22:59:30 +0100 Subject: [PATCH 415/703] operator: more RSI-reducing operator function name changes. * specs/operator_spec.yaml (cons, length): Remove - just pass `table.pack` or `table.len` instead, resp. (["and"], ["or"], ["not"]): Rename these... (conj, disj, neg): ...to these. * lib/std/operator.lua: Likewise. Give full and proper LDocs for each function. * lib/std/functional.lua (M.op): Update deprecation redirections. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 14 ++- lib/std/functional.lua | 8 +- lib/std/operator.lua | 189 +++++++++++++++++++++++++++++---------- lib/std/table.lua | 1 - specs/operator_spec.yaml | 33 ++----- 5 files changed, 159 insertions(+), 86 deletions(-) diff --git a/NEWS b/NEWS index 13d41d6..2b3d8d4 100644 --- a/NEWS +++ b/NEWS @@ -21,13 +21,11 @@ Stdlib NEWS - User visible changes for production code. Similarly `_DEBUG = false` deactivates these functions in the same way. - - New `std.operator` module, with easier to type operator names (`deref`, - `diff`, `eq`, `neq`, `prod`, `quot`, and `sum`), plus brand new - functional operators for concatenation `concat`, table construction - `cons`, list length `length`; plus new mathematical operators `mod`, - and `pow`; and relational operators `lt`, `lte`, `gt` and `gte`. - The `length` operator respects the `__len` metamethod, if any, even on - Lua 5.1. + - 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`. - New `std.vector` object, for clean and fast queue-like or stack-like container management. When alien is installed, and element types @@ -166,7 +164,7 @@ Stdlib NEWS - User visible changes name now gives a deprecation warning. - `functional.op` has been moved to a new `std.operator` module, the - old name now gives a deprecation warning. + 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. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 8598aee..496e741 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -544,7 +544,7 @@ M.fold = DEPRECATED ("41", "'std.functional.fold'", local function DEPRECATEOP (t, old, new) return DEPRECATED ("41", "'std.functional.op[" .. old .. "]'", - "use 'std.functional.operator." .. new .. "' instead", t[new]) + "use 'std.operator." .. new .. "' instead", t[new]) end M.op = { @@ -553,9 +553,9 @@ M.op = { ["-"] = DEPRECATEOP (operator, "-", "diff"), ["*"] = DEPRECATEOP (operator, "*", "prod"), ["/"] = DEPRECATEOP (operator, "/", "quot"), - ["and"] = DEPRECATEOP (operator, "and", "and"), - ["or"] = DEPRECATEOP (operator, "or", "or"), - ["not"] = DEPRECATEOP (operator, "not", "not"), + ["and"] = DEPRECATEOP (operator, "and", "conj"), + ["or"] = DEPRECATEOP (operator, "or", "disj"), + ["not"] = DEPRECATEOP (operator, "not", "neg"), ["=="] = DEPRECATEOP (operator, "==", "eq"), ["~="] = DEPRECATEOP (operator, "~=", "neq"), } diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 1a281f3..5db2044 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -1,55 +1,152 @@ --[[-- - Functional Operators. + Functional forms of Lua operators. @module std.operator ]] - local base = require "std.base" +local tostring = base.tostring + + +local M = { + --- Stringify and concatenate arguments. + -- @param a an argument + -- @param b another argument + -- @return concatenation of stringified arguments. + -- @usage + -- --> "=> 1000010010" + -- functional.reduce (concat, "=> ", ipairs, {10000, 100, 10}) + concat = function (a, b) return tostring (a) .. tostring (b) end, + + --- Dereference a table. + -- @tparam table t a table + -- @param k a key to lookup in *t* + -- @return value stored at *t[k]* if any, otherwise `nil` + -- @usage + -- --> 4 + -- functional.reduce (deref, {1, {{2, 3, 4}, 5}}, std.ielems, {2, 1, 3}) + deref = function (t, k) return t and t[k] or nil end, + + --- Return the sum of the arguments. + -- @param a an argument + -- @param b another argument + -- @return the sum of the *a* and *b* + -- @usage + -- --> 10110 + -- functional.foldl (sum, {10000, 100, 10}) + sum = function (a, b) return a + b end, + + --- Return the difference of the arguments. + -- @param a an argument + -- @param b another argument + -- @return the difference between *a* and *b* + -- @usage + -- --> 890 + -- functional.foldl (sum, {10000, 100, 10}) + diff = function (a, b) return a - b end, + + --- Return the product of the arguments. + -- @param a an argument + -- @param b another argument + -- @return the product of *a* and *b* + -- @usage + -- --> 10000000 + -- functional.foldl (sum, {10000, 100, 10}) + prod = function (a, b) return a * b end, + + --- Return the quotient of the arguments. + -- @param a an argument + -- @param b another argument + -- @return the quotient *a* and *b* + -- @usage + -- --> 1000 + -- functional.foldr (quot, {10000, 100, 10}) + quot = function (a, b) return a / b end, + + --- Return the modulus of the arguments. + -- @param a an argument + -- @param b another argument + -- @return the modulus of *a* and *b* + -- @usage + -- --> 3 + -- functional.foldl (mod, {65536, 100, 11}) + mod = function (a, b) return a % b end, + + --- Return the exponent of the arguments. + -- @param a an argument + -- @param b another argument + -- @return the *a* to the power of *b* + -- @usage + -- --> 4096 + -- functional.foldl (pow, {2, 3, 4}) + pow = function (a, b) return a ^ b end, ---- Functional forms of Lua operators. --- --- 1. `concat`: concatenation --- 1. `deref`: dereference a table --- 1. `cons`: tablification --- 1. `length`: table or string length --- 1. `sum`: addition --- 1. `diff`: subtraction --- 1. `prod`: multiplication --- 1. `quot`: division --- 1. `mod`: modulo --- 1. `pow`: exponentiation --- 1. `and`: logical and --- 1. `or`: logical or --- 1. `not`: logical not --- 1. `eq`: equality --- 1. `neq`: inequality --- 1. `lt`: less than --- 1. `lte`: less than or equal --- 1. `gt`: greater than --- 1. `gte`: greater than or equal --- @table std.operator - ---- -return { - concat = function (a, b) return tostring (a) .. tostring (b) end, - deref = function (t, s) return t and t[s] or nil end, - cons = function (...) return {...} end, - length = base.len, - sum = function (a, b) return a + b end, - diff = function (a, b) return a - b end, - prod = function (a, b) return a * b end, - quot = function (a, b) return a / b end, - mod = function (a, b) return a % b end, - pow = function (a, b) return a ^ b end, - ["and"] = function (a, b) return a and b end, - ["or"] = function (a, b) return a or b end, - ["not"] = function (a) return not a end, - eq = function (a, b) return a == b end, - neq = function (a, b) return a ~= b end, - lt = function (a, b) return a < b end, - lte = function (a, b) return a <= b end, - gt = function (a, b) return a > b end, - gte = function (a, b) return a >= b end, + --- Return the logical conjunction of the arguments. + -- @param a an argument + -- @param b another argument + -- @return logical *a* and *b* + -- @usage + -- --> true + -- functional.foldl (conj, {true, 1, "false"}) + conj = function (a, b) return a and b end, + + --- Return the logical disjunction of the arguments. + -- @param a an argument + -- @param b another argument + -- @return logical *a* or *b* + -- @usage + -- --> true + -- functional.foldl (disj, {true, 1, false}) + disj = function (a, b) return a or b end, + + --- Return the logical negation of the arguments. + -- @param a an argument + -- @return not *a* + -- @usage + -- --> {true, false, false, false} + -- functional.bind (functional.map, {std.ielems, neg}) {false, true, 1, 0} + neg = function (a) return not a end, + + --- Return the equality of the arguments. + -- @param a an argument + -- @param b another argument + -- @return `true` if *a* is *b*, otherwise `false` + eq = function (a, b) return a == b end, + + --- Return the inequality of the arguments. + -- @param a an argument + -- @param b another argument + -- @return `false` if *a* is *b*, otherwise `true` + -- @usage + -- --> true + -- local f = require "std.functional" + -- table.empty (f.filter (f.bind (neq, {6}), std.ielems, {6, 6, 6}) + neq = function (a, b) return a ~= b end, + + --- Return whether the arguments are in ascending order. + -- @param a an argument + -- @param b another argument + -- @return `true` if *a* is less then *b*, otherwise `false` + lt = function (a, b) return a < b end, + + --- Return whether the arguments are not in descending order. + -- @param a an argument + -- @param b another argument + -- @return `true` if *a* is not greater then *b*, otherwise `false` + lte = function (a, b) return a <= b end, + + --- Return whether the arguments are in descending order. + -- @param a an argument + -- @param b another argument + -- @return `true` if *a* is greater then *b*, otherwise `false` + gt = function (a, b) return a > b end, + + --- Return whether the arguments are not in ascending order. + -- @param a an argument + -- @param b another argument + -- @return `true` if *a* is not greater then *b*, otherwise `false` + gte = function (a, b) return a >= b end, } + +return M diff --git a/lib/std/table.lua b/lib/std/table.lua index f3b03c1..2003011 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -386,7 +386,6 @@ M = { -- @function sort -- @tparam table t unsorted table -- @tparam[opt=std.operator.lt] comparator c ordering function callback - -- lua `<` operator -- @return *t* with keys sorted accordind to *c* -- @usage table.concat (sort (object)) sort = X ("sort (table, function?)", sort), diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index b612bd9..10f487e 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -36,27 +36,6 @@ specify std.operator: expect (f ({"foo", "bar"}, 1)).to_be "foo" expect (f ({foo = "bar"}, "foo")).to_be "bar" -- describe cons: - - before: - f = M.cons - - - it packs its arguments into a table: - expect (f ()).to_equal {} - expect (f (42)).to_equal {42} - expect (f ("foo", "bar")).to_equal {"foo", "bar"} - expect (f ("a", "b", "c", 1, 2, 3)).to_equal {"a", "b", "c", 1, 2, 3} - -- describe length: - - before: - f = M.length - - - it returns the length of a string: - expect (f "1234567890").to_be (10) - - it returns the length of a table: - expect (f {1, 2, 3, 4, 5}).to_be (5) - - it uses the __len metamethod: - expect (f (setmetatable ({}, { __len = function () return 42 end }))).to_be (42) - - describe sum: - before: f = M.sum @@ -99,9 +78,9 @@ specify std.operator: - it returns the power of its arguments: expect (f (99, 2)).to_be (math.pow (99, 2)) -- describe and: +- describe conj: - before: - f = M["and"] + f = M.conj - it returns the logical and of its arguments: expect (f (false, false)).to_be (false) @@ -114,9 +93,9 @@ specify std.operator: expect (f (nil, 0)).to_be (nil) expect (f (0, "false")).to_be ("false") -- describe or: +- describe disj: - before: - f = M["or"] + f = M.disj - it returns the logical or of its arguments: expect (f (false, false)).to_be (false) @@ -129,9 +108,9 @@ specify std.operator: expect (f (nil, 0)).to_be (0) expect (f (0, "false")).to_be (0) -- describe not: +- describe neg: - before: - f = M["not"] + f = M.neg - it returns the logical not of its argument: expect (f (false)).to_be (true) From 18842551e9ca1befe19aaf280faa2d2079eca5cb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 23:08:33 +0100 Subject: [PATCH 416/703] functional: remove std.operator expansions from lambda. There's no good reason to clog up the lambda functable with a copy of std.operator, when we can just pass the operators without interposing lambda if we need to. * lib/std/functional.lua (lambda): Remove std.operator expansions. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 --- lib/std/functional.lua | 46 ++++++++++++++++++------------------------ 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/NEWS b/NEWS index 2b3d8d4..5bdc8aa 100644 --- a/NEWS +++ b/NEWS @@ -63,9 +63,6 @@ Stdlib NEWS - User visible changes table.sort (t, lambda "= _1 < _2" - or, equivalently using `std.operator` references: - - table.sort (t, lambda "<") - New `functional.map_with` that returns a new table with keys matching the argument table, and values made by mapping the supplied function diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 496e741..7c6269f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -10,7 +10,6 @@ local base = require "std.base" local debug = require "std.debug" -local operator = require "std.operator" local ipairs, ireverse, len, pairs = base.ipairs, base.ireverse, base.len, base.pairs @@ -165,11 +164,6 @@ end local lambda = memoize (function (s) local expr - -- Support operator table lookup. - if operator[s] then - return operator[s] - end - -- Support "|args|expression" format. local args, body = s:match "^|([^|]*)|%s*(.+)$" if args and body then @@ -269,7 +263,7 @@ local M = { -- @tparam table argt table of *fn* arguments to bind -- @return function with *argt* arguments already bound -- @usage - -- cube = bind (lambda "^", {[2] = 3}) + -- cube = bind (std.operator.pow, {[2] = 3}) bind = X ("bind (func, any?*)", bind), --- Identify callable types. @@ -383,7 +377,7 @@ local M = { -- @see foldr -- @see reduce -- @usage - -- foldl (lambda "/", {10000, 100, 10}) == (10000 / 100) / 10 + -- foldl (std.operator.quot, {10000, 100, 10}) == (10000 / 100) / 10 foldl = X ("foldl (function, [any], table)", foldl), --- Fold a binary function right associatively. @@ -397,7 +391,7 @@ local M = { -- @see foldl -- @see reduce -- @usage - -- foldr (lambda "/", {10000, 100, 10}) == 10000 / (100 / 10) + -- foldr (std.operator.quot, {10000, 100, 10}) == 10000 / (100 / 10) foldr = X ("foldr (function, [any], table)", foldr), --- Identity function. @@ -410,11 +404,10 @@ local M = { -- -- A valid lambda string takes one of the following forms: -- - -- 1. `'operator'`: where *op* is a key in @{std.operator}, equivalent to that operation -- 1. `'=expression'`: equivalent to `function (...) return (expression) end` -- 1. `'|args|expression'`: equivalent to `function (args) return (expression) end` -- - -- The second form (starting with `=`) automatically assigns the first + -- The first form (starting with `=`) automatically assigns the first -- nine arguments to parameters `_1` through `_9` for use within the -- expression body. -- @@ -424,8 +417,7 @@ local M = { -- @string s a lambda string -- @treturn table compiled lambda string, can be called like a function -- @usage - -- -- The following are all equivalent: - -- lambda '<' + -- -- The following are equivalent: -- lambda '= _1 < _2' -- lambda '|a,b| a 2 ^ 3 ^ 4 ==> 4096 - -- reduce (lambda '^', 2, std.ipairs, {3, 4}) + -- reduce (std.operator.pow, 2, std.ipairs, {3, 4}) reduce = X ("reduce (func, any, func, any*)", reduce), --- Zip a table of tables. @@ -542,22 +534,24 @@ M.fold = DEPRECATED ("41", "'std.functional.fold'", "use 'std.functional.reduce' instead", reduce) -local function DEPRECATEOP (t, old, new) +local operator = require "std.operator" + +local function DEPRECATEOP (old, new) return DEPRECATED ("41", "'std.functional.op[" .. old .. "]'", - "use 'std.operator." .. new .. "' instead", t[new]) + "use 'std.operator." .. new .. "' instead", operator[new]) end M.op = { - ["[]"] = DEPRECATEOP (operator, "[]", "deref"), - ["+"] = DEPRECATEOP (operator, "+", "sum"), - ["-"] = DEPRECATEOP (operator, "-", "diff"), - ["*"] = DEPRECATEOP (operator, "*", "prod"), - ["/"] = DEPRECATEOP (operator, "/", "quot"), - ["and"] = DEPRECATEOP (operator, "and", "conj"), - ["or"] = DEPRECATEOP (operator, "or", "disj"), - ["not"] = DEPRECATEOP (operator, "not", "neg"), - ["=="] = DEPRECATEOP (operator, "==", "eq"), - ["~="] = DEPRECATEOP (operator, "~=", "neq"), + ["[]"] = DEPRECATEOP ("[]", "deref"), + ["+"] = DEPRECATEOP ("+", "sum"), + ["-"] = DEPRECATEOP ("-", "diff"), + ["*"] = DEPRECATEOP ("*", "prod"), + ["/"] = DEPRECATEOP ("/", "quot"), + ["and"] = DEPRECATEOP ("and", "conj"), + ["or"] = DEPRECATEOP ("or", "disj"), + ["not"] = DEPRECATEOP ("not", "neg"), + ["=="] = DEPRECATEOP ("==", "eq"), + ["~="] = DEPRECATEOP ("~=", "neq"), } return M From 5e98864b40541d612db80a647f344b7f3da788f6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Aug 2014 23:30:26 +0100 Subject: [PATCH 417/703] package: tidy up LDocs, and remove unnecessary M references. * lib/std/package.lua (pathsub, find): Remove spurious `M.` prefixes. (mappath_callback): Rename from this... (mappathcb): ...to this. * HACKING: Add LDoc style notes. Signed-off-by: Gary V. Vaughan --- HACKING | 14 ++++++++++++++ lib/std/package.lua | 24 ++++++++++++------------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/HACKING b/HACKING index b002f09..c71ed27 100644 --- a/HACKING +++ b/HACKING @@ -1,3 +1,5 @@ +## Lua + - 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 @@ -33,3 +35,15 @@ n a number s a string t a table + +## LDocs + + - `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`. diff --git a/lib/std/package.lua b/lib/std/package.lua index 003de16..f47cd9a 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -38,9 +38,9 @@ local dirsep, pathsep, path_mark, execdir, igmark = local function pathsub (path) return path:gsub ("%%?.", function (capture) if capture == "?" then - return M.path_mark + return path_mark elseif capture == "/" then - return M.dirsep + return dirsep else return capture:gsub ("^%%", "", 1) end @@ -49,7 +49,7 @@ end local function find (pathstrings, patt, init, plain) - local paths = split (pathstrings, M.pathsep) + 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 @@ -116,14 +116,14 @@ local function X (decl, fn) end M = { - --- Look for a path segment match of `patt` in `pathstrings`. + --- 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` + -- @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` + -- @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 @@ -133,7 +133,7 @@ M = { --- 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 + -- @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 @@ -144,9 +144,9 @@ M = { --- Call a function with each element of a path string. -- @function mappath -- @string pathstrings a `package.path` like string - -- @tparam mappath_callback callback function to call for each element - -- @param ... additional arguments passed to `callback` - -- @return nil, or first non-nil returned by `callback` + -- @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), @@ -191,7 +191,7 @@ return M -- @section Types --- Function signature of a callback for @{mappath}. --- @function mappath_callback +-- @function mappathcb -- @string element an element from a `pathsep` delimited string of -- paths -- @param ... additional arguments propagated from @{mappath} From 86620ac0a5d789bcb413f39a074b661616133601 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Aug 2014 18:05:18 +0100 Subject: [PATCH 418/703] refactor: make all monkey_patch functions work the same. * specs/std_spec.yaml, specs/io_spec.yaml, specs/math_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml (monkey_patch): Specify injection of all exported apis into the given namespace. * lib/std/base.lua (copy): Support an optional `dest` argument. (merge): Like copy, but don't overwrite pre-existing entries at the same key. * lib/std.lua.in, lib/std/io.lua, lib/std/math.lua, lib/std/string.lua, lib/std/table.lua: Use merge and copy to simplify tracking and injecting monkey_patches. * specs/std_specl.yaml (barrel): Specify behaviour of running all monkey_patch functions, and recreating the legacy global api by additionally injecting those functions. * lib/std.lua.in (barrel): Update to meet tighter specifications. * HACKING: Note about global hygiene and use of monkey_patch (). * NEWS: Update. Signed-off-by: Gary V. Vaughan --- HACKING | 6 +++ NEWS | 6 +++ lib/std.lua.in | 43 ++++++++++---------- lib/std/base.lua | 17 +++++--- lib/std/io.lua | 25 +++++++----- lib/std/math.lua | 15 +++---- lib/std/string.lua | 17 ++++---- lib/std/table.lua | 22 +++++----- specs/io_spec.yaml | 33 +++++++++------ specs/math_spec.yaml | 16 ++++---- specs/std_spec.yaml | 91 +++++++++++++++++++++++------------------- specs/string_spec.yaml | 35 +++++++++------- specs/table_spec.yaml | 33 +++++++++------ 13 files changed, 203 insertions(+), 156 deletions(-) diff --git a/HACKING b/HACKING index c71ed27..9740ad1 100644 --- a/HACKING +++ b/HACKING @@ -1,5 +1,11 @@ ## Lua + - Requiring any stdlib module must not leak any symbols into the + global namespace. To help users who want to do that, there are + monkey_patch functions in the relevant modules. For convenience + when writing throw-away scripts, there's also `std.barrel()`, to + replicate the behaviour of pre-hygienic stdlib. + - 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 diff --git a/NEWS b/NEWS index 5bdc8aa..59fba0e 100644 --- a/NEWS +++ b/NEWS @@ -222,6 +222,12 @@ Stdlib NEWS - User visible changes ** 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 diff --git a/lib/std.lua.in b/lib/std.lua.in index 29e7e95..936d9a0 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -25,18 +25,11 @@ local base = require "std.base" -local M +local M, monkeys -local function monkey_patch (namespace) - namespace = namespace or _G - - for n, fn in pairs (M) do - if type (fn) == "function" and n ~= "barrel" and n ~= "monkey_patch" then - namespace[n] = fn - end - end - return M +local function monkey_patch (namespace) + return base.copy (namespace or _G, monkeys) end @@ -44,26 +37,28 @@ local function barrel (namespace) namespace = namespace or _G -- Older releases installed the following into _G by default. - for v in base.ielems { + for _, name in pairs { "functional.bind", "functional.collect", "functional.compose", "functional.curry", "functional.filter", "functional.id", - "functional.map", "functional.op", + "functional.map", "io.die", "io.warn", "string.pickle", "string.prettytostring", "string.render", - "string.require_version", - "table.metamethod", "table.pack", "table.totable", + "table.pack", "table.totable", "tree.ileaves", "tree.inodes", "tree.leaves", "tree.nodes", } do - local module, method = v:match "^(.*)%.(.-)$" + local module, method = name:match "^(.*)%.(.-)$" namespace[method] = M[module][method] end - -- Support fold, even though we renamed to reduce in v41. + -- Support old api names, for backwards compatibility. namespace.fold = M.functional.reduce + namespace.metamethod = M.getmetamethod + namespace.op = M.operator + namespace.require_version = M.require require "std.io".monkey_patch (namespace) require "std.math".monkey_patch (namespace) @@ -88,8 +83,6 @@ local function X (decl, fn) end M = { - version = "General Lua libraries / @VERSION@", - --- Enhance core `assert` to also allow formatted arguments. -- @function assert -- @param expect expression, expected to be *truthy* @@ -123,14 +116,14 @@ M = { -- @see pairs -- @usage -- for value in std.elems {a = 1, b = 2, c = 5} do process (value) end - elems = X ("elems (table)", base.elems), + elems = X ("elems (table)", base.elems), --- Evaluate a string as Lua code. -- @function eval -- @string s string of Lua code -- @return result of evaluating `s` -- @usage std.eval "math.pow (2, 10)" - eval = X ("eval (string)", base.eval), + eval = X ("eval (string)", base.eval), --- An iterator over the integer keyed elements of a sequence. -- If *t* has a `__len` metamethod, iterate up to the index it returns. @@ -241,9 +234,19 @@ M = { -- -- {1=baz,foo=bar} -- print (std.tostring {foo="bar","baz"}) tostring = X ("tostring (any?)", base.tostring), + + version = "General Lua libraries / @VERSION@", } +monkeys = base.copy ({}, M) + +-- Don't monkey_patch these apis into _G! +for _, api in ipairs {"barrel", "monkey_patch", "version"} do + monkeys[api] = nil +end + + --- Metamethods -- @section Metamethods diff --git a/lib/std/base.lua b/lib/std/base.lua index b5cff18..384058b 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -121,10 +121,10 @@ local function pairs (t) end -local function copy (t) - local r = {} - for k, v in pairs (t) do r[k] = v end - return r +local function copy (dest, src) + if src == nil then dest, src = {}, dest end + for k, v in pairs (src) do dest[k] = v end + return dest end @@ -214,6 +214,12 @@ local function leaves (it, tr) end +local function merge (dest, src) + for k, v in pairs (src) do dest[k] = dest[k] or v end + return dest +end + + local function prototype (o) return (getmetatable (o) or {})._type or io.type (o) or type (o) end @@ -350,7 +356,8 @@ end return { - copy = copy, + copy = copy, + merge = merge, -- std.lua -- assert = assert, diff --git a/lib/std/io.lua b/lib/std/io.lua index cbccecf..e85a64c 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -23,7 +23,7 @@ local split = base.split local dirsep = string.match (package.config, "^(%S+)\n") -local M +local M, monkeys local function input_handle (h) @@ -74,12 +74,16 @@ end local function monkey_patch (namespace) namespace = namespace or _G + namespace.io = base.copy (namespace.io or {}, monkeys) - local file_metatable = getmetatable (namespace.io.stdin) - file_metatable.readlines = M.readlines - file_metatable.writelines = M.writelines + if namespace.io.stdin then + local mt = getmetatable (namespace.io.stdin) or {} + mt.readlines = M.readlines + mt.writelines = M.writelines + setmetatable (namespace.io.stdin, mt) + end - return M + return namespace.io end @@ -164,9 +168,9 @@ M = { -- @usage die ("oh noes! (%s)", tostring (obj)) die = X ("die (string, any?*)", function (...) warn (...); error () end), - --- Overwrite core methods and metamethods with `std` enhanced versions. + --- Overwrite core `io` methods with `std` enhanced versions. -- - -- Adds @{readlines} and @{writelines} metamethods to core file objects. + -- Also adds @{readlines} and @{writelines} metamethods to core file objects. -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the `std.io` module table @@ -255,11 +259,10 @@ M = { } -for k, v in pairs (io) do - M[k] = M[k] or v -end +monkeys = base.copy ({}, M) -- before deprecations and core merge + -return M +return base.merge (M, io) diff --git a/lib/std/math.lua b/lib/std/math.lua index 5b54524..7dfaed1 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,6 +11,7 @@ ]] +local base = require "std.base" local debug = require "std.debug" local M @@ -30,8 +31,8 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.math.floor = M.floor - return M + namespace.math = base.copy (namespace.math or {}, M) + return namespace.math end @@ -61,9 +62,7 @@ M = { -- @usage tenths = floor (magnitude, 1) floor = X ("floor (number, int?)", floor), - --- Overwrite core methods with `std` enhanced versions. - -- - -- Replaces core `math.floor` with `std.math` version. + --- Overwrite core `math` methods with `std` enhanced versions. -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table @@ -80,8 +79,4 @@ M = { } -for k, v in pairs (math) do - M[k] = M[k] or v -end - -return M +return base.merge (M, math) diff --git a/lib/std/string.lua b/lib/std/string.lua index d22207d..187a20f 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -76,11 +76,14 @@ end local function monkey_patch (namespace) + namespace = namespace or _G + namespace.string = base.copy (namespace.string or {}, M) + local string_metatable = getmetatable "" string_metatable.__concat = M.__concat string_metatable.__index = M.__index - return M + return namespace.string end @@ -345,12 +348,10 @@ M = { ltrim = X ("ltrim (string, string?)", function (s, r) return s:gsub ("^" .. (r or "%s+"), "") end), - --- Overwrite core methods and metamethods with `std` enhanced versions. + --- Overwrite core `string` methods with `std` enhanced versions. -- - -- Adds auto-stringification to `..` operator on core strings, and + -- Also adds auto-stringification to `..` operator on core strings, and -- integer indexing of strings with `[]` dereferencing. - -- - -- Also replaces core `tostring` functions with `std.string` version. -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table @@ -500,11 +501,7 @@ M.tostring = DEPRECATED ("41", "'std.string.tostring'", -for k, v in pairs (string) do - M[k] = M[k] or v -end - -return M +return base.merge (M, string) diff --git a/lib/std/table.lua b/lib/std/table.lua index 2003011..bb30be4 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,6 +11,8 @@ ]] +local core = _G.table + local base = require "std.base" local debug = require "std.debug" @@ -20,7 +22,7 @@ local ipairs, pairs = base.ipairs, base.pairs local len = base.len -local M +local M, monkeys local function merge_allfields (t, u, map, nometa) @@ -167,8 +169,9 @@ end local function monkey_patch (namespace) - namespace.table.sort = M.sort - return M + namespace = namespace or _G + namespace.table = base.copy (namespace.table or {}, monkeys) + return namespace.table end @@ -390,9 +393,7 @@ M = { -- @usage table.concat (sort (object)) sort = X ("sort (table, function?)", sort), - --- Overwrite core methods with `std` enhanced versions. - -- - -- Replaces core `table.sort` with `std.table` version. + --- Overwrite core `table` methods with `std` enhanced versions. -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table @@ -415,6 +416,9 @@ M = { } +monkeys = base.copy ({}, M) -- before deprecations and core merge + + --[[ ============= ]]-- --[[ Deprecations. ]]-- --[[ ============= ]]-- @@ -443,11 +447,7 @@ M.ripairs = DEPRECATED ("41", "'std.table.ripairs'", -for k, v in pairs (table) do - M[k] = M[k] or v -end - -return M +return base.merge (M, table) diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 8365edc..228a025 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -143,27 +143,34 @@ specify std.io: - describe monkey_patch: - before: - mt = {} - t = { - io = { - stdin = setmetatable ({}, mt), - stdout = setmetatable ({}, mt), - stderr = setmetatable ({}, mt), - }, - } + namespace = {} - f, badarg = init (M, this_module, "monkey_patch") + f, badarg = init (M, this_module, "monkey_patch") - it diagnoses wrong argument types: expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: expect (f (t, false)).to_raise (badarg (2)) - - it installs readlines metamethod: - f (t) + - it returns the monkey_patched io entry from namespace: + namespace = {} + expect (f (namespace)).to_be (namespace.io) + - it injects std.io apis into the given namespace: + namespace = {} + f (namespace) + for _, api in ipairs (extend_base) do + expect (namespace.io[api]).to_be (M[api]) + end + - it installs file methods: + mt = { "file metatable" } + io = f { + io = { + stdin = setmetatable ({ "stdin" }, mt), + stdout = setmetatable ({ "stdout" }, mt), + stderr = setmetatable ({ "stderr" }, mt) + } + } expect (mt.readlines).to_be (M.readlines) - - it installs writelines metamethod: - f (t) expect (mt.writelines).to_be (M.writelines) diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 46d5ac8..441d392 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -58,10 +58,6 @@ specify std.math: - describe monkey_patch: - before: - t = { - math = {}, - } - f, badarg = init (M, this_module, "monkey_patch") - it diagnoses wrong argument types: | @@ -69,9 +65,15 @@ specify std.math: - it diagnoses too many arguments: expect (f (t, false)).to_raise (badarg (2)) - - it installs math.floor function: - f (t) - expect (t.math.floor).to_be (M.floor) + - it returns the monkey_patched math entry namespace: + namespace = {} + expect (f (namespace)).to_be (namespace.math) + - it injects std.math apis into the given namespace: + namespace = {} + f (namespace) + for _, api in ipairs (extend_base) do + expect (namespace.math[api]).to_be (M[api]) + end - describe round: diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index f70c4af..6a9e1b1 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -81,87 +81,94 @@ specify std: - describe barrel: - before: - io_mt = {} - t = { + mt = { "file metatable" } + namespace = { io = { - stdin = setmetatable ({}, io_mt), - stdout = setmetatable ({}, io_mt), - stderr = setmetatable ({}, io_mt), + stdin = setmetatable ({}, mt), + stdout = setmetatable ({}, mt), + stderr = setmetatable ({}, mt), }, - math = {}, - table = {}, } f, badarg = init (M, this_module, "barrel") - f (t) + f (namespace) - it diagnoses wrong argument types: expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: expect (f ({}, false)).to_raise (badarg (2)) + - it installs std monkey patches: + for _, api in ipairs (exported_apis) do + if type (M[api]) == "function" and + api ~= "barrel" and api ~= "monkey_patch" + then + expect (namespace[api]).to_be (M[api]) + end + end - it installs std.io monkey patches: - expect (io_mt.readlines).to_be (M.io.readlines) - expect (io_mt.writelines).to_be (M.io.writelines) - - it installs std.lua monkey patches: - expect (t.assert).to_be (M.assert) - expect (t.elems).to_be (M.elems) - expect (t.eval).to_be (M.eval) - expect (t.ielems).to_be (M.ielems) - expect (t.ipairs).to_be (M.ipairs) - expect (t.pairs).to_be (M.pairs) - expect (t.require).to_be (M.require) + for _, api in ipairs { "catdir", "catfile", "die", "monkey_patch", + "process_files", "readlines", "shell", "slurp", "splitdir", "warn", + "writelines" } + do + expect (namespace.io[api]).to_be (M.io[api]) + end + expect (mt.readlines).to_be (M.io.readlines) + expect (mt.writelines).to_be (M.io.writelines) - it installs std.math monkey patches: - expect (t.math.floor).to_be (M.math.floor) + for _, api in ipairs { "floor", "monkey_patch", "round" } do + expect (namespace.math[api]).to_be (M.math[api]) + end - it installs std.string monkey patches: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" - expect (mt.__append).to_be (M.string.__append) expect (mt.__concat).to_be (M.string.__concat) expect (mt.__index).to_be (M.string.__index) + + for _, api in ipairs { "__concat", "__index", "caps", "chomp", + "escape_pattern", "escape_shell", "finds", "format", "ltrim", + "monkey_patch", "numbertosi", "ordinal_suffix", "pad", "pickle", + "prettytostring", "render", "rtrim", "split", "tfind", "trim", + "wrap" } + do + expect (namespace.string[api]).to_be (M.string[api]) + end - it installs std.table monkey patches: - expect (t.table.sort).to_be (M.table.sort) - - it scribbles into the supplied namespace: - expect (t).should_equal { - assert = M.assert, + for _, api in ipairs { "clone", "clone_select", "depair", "empty", + "enpair", "flatten", "insert", "invert", "keys", "len", "merge", + "merge_select", "monkey_patch", "new", "pack", "project", + "shape", "size", "sort", "totable", "values" } + do + expect (namespace.table[api]).to_be (M.table[api]) + end + - it scribbles backwards compatibility apis: + for api, fn in pairs { bind = M.functional.bind, - case = M.case, collect = M.functional.collect, compose = M.functional.compose, curry = M.functional.curry, die = M.io.die, - elems = M.elems, - eval = M.eval, filter = M.functional.filter, fold = M.functional.reduce, - getmetamethod = M.getmetamethod, id = M.functional.id, - ielems = M.ielems, ileaves = M.tree.ileaves, inodes = M.tree.inodes, - io = t.io, - ipairs = M.ipairs, - ireverse = M.ireverse, leaves = M.tree.leaves, map = M.functional.map, - math = t.math, - metamethod = M.table.metamethod, + metamethod = M.getmetamethod, nodes = M.tree.nodes, - op = M.functional.op, + op = M.operator, pack = M.table.pack, - pairs = M.pairs, pickle = M.string.pickle, prettytostring = M.string.prettytostring, render = M.string.render, - require = M.require, - require_version = M.string.require_version, - ripairs = M.ripairs, - table = t.table, - tostring = M.tostring, + require_version = M.require, totable = M.table.totable, warn = M.io.warn, - } + } do + expect (namespace[api]).to_be (fn) + end - describe elems: - before: diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index deafe62..20b3e2d 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -4,12 +4,12 @@ before: global_table = "_G" extend_base = { "__concat", "__index", - "assert", "caps", "chomp", "escape_pattern", - "escape_shell", "finds", "format", "ltrim", - "monkey_patch", "numbertosi", "ordinal_suffix", - "pad", "pickle", "prettytostring", "render", - "require_version", "rtrim", "split", - "tfind", "tostring", "trim", "wrap" } + "caps", "chomp", "escape_pattern", "escape_shell", + "finds", "format", "ltrim", "monkey_patch", + "numbertosi", "ordinal_suffix", "pad", "pickle", + "prettytostring", "render", "rtrim", "split", + "tfind", "trim", "wrap" } + deprecations = { "assert", "require_version", "tostring" } M = require (this_module) getmetatable ("").__concat = M.__concat @@ -28,8 +28,12 @@ specify std.string: expect (show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core string table: + apis = require "std.base".copy (extend_base) + for _, v in ipairs (deprecations) do + apis[#apis + 1] = v + end expect (show_apis {from=base_module, not_in=this_module}). - to_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (apis) - context via the std module: - it does not touch the global table: @@ -304,21 +308,24 @@ specify std.string: - before: f, badarg = init (M, this_module, "monkey_patch") - t = {} - f (t) - - it diagnoses wrong argument types: expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: expect (f (t, false)).to_raise (badarg (2)) - - it installs concat metamethod: + - it returns the monkey_patched namespace: + namespace = {} + expect (f (namespace)).to_be (namespace.string) + - it injects std.string apis into given namespace: + namespace = {} + f (namespace) + for _, api in ipairs (extend_base) do + expect (namespace.string[api]).to_be (M[api]) + end + - it installs string metamethods: # FIXME: string metatable monkey-patches leak out! mt = getmetatable "" expect (mt.__concat).to_be (M.__concat) - - it installs index metamethod: - # FIXME: string metatable monkey-patches leak out! - mt = getmetatable "" expect (mt.__index).to_be (M.__index) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 386aa6c..ae5e750 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -3,11 +3,12 @@ before: | this_module = "std.table" global_table = "_G" - extend_base = { "clone", "clone_rename", "clone_select", "depair", - "empty", "enpair", "flatten", "insert", "invert", - "keys", "len", "merge", "merge_select", "metamethod", - "monkey_patch", "new", "pack", "project", "ripairs", - "shape", "size", "sort", "totable", "values" } + extend_base = { "clone", "clone_select", "depair", "empty", + "enpair", "flatten", "insert", "invert", "keys", + "len", "merge", "merge_select", "monkey_patch", + "new", "pack", "project", "shape", "size", "sort", + "totable", "values" } + deprecations = { "clone_rename", "metamethod", "ripairs" } M = require "std.table" @@ -21,8 +22,12 @@ specify std.table: expect (show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core table table: + apis = require "std.base".copy (extend_base) + for _, v in ipairs (deprecations) do + apis[#apis + 1] = v + end expect (show_apis {from=base_module, not_in=this_module}). - to_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (apis) - context via the std module: - it does not touch the global table: @@ -484,18 +489,20 @@ specify std.table: - before: f, badarg = init (M, this_module, "monkey_patch") - t = { - table = {}, - } - f (t) - - it diagnoses wrong argument types: expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - it diagnoses too many arguments: expect (f ({}, false)).to_raise (badarg (2)) - - it installs table.sort function: - expect (t.table.sort).to_be (M.sort) + - it returns the monkey_patched table entry from namespace: + namespace = {} + expect (f (namespace)).to_be (namespace.table) + - it injects std.table apis into given namespace: + namespace = {} + f (namespace) + for _, api in ipairs (extend_base) do + expect (namespace.table[api]).to_be (M[api]) + end - describe new: From c9a2e7911bae2f84279f27c4f9f65b7310c6235d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Aug 2014 18:57:56 +0100 Subject: [PATCH 419/703] table: ensure there is always a maxn function. * specs/table_spec.yaml (maxn): Specify behaviour of maxn. (len): Add missing specifications. * specs/std_spec.yaml (barrel): Add maxn to list of monkey_patch apis from std.table. * lib/std/base.lua (maxn): Use core table.maxn if available, or else define our own. * lib/std/container.lua, lib/std/debug.lua: Use it! * lib/std/table.lua (maxn): Export it. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 3 +++ lib/std/base.lua | 10 +++++++++ lib/std/container.lua | 3 +-- lib/std/debug.lua | 3 +-- lib/std/table.lua | 19 ++++++++++------ specs/std_spec.yaml | 4 ++-- specs/table_spec.yaml | 52 ++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 78 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index 59fba0e..a73dbc7 100644 --- a/NEWS +++ b/NEWS @@ -136,6 +136,9 @@ Stdlib NEWS - User visible changes calculate default *pos* parameter, as well as diagnosing out of bounds *pos* parameters consistently on any supported version of Lua. + - New `table.maxn` is available when Lua compiled without + compatibility, but uses the core implementation otherwise. + ** Deprecations: - Deprecated APIs are kept for a minimum of 1 year following the first diff --git a/lib/std/base.lua b/lib/std/base.lua index 384058b..41dac6b 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -214,6 +214,15 @@ local function leaves (it, tr) end +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 + + local function merge (dest, src) for k, v in pairs (src) do dest[k] = dest[k] or v end return dest @@ -396,6 +405,7 @@ return { insert = insert, last = last, len = len, + maxn = maxn, -- tree.lua -- leaves = leaves, diff --git a/lib/std/container.lua b/lib/std/container.lua index a69d980..3436ac5 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -71,10 +71,9 @@ local base = require "std.base" local debug = require "std.debug" local ipairs, pairs = base.ipairs, base.pairs -local insert, len = base.insert, base.len +local insert, len, maxn = base.insert, base.len, base.maxn local prototype = base.prototype local argcheck = debug.argcheck -local maxn = table.maxn diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 8e45f57..f563ef1 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -34,10 +34,9 @@ local base = require "std.base" local _ARGCHECK = debug_init._ARGCHECK local _DEBUG = debug_init._DEBUG -local maxn = table.maxn local argerror = base.argerror local split, tostring = base.split, base.tostring -local insert, last, len = base.insert, base.last, base.len +local insert, last, len, maxn = base.insert, base.last, base.len, base.maxn local ipairs, pairs = base.ipairs, base.pairs local M diff --git a/lib/std/table.lua b/lib/std/table.lua index bb30be4..5ef7fa0 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -307,6 +307,11 @@ M = { -- @usage for i = 1, len (t) do process (t[i]) end len = X ("len (table)", base.len), + --- Largest integer key in a table. + -- @tparam table t a table + -- @treturn int largest integer key in *t* + maxn = X ("maxn (table)", base.maxn), + --- Destructively merge another table's fields into another. -- @function merge -- @tparam table t destination table @@ -335,6 +340,13 @@ M = { merge_select = X ("merge_select (table, table, [table], boolean|:nometa?)", merge_namedfields), + --- Overwrite core `table` methods with `std` enhanced versions. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage local table = require "std.table".monkey_patch () + monkey_patch = X ("monkey_patch (table?)", monkey_patch), + --- Make a table with a default value for unset keys. -- @function new -- @param[opt=nil] x default entry value @@ -393,13 +405,6 @@ M = { -- @usage table.concat (sort (object)) sort = X ("sort (table, function?)", sort), - --- Overwrite core `table` methods with `std` enhanced versions. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage local table = require "std.table".monkey_patch () - monkey_patch = X ("monkey_patch (table?)", monkey_patch), - --- Turn an object into a table according to `__totable` metamethod. -- @function totable -- @tparam object|table|string x object to turn into a table diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 6a9e1b1..5da30aa 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -136,8 +136,8 @@ specify std: end - it installs std.table monkey patches: for _, api in ipairs { "clone", "clone_select", "depair", "empty", - "enpair", "flatten", "insert", "invert", "keys", "len", "merge", - "merge_select", "monkey_patch", "new", "pack", "project", + "enpair", "flatten", "insert", "invert", "keys", "len", "maxn", + "merge", "merge_select", "monkey_patch", "new", "pack", "project", "shape", "size", "sort", "totable", "values" } do expect (namespace.table[api]).to_be (M.table[api]) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index ae5e750..9547bd7 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -5,9 +5,9 @@ before: | extend_base = { "clone", "clone_select", "depair", "empty", "enpair", "flatten", "insert", "invert", "keys", - "len", "merge", "merge_select", "monkey_patch", - "new", "pack", "project", "shape", "size", "sort", - "totable", "values" } + "len", "maxn", "merge", "merge_select", + "monkey_patch", "new", "pack", "project", + "shape", "size", "sort", "totable", "values" } deprecations = { "clone_rename", "metamethod", "ripairs" } M = require "std.table" @@ -333,6 +333,52 @@ specify std.table: expect (f (subject)).not_to_equal (subject) +- describe len: + - before: + f, badarg = init (M, this_module, "len") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_raise (badarg (2)) + + - it returns the length 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 elements after a hole: + expect (f {1, 2, [5]=3}).to_be (2) + - it respects __len metamethod: + t = setmetatable ({1, 2, [5]=3}, {__len = function () return 42 end}) + expect (f (t)).to_be (42) + + +- describe maxn: + - before: + f, badarg = init (M, this_module, "maxn") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_raise (badarg (2)) + + - 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 From a4f12450b3052df3560c54ef7e29c183beaeb14e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Aug 2014 21:11:42 +0100 Subject: [PATCH 420/703] refactor: modules can only require std.base and std.debug. * lib/std/table.lua (totable): Move implementation from here... * lib/std/base.lua (totable): ...to here. * lib/std/string.lua: Import core implementation from std.base instead of argcheck wrapper from std.table. * lib/std/io.lua (dirsep, catfile): Move implementations from here... * lib/std/string.lua (escape_pattern): ...here... * lib/std/table.lua (invert): ... and here... * lib/std/base.lua (dirsep, catfile, invert): ...to here. * lib/std/package.lua: Import core implementations from std.base instead eof argcheck wrappers from user interface files. * lib/std/list.lua, lib/std/tree.lua (func): Remove spurious require. * lib/std/set.lua, lib/std/tree.lua, lib/std/vector.lua (container): Fold into Container prototype constructor. * lib/std/list.lua, lib/std/strbuf.lua (object): Fold into Object prototype constructor. * lib/std/string.lua (strbuf): Fold into StrBuf prototype constructor. * lib/std/math.lua (debug): Fold into X definition. * HACKING: Add a description of how to use `require` idiomatically in stdlib source. Signed-off-by: Gary V. Vaughan --- HACKING | 19 +++++++++++++++++ lib/std/base.lua | 51 +++++++++++++++++++++++++++++++++++++++++++-- lib/std/io.lua | 5 ++--- lib/std/list.lua | 4 +--- lib/std/math.lua | 3 +-- lib/std/package.lua | 5 ++--- lib/std/set.lua | 4 ++-- lib/std/strbuf.lua | 3 +-- lib/std/string.lua | 13 +++--------- lib/std/table.lua | 32 ++-------------------------- lib/std/tree.lua | 6 ++---- lib/std/vector.lua | 3 +-- 12 files changed, 85 insertions(+), 63 deletions(-) diff --git a/HACKING b/HACKING index 9740ad1..6739280 100644 --- a/HACKING +++ b/HACKING @@ -6,6 +6,25 @@ when writing throw-away scripts, there's also `std.barrel()`, to replicate the behaviour of pre-hygienic stdlib. + - 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 diff --git a/lib/std/base.lua b/lib/std/base.lua index 41dac6b..a5a12d2 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,6 +23,9 @@ ]] +local dirsep = string.match (package.config, "^(%S+)\n") + + local function argerror (name, i, extramsg, level) level = level or 1 local s = string.format ("bad argument #%d to '%s'", i, name) @@ -57,6 +60,11 @@ local function callable (x) end +local function catfile (...) + return table.concat ({...}, dirsep) +end + + -- Lua < 5.2 doesn't call `__len` automatically! local function len (t) local m = getmetamethod (t, "__len") @@ -154,6 +162,11 @@ local function elems (t) end +local function escape_pattern (s) + return s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") +end + + local function eval (s) return loadstring ("return " .. s)() end @@ -189,6 +202,15 @@ local function insert (t, pos, v) end +local function invert (t) + local i = {} + for k, v in pairs (t) do + i[v] = k + end + return i +end + + -- Be careful not to compact holes from `t` when reversing. local function ireverse (t) local r, tlen = {}, len (t) @@ -363,6 +385,22 @@ local function tostring (x) end +local function totable (x) + local m = getmetamethod (x, "__totable") + if m then + return m (x) + elseif type (x) == "table" then + return x + elseif type (x) == "string" then + local t = {} + x:gsub (".", function (c) t[#t + 1] = c end) + return t + else + return nil + end +end + + return { copy = copy, @@ -390,22 +428,31 @@ return { nop = function () end, reduce = reduce, + -- io.lua -- + catfile = catfile, + -- list.lua -- compare = compare, -- object.lua -- prototype = prototype, + -- package.lua -- + dirsep = dirsep, + -- string.lua -- - render = render, - split = split, + escape_pattern = escape_pattern, + render = render, + split = split, -- table.lua -- getmetamethod = getmetamethod, insert = insert, + invert = invert, last = last, len = len, maxn = maxn, + totable = totable, -- tree.lua -- leaves = leaves, diff --git a/lib/std/io.lua b/lib/std/io.lua index e85a64c..778a3d4 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -15,12 +15,12 @@ local base = require "std.base" local debug = require "std.debug" local argerror = debug.argerror +local dirsep = base.dirsep local ipairs, pairs = base.ipairs, base.pairs local insert, len = base.insert, base.len local leaves = base.leaves local split = base.split -local dirsep = string.match (package.config, "^(%S+)\n") local M, monkeys @@ -155,8 +155,7 @@ M = { -- @see catdir -- @see splitdir -- @usage filepath = catfile ("relative", "path", "filename") - catfile = X ("catfile (string*)", - function (...) return table.concat ({...}, dirsep) end), + catfile = X ("catfile (string*)", base.catfile), --- Die with error. -- This function uses the same rules to build a message prefix diff --git a/lib/std/list.lua b/lib/std/list.lua index 762ea26..2e866e5 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -28,10 +28,8 @@ local base = require "std.base" local debug = require "std.debug" -local func = require "std.functional" -local object = require "std.object" -local Object = object {} +local Object = require "std.object" {} local ipairs, pairs = base.ipairs, base.pairs local len = base.len diff --git a/lib/std/math.lua b/lib/std/math.lua index 7dfaed1..b197c21 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -12,7 +12,6 @@ local base = require "std.base" -local debug = require "std.debug" local M @@ -49,7 +48,7 @@ end local function X (decl, fn) - return debug.argscheck ("std.math." .. decl, fn) + return require "std.debug".argscheck ("std.math." .. decl, fn) end diff --git a/lib/std/package.lua b/lib/std/package.lua index f47cd9a..d8f36ab 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -13,10 +13,9 @@ local base = require "std.base" local debug = require "std.debug" -local catfile = require "std.io".catfile -local invert = require "std.table".invert -local escape_pattern = require "std.string".escape_pattern +local catfile, escape_pattern, invert = + base.catfile, base.escape_pattern, base.invert local ipairs, pairs, split = base.ipairs, base.pairs, base.split local M diff --git a/lib/std/set.lua b/lib/std/set.lua index 4be577b..c4a61ee 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -12,9 +12,9 @@ ]] local base = require "std.base" -local container = require "std.container" -local Container = container {} +local Container = require "std.container" {} + local ielems, pairs, prototype = base.ielems, base.pairs, base.prototype diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 1d33d5d..86e743c 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -4,9 +4,8 @@ ]] local base = require "std.base" -local object = require "std.object" -local Object = object {} +local Object = require "std.object" {} local insert = base.insert diff --git a/lib/std/string.lua b/lib/std/string.lua index 187a20f..189dea4 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -12,16 +12,14 @@ local base = require "std.base" local debug = require "std.debug" -local strbuf = require "std.strbuf" -local table = require "std.table" -local StrBuf = strbuf {} +local StrBuf = require "std.strbuf" {} local getmetamethod = base.getmetamethod local insert, len = base.insert, base.len local pairs = base.pairs local render = base.render -local totable = table.totable +local totable = base.totable local M @@ -92,11 +90,6 @@ local function caps (s) end -local function escape_pattern (s) - return s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") -end - - local function escape_shell (s) return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) end @@ -305,7 +298,7 @@ M = { -- @string s any string -- @treturn string *s* with active pattern characters escaped -- @usage substr = inputstr:match (escape_pattern (literal)) - escape_pattern = X ("escape_pattern (string)", escape_pattern), + escape_pattern = X ("escape_pattern (string)", base.escape_pattern), --- Escape a string to be used as a shell token. -- Quotes spaces, parentheses, brackets, quotes, apostrophes and diff --git a/lib/std/table.lua b/lib/std/table.lua index 5ef7fa0..bc96009 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -78,15 +78,6 @@ local function flatten (t) end -local function invert (t) - local i = {} - for k, v in pairs (t) do - i[v] = k - end - return i -end - - local function keys (t) local l = {} for k in pairs (t) do @@ -96,7 +87,6 @@ local function keys (t) end - local function new (x, t) return setmetatable (t or {}, {__index = function (t, i) @@ -175,24 +165,6 @@ local function monkey_patch (namespace) end -local getmetamethod = base.getmetamethod - -local function totable (x) - local m = getmetamethod (x, "__totable") - if m then - return m (x) - elseif type (x) == "table" then - return x - elseif type (x) == "string" then - local t = {} - x:gsub (".", function (c) t[#t + 1] = c end) - return t - else - return nil - end -end - - local function values (t) local l = {} for _, v in pairs (t) do @@ -290,7 +262,7 @@ M = { -- @tparam table t a table with `{k=v, ...}` -- @treturn table inverted table `{v=k, ...}` -- @usage values = invert (t) - invert = X ("invert (table)", invert), + invert = X ("invert (table)", base.invert), --- Make the list of keys in table. -- @function keys @@ -410,7 +382,7 @@ M = { -- @tparam object|table|string x object to turn into a table -- @treturn table resulting table or `nil` -- @usage print (table.concat (totable (object))) - totable = X ("totable (object|table|string)", totable), + totable = X ("totable (object|table|string)", base.totable), --- Make the list of values of a table. -- @function values diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 4f45b4d..44488b7 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -12,16 +12,14 @@ ]] local base = require "std.base" -local container = require "std.container" -local func = require "std.functional" local operator = require "std.operator" -local Container = container {} +local Container = require "std.container" {} local ielems, ipairs, base_leaves, pairs, prototype = base.ielems, base.ipairs, base.leaves, base.pairs, base.prototype local last, len = base.last, base.len -local reduce = func.reduce +local reduce = base.reduce local Tree -- forward declaration diff --git a/lib/std/vector.lua b/lib/std/vector.lua index ed47454..d7d30ba 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -35,10 +35,9 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local have_alien, alien = pcall (require, "alien") local base = require "std.base" -local container = require "std.container" local debug = require "std.debug" -local Container = container {} +local Container = require "std.container" {} local typeof = type From efcee2454de303336e217f46f26717d324adceb2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Aug 2014 22:12:54 +0100 Subject: [PATCH 421/703] table: add remove for orthogonality with insert. * specs/table_spec.yaml (remove): Specify expected behaviour. * lib/std/table.lua (remove): Implement specified behaviors. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 +++++--- lib/std/table.lua | 22 ++++++++++++++++++++++ specs/table_spec.yaml | 42 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index a73dbc7..ba07480 100644 --- a/NEWS +++ b/NEWS @@ -132,12 +132,14 @@ Stdlib NEWS - User visible changes - 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` returns its result, and uses `table.len` to + - 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. - - New `table.maxn` is available when Lua compiled without - compatibility, but uses the core implementation otherwise. + - `table.insert` returns the modified table. + + - New `table.maxn` is available even when Lua compiled without + compatibility, but uses the core implementation when possible. ** Deprecations: diff --git a/lib/std/table.lua b/lib/std/table.lua index bc96009..7e76a58 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -16,6 +16,7 @@ local core = _G.table local base = require "std.base" local debug = require "std.debug" +local argerror = debug.argerror local collect = base.collect local leaves = base.leaves local ipairs, pairs = base.ipairs, base.pairs @@ -165,6 +166,18 @@ local function monkey_patch (namespace) end +local _remove = table.remove + +local function remove (t, pos) + local lent = len (t) + pos = pos or lent + if pos < math.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 function values (t) local l = {} for _, v in pairs (t) do @@ -340,6 +353,15 @@ M = { -- @treturn table list of *fkey* fields from *tt* project = X ("project (any, list of tables)", project), + --- 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 + -- @treturn removed value, or else `nil` + remove = X ("remove (table, int?)", remove), + --- Shape a table according to a list of dimensions. -- -- Dimensions are given outermost first and items from the original diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 9547bd7..e560703 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -7,7 +7,8 @@ before: | "enpair", "flatten", "insert", "invert", "keys", "len", "maxn", "merge", "merge_select", "monkey_patch", "new", "pack", "project", - "shape", "size", "sort", "totable", "values" } + "remove", "shape", "size", "sort", "totable", + "values" } deprecations = { "clone_rename", "metamethod", "ripairs" } M = require "std.table" @@ -636,6 +637,45 @@ specify std.table: expect (f ("first", l)).to_equal {false, 1, "1st"} +- describe remove: + - before: + f, badarg = init (M, this_module, "remove") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + - it diagnoses out of bounds pos arguments: + 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" + - it diagnoses too many arguments: + expect (f ({}, 1, false)).to_raise (badarg (3)) + + - 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 ripairs: - before: f = M.ripairs From e6ee6903ccb0ee2dd8a4019ddf764226b71eb280 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 29 Aug 2014 00:09:55 +0100 Subject: [PATCH 422/703] doc: overhaul LDocs for std.table. * lib/std/table.lua: Tidy up LDocs, and add @usage examples. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index 7e76a58..4b01ced 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -233,9 +233,12 @@ M = { --- Turn a list of pairs into a table. -- @todo Find a better name. -- @function depair - -- @tparam table ls list of lists `{{i1, v1}, ..., {in, vn}}` - -- @treturn table a new list containing table `{i1=v1, ..., in=vn}` + -- @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. @@ -244,6 +247,9 @@ M = { -- @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. @@ -257,6 +263,9 @@ M = { -- @function flatten -- @tparam table t a table -- @treturn table a list of all non-table elements of *t* + -- @usage + -- --> {1, 2, 3, 4, 5} + -- flatten {{1, {{2}, 3}, 4}, 5} flatten = X ("flatten (table)", flatten), --- Enhance core *table.insert* to return its result. @@ -268,13 +277,18 @@ M = { -- @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)", base.insert), --- Invert a table. -- @function invert -- @tparam table t a table with `{k=v, ...}` -- @treturn table inverted table `{v=k, ...}` - -- @usage values = invert (t) + -- @usage + -- --> {a=1, b=2, c=3} + -- invert {"a", "b", "c"} invert = X ("invert (table)", base.invert), --- Make the list of keys in table. @@ -293,8 +307,12 @@ M = { len = X ("len (table)", base.len), --- 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)", base.maxn), --- Destructively merge another table's fields into another. @@ -344,6 +362,9 @@ M = { -- @function pack -- @param ... tuple -- @return list + -- @usage + -- --> {1, 2, "ax"} + -- pack (("ax1"):find "(%D+)") pack = function (...) return {...} end, --- Project a list of fields from a list of tables. @@ -351,6 +372,9 @@ M = { -- @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), --- Enhance core *table.remove* to respect `__len` when *pos* is omitted. @@ -359,7 +383,11 @@ M = { -- @function remove -- @tparam table t a table -- @int[opt=len (t)] pos index from which to remove an element - -- @treturn removed value, or else `nil` + -- @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), --- Shape a table according to a list of dimensions. @@ -382,13 +410,18 @@ M = { -- @tparam table dims table of dimensions `{d1, ..., dn}` -- @tparam table t a table of elements -- @return reshaped list + -- @usage + -- --> {{"a", "b"}, {"c", "d"}, {"e", "f"}} + -- shape ({3, 2}, {"a", "b", "c", "d", "e", "f"}) shape = X ("shape (table, table)", shape), --- 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 count = size {foo = true, bar = true, baz = false} + -- @usage + -- --> 3 + -- size {foo = true, bar = true, baz = false} size = X ("size (table)", size), --- Enhance core *table.sort* to return its result. @@ -411,6 +444,9 @@ M = { -- @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), } From e208a94f6369a7371c5b354e718bcf671596a438 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 30 Aug 2014 16:43:26 +0100 Subject: [PATCH 423/703] table: deprecate totable. Closes #74. * specs/container_spec.yaml (tablification): Remove specs. * specs/object_spec.yaml (copy): Use this new function instead of `totable`. * specs/object_spec.yaml, specs/set_spec.yaml, specs/strbuf_spec.yaml, specs/tree_spec.yaml (__totable): Remove. * specs/table_spec.yaml (totable): Deprecate this function. * specs/spec_helper.lua (totable): Remove. * lib/std/table.lua (totable): Likewise. * lib/std/base.lua (totable): Remove implementation. * lib/std.lua.in (barrel): Remove "table.totable". * lib/std/container.lua (__totable): Remove. (__pairs): Return elements in order, omitting private elements. (__tostring): Use it to concatenate elements in order. (__call, instantiate, mapfields): Iterate with `next` to fetch raw elements, rather than ordered object __pairs elements. * lib/std/object (__totable): Remove reference. * lib/std/set.lua (__totable): Remove. (__tostring): Fetch keys for display using std.pairs. * lib/std/string.lua (pickle): Likewise. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 15 ++++- lib/std.lua.in | 2 +- lib/std/base.lua | 17 ----- lib/std/container.lua | 130 +++++++++++++++++--------------------- lib/std/object.lua | 23 +++---- lib/std/set.lua | 18 +++--- lib/std/string.lua | 4 +- lib/std/table.lua | 25 ++++++-- specs/container_spec.yaml | 12 ---- specs/object_spec.yaml | 41 +++++------- specs/set_spec.yaml | 15 ----- specs/spec_helper.lua | 17 ----- specs/std_spec.yaml | 3 +- specs/strbuf_spec.yaml | 12 ---- specs/table_spec.yaml | 14 ++-- specs/tree_spec.yaml | 8 --- 16 files changed, 134 insertions(+), 222 deletions(-) diff --git a/NEWS b/NEWS index ba07480..772702a 100644 --- a/NEWS +++ b/NEWS @@ -121,7 +121,9 @@ Stdlib NEWS - User visible changes Lua 5.1. All of stdlib's implementation now uses `std.pairs` rather than `pairs` - internally. + 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. @@ -225,6 +227,8 @@ Stdlib NEWS - User visible changes - `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 @@ -270,6 +274,15 @@ Stdlib NEWS - User visible changes 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: diff --git a/lib/std.lua.in b/lib/std.lua.in index 936d9a0..1150d05 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -46,7 +46,7 @@ local function barrel (namespace) "string.pickle", "string.prettytostring", "string.render", - "table.pack", "table.totable", + "table.pack", "tree.ileaves", "tree.inodes", "tree.leaves", "tree.nodes", } do diff --git a/lib/std/base.lua b/lib/std/base.lua index a5a12d2..a1215b8 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -385,22 +385,6 @@ local function tostring (x) end -local function totable (x) - local m = getmetamethod (x, "__totable") - if m then - return m (x) - elseif type (x) == "table" then - return x - elseif type (x) == "string" then - local t = {} - x:gsub (".", function (c) t[#t + 1] = c end) - return t - else - return nil - end -end - - return { copy = copy, @@ -452,7 +436,6 @@ return { last = last, len = len, maxn = maxn, - totable = totable, -- tree.lua -- leaves = leaves, diff --git a/lib/std/container.lua b/lib/std/container.lua index 3436ac5..3c15840 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -95,11 +95,17 @@ local argcheck = debug.argcheck -- @treturn table a new table with fields from proto and t merged in. local function instantiate (proto, t) local obj = {} - for k, v in pairs (proto) do + local k, v = next (proto) + while k do obj[k] = v + k, v = next (proto, k) end - for k, v in pairs (t or {}) do + + t = t or {} + k, v = next (t) + while k do obj[k] = v + k, v = next (t, k) end return obj end @@ -136,16 +142,6 @@ end --[[ ================= ]]-- ---- Return `obj` with references to the fields of `src` merged in. --- @function mapfields --- @static --- @tparam table obj destination object --- @tparam table src fields to copy into clone --- @tparam[opt={}] table map `{old_key=new_key, ...}` --- @treturn table *obj* with non-private fields from *src* merged, and --- a metatable with private fields (if any) merged, both sets of keys --- renamed according to *map* --- @see std.object.mapfields local function mapfields (obj, src, map) local mt = getmetatable (obj) or {} @@ -154,17 +150,20 @@ local function mapfields (obj, src, map) -- when map is provided (i.e. if `map == {}`, copy nothing). if map == nil or next (map) then map = map or {} - for k, v in pairs (src) do + local k, v = next (src) + while k do local key, dst = map[k] or k, obj local kind = type (key) if kind == "string" and key:sub (1, 1) == "_" then - dst = mt - elseif kind == "number" and len (dst) + 1 < key then + mt[key] = v + elseif next (map) and kind == "number" and len (dst) + 1 < key then -- When map is given, but has fewer entries than src, stop copying -- fields when map is exhausted. break + else + dst[key] = v end - dst[key] = v + k, v = next (src, k) end end @@ -173,8 +172,11 @@ local function mapfields (obj, src, map) mt._functions = nil -- Inject module functions. - for k, v in pairs (src._functions or {}) do + local t = src._functions or {} + local k, v = next (t) + while (k) do obj[k] = modulefunction (v) + k, v = next (t, k) end -- Only set non-empty metatable. @@ -185,16 +187,6 @@ local function mapfields (obj, src, map) end ---- Return a clone of this container. --- @function __call --- @param x a table if prototype `_init` is a table, otherwise first --- argument for a function type `_init` --- @param ... any additional arguments for `_init` --- @treturn std.container a clone of the called container. --- @see std.object:__call --- @usage --- local Container = require "std.container" {} -- not a typo! --- local new = Container {"init", {"elements"}, 2, "insert"} local function __call (self, x, ...) local mt = getmetatable (self) local obj_mt = mt @@ -204,10 +196,12 @@ local function __call (self, x, ...) -- a lot of fields to test and copy. If you need to clone a lot of -- objects from a prototype with several module functions, it's much -- faster to clone objects from each other than the prototype! - for k, v in pairs (self) do + local k, v = next (self) + while (k) do if type (v) ~= "table" or v._type ~= "modulefunction" then obj[k] = v end + k, v = next (self, k) end if type (mt._init) == "function" then @@ -270,56 +264,50 @@ else end ---- Return a string representation of this container. --- @function __tostring --- @treturn string stringified container representation --- @see std.object.__tostring --- @usage print (acontainer) -function M.__tostring (self) - local totable = getmetatable (self).__totable - local array = instantiate (totable (self)) - local other = instantiate (array) - local s = "" - if len (other) > 0 then - for i in ipairs (other) do other[i] = nil end - end - for k in pairs (other) do array[k] = nil end - for i, v in ipairs (array) do array[i] = tostring (v) end - - local keys, dict = {}, {} - for k in pairs (other) do insert (keys, k) end - table.sort (keys, function (a, b) return tostring (a) < tostring (b) end) - for _, k in ipairs (keys) do - insert (dict, tostring (k) .. "=" .. tostring (other[k])) - end - - if len (array) > 0 then - s = s .. table.concat (array, ", ") - if next (dict) ~= nil then s = s .. "; " end - end - if len (dict) > 0 then - s = s .. table.concat (dict, ", ") +function M.__pairs (self) + local keys = {} + local k = next (self) + while k do + keys[#keys + 1] = k + k = next (self, k) end - return prototype (self) .. " {" .. s .. "}" + -- Sort numbers first then asciibetically + table.sort (keys, function (a, b) + if type (a) == "number" then + return type (b) ~= "number" or a < b + else + return type (b) ~= "number" and tostring (a) < tostring (b) + end + end) + + local n, lenkeys = 0, #keys + return function (t, k) + n = n + 1 + if n <= lenkeys then + local key = keys[n] + return key, self[key] + end + end, self, nil end ---- Return a table representation of this container. --- @function __totable --- @treturn table a shallow copy of non-private container fields --- @see std.object:__totable --- @usage --- local tostring = require "std.string".tostring --- print (totable (acontainer)) -function M.__totable (self) - local t = {} +function M.__tostring (self) + local n, ibuf, kbuf = 1, {}, {} for k, v in pairs (self) do - if type (k) ~= "string" or k:sub (1, 1) ~= "_" then - t[k] = v + if type (k) == "number" and k == n then + ibuf[#ibuf + 1] = tostring (v) + n = n + 1 + else + kbuf[#kbuf + 1] = tostring (k) .. "=" .. tostring (v) end end - return t + + local buf = {} + if next (ibuf) then buf[#buf + 1] = table.concat (ibuf, ", ") end + if next (kbuf) then buf[#buf + 1] = table.concat (kbuf, ", ") end + + return prototype (self) .. " {" .. table.concat (buf, "; ") .. "}" end @@ -344,5 +332,5 @@ return setmetatable ({ __call = M.__call, __tostring = M.__tostring, - __totable = M.__totable, + __pairs = M.__pairs, }) diff --git a/lib/std/object.lua b/lib/std/object.lua index cb15d77..b2f6e8b 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -215,6 +215,14 @@ return Container { -- new = Object {"initialisation", "elements"} + --- Return an in-order iterator over public object fields. + -- @function __pairs + -- @treturn function iterator function + -- @treturn object *self* + -- @usage + -- for k, v in std.pairs (anobject) do process (k, v) end + + --- Return a string representation of this object. -- -- First the object type, and then between { and } a list of the @@ -224,20 +232,7 @@ return Container { -- This function doesn't recurse explicity, but relies upon suitable -- `__tostring` metamethods in field values. -- @function __tostring - -- @treturn string stringified container representation + -- @treturn string stringified object representation -- @see tostring -- @usage print (anobject) - - - --- Return a shallow copy of non-private object fields. - -- - -- Used by @{clone} to get the base contents of the new object. Can - -- be overridden in other objects for greater control of which fields - -- are considered non-private. - -- @function __totable - -- @treturn table a shallow copy of non-private object fields - -- @see std.table.totable - -- @usage - -- tostring = require "std.string".tostring - -- print (totable (anobject)) } diff --git a/lib/std/set.lua b/lib/std/set.lua index c4a61ee..ee0eb06 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -279,16 +279,16 @@ Set = Container { __lt = proper_subset, - -- Set to table conversion. - -- @treturn table table representation of a set. - -- @see std.table.totable - __totable = function (self) - local t = {} - for e in elems (self) do - t[#t + 1] = e + -- Return a string representation of this set. + -- @treturn string string representation of a set. + -- @see std.tostring + __tostring = function (self) + local keys = {} + for k in pairs (self) do + keys[#keys + 1] = tostring (k) end - table.sort (t) - return t + table.sort (keys) + return prototype (self) .. " {" .. table.concat (keys, ", ") .. "}" end, diff --git a/lib/std/string.lua b/lib/std/string.lua index 189dea4..3e20d79 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -15,11 +15,11 @@ local debug = require "std.debug" local StrBuf = require "std.strbuf" {} +local copy = base.copy local getmetamethod = base.getmetamethod local insert, len = base.insert, base.len local pairs = base.pairs local render = base.render -local totable = base.totable local M @@ -233,7 +233,7 @@ local function pickle (x) type (x) == "nil" then return tostring (x) else - x = totable (x) or x + x = copy (x) or x if type (x) == "table" then local s, sep = "{", "" for i, v in pairs (x) do diff --git a/lib/std/table.lua b/lib/std/table.lua index 4b01ced..deacbe4 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -432,13 +432,6 @@ M = { -- @usage table.concat (sort (object)) sort = X ("sort (table, function?)", sort), - --- Turn an object into a table according to `__totable` metamethod. - -- @function totable - -- @tparam object|table|string x object to turn into a table - -- @treturn table resulting table or `nil` - -- @usage print (table.concat (totable (object))) - totable = X ("totable (object|table|string)", base.totable), - --- Make the list of values of a table. -- @function values -- @tparam table t any table @@ -481,6 +474,24 @@ M.ripairs = DEPRECATED ("41", "'std.table.ripairs'", "use 'std.ripairs' instead", base.ripairs) +M.totable = DEPRECATED ("41", "'std.table.totable'", + "use 'std.pairs' instead", + function (x) + local m = base.getmetamethod (x, "__totable") + if m then + return m (x) + elseif type (x) == "table" then + return x + elseif type (x) == "string" then + local t = {} + x:gsub (".", function (c) t[#t + 1] = c end) + return t + else + return nil + end + end) + + return base.merge (M, table) diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 05e20d6..7d57fe1 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -114,15 +114,3 @@ specify std.container: not_to_contain ";" expect (tostring (things {one = true, two = true, three = true})). to_contain ";" - - -- describe tablification: - - before: - totable = require "std.table".totable - Derived = Container {_type = "Derived", "one", "two", three = true} - - it returns a table: - expect (prototype (totable (Derived))).to_be "table" - - it contains all non-hidden fields of container: - expect (totable (Derived)).to_contain.all_of {"one", "two", "three"} - - it does not contain any hidden fields of container: - expect (totable (Derived)).to_equal {"one", "two", three = true} diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 036fab2..a4d6603 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -3,6 +3,12 @@ before: obj = Object {"foo", "bar", baz="quux"} prototype = Object.prototype + function copy (t) + local r = {} + for k, v in pairs (t) do r[k] = v end + return r + end + specify std.object: - context when required: - context by name: @@ -86,9 +92,6 @@ specify std.object: - describe instantiation from a prototype: - - before: - totable = require "std.table".totable - - context when _init is nil: - before: Array = Object { @@ -96,20 +99,21 @@ specify std.object: "foo", "bar", "baz", } Array._init = nil + - it contains user-defined fields: - expect (totable (Array)). + expect (copy (Array)). to_equal {"foo", "bar", "baz"} - it sets array part of instance object from positional parameters: array = Array {"first", "second", "third"} - expect (totable (array)). + expect (copy (array)). to_equal {"first", "second", "third"} - it uses prototype values for missing positional parameters: array = Array {"first", "second"} - expect (totable (array)). + expect (copy (array)). to_equal {"first", "second", "baz"} - it merges surplas positional parameters: array = Array {"first", "second", "third", "fourth"} - expect (totable (array)). + expect (copy (array)). to_equal {"first", "second", "third", "fourth"} - context when _init is an empty table: @@ -120,7 +124,7 @@ specify std.object: "first", "second", "third", } - it contains user-defined fields: - expect (totable (Prototype)). + expect (copy (Prototype)). to_equal {"first", "second", "third"} - it ignores positional parameters: | instance = Prototype {"foo", "bar"} @@ -136,19 +140,19 @@ specify std.object: errout = "no errors", } - it contains user-defined fields: - expect (totable (Process)). + expect (copy (Process)). to_equal {status = -1, output = "empty", errout = "no errors"} - it sets user-defined fields from positional parameters: proc = Process {0, "output", "diagnostics"} - expect (totable (proc)). + expect (copy (proc)). to_equal {status = 0, output = "output", errout = "diagnostics"} - it uses prototype values for missing positional parameters: proc = Process {0, "output"} - expect (totable (proc)). + expect (copy (proc)). to_equal {status = 0, output = "output", errout = "no errors"} - it discards surplus positional parameters: proc = Process {0, "output", "diagnostics", "garbage"} - expect (totable (proc)). + expect (copy (proc)). to_equal { status = 0, output = "output", errout = "diagnostics" } - context when _init is a function: @@ -276,19 +280,6 @@ specify std.object: expect (a > b).to_be (false) -- describe __totable: - - before: - totable = require "std.table".totable - Derived = Object {_type = "Derived", "one", "two", three = true} - - - it returns a table: - expect (prototype (totable (Derived))).to_be "table" - - it contains all non-hidden fields of object: - expect (totable (Derived)).to_contain.all_of {"one", "two", "three"} - - it does not contain any hidden fields of object: - expect (totable (Derived)).to_equal {"one", "two", three = true} - - - describe __tostring: - before: obj = Object {_type = "Derived", "one", "two", "three"} diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index 817abff..d4b8c46 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -1,7 +1,6 @@ before: Set = require "std.set" prototype = require "std.object".prototype - totable = require "std.table".totable s = Set {"foo", "bar", "bar"} specify std.set: @@ -307,20 +306,6 @@ specify std.set: to_equal (Set {"foo", "bar", "baz", "quux"}) -- describe __totable: - - before: - s = Set {"foo", "bar", "baz"} - - - it returns a table: - expect (prototype (totable (s))).to_be "table" - - it contains all non-hidden fields of object: - expect (totable (s)).to_contain.all_of {"foo", "bar", "baz"} - - it contains fields of set in order: - expect (totable (s)).to_equal {"bar", "baz", "foo"} - - it does not contain any hidden fields of object: - expect (totable (s)).to_equal {"bar", "baz", "foo"} - - - describe __tostring: - before: s = Set {"foo", "bar", "baz"} diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 1d260e9..2acea40 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -213,23 +213,6 @@ function show_apis (argt) end --- Not local, so that it is available to spec examples. -function totable (x) - local _, m = pcall (function (x) return getmetatable (x)["__totable"] end, x) - if type (m) == "function" then - return m (x) - elseif type (x) == "table" then - return x - elseif type (x) == "string" then - local t = {} - x:gsub (".", function (c) t[#t + 1] = c end) - return t - else - return nil - end -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 diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 5da30aa..aedb5f5 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -138,7 +138,7 @@ specify std: for _, api in ipairs { "clone", "clone_select", "depair", "empty", "enpair", "flatten", "insert", "invert", "keys", "len", "maxn", "merge", "merge_select", "monkey_patch", "new", "pack", "project", - "shape", "size", "sort", "totable", "values" } + "shape", "size", "sort", "values" } do expect (namespace.table[api]).to_be (M.table[api]) end @@ -164,7 +164,6 @@ specify std: prettytostring = M.string.prettytostring, render = M.string.render, require_version = M.require, - totable = M.table.totable, warn = M.io.warn, } do expect (namespace[api]).to_be (fn) diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index efdff04..6cbf72e 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -71,15 +71,3 @@ specify std.strbuf: b = b .. "baz" expect (object.type (b)).to_be "StrBuf" expect (tostring (b)).to_be "foobarbaz" - - -- describe __totable: - - before: - totable = (require "std.table").totable - - - it returns a table: - expect (object.type (totable (b))).to_be "table" - - it contains all non-hidden fields of object: - expect (totable (b)).to_contain.all_of {"foo", "bar"} - - it does not contain any hidden fields of object: - expect (totable (b)).to_equal {"foo", "bar"} diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index e560703..022b7dd 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -7,9 +7,8 @@ before: | "enpair", "flatten", "insert", "invert", "keys", "len", "maxn", "merge", "merge_select", "monkey_patch", "new", "pack", "project", - "remove", "shape", "size", "sort", "totable", - "values" } - deprecations = { "clone_rename", "metamethod", "ripairs" } + "remove", "shape", "size", "sort", "values" } + deprecations = { "clone_rename", "metamethod", "ripairs", "totable" } M = require "std.table" @@ -784,12 +783,9 @@ specify std.table: f, badarg = init (M, this_module, "totable") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "object, table or string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "object, table or string", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - it writes a deprecation warning on first call: + expect (capture (f, {{}})).to_contain_error "was deprecated" + expect (capture (f, {{}})).not_to_contain_error "was deprecated" - it calls object's __totable metamethod: object = setmetatable ({content = t}, mt) diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 6b1b70e..025d7fb 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -7,7 +7,6 @@ before: | specify std.tree: - before: prototype = (require "std.object").prototype - totable = (require "std.table").totable t = {foo="foo", fnord={branch={bar="bar", baz="baz"}}, quux="quux"} tr = Tree (t) @@ -397,13 +396,6 @@ specify std.tree: tr[{"foo", "branch", "baz"}] = "leaf2" expect (tr).to_equal (Tree {foo=Tree {branch=Tree {bar="leaf1", baz="leaf2"}}}) -- describe __totable: - - it returns a table: - expect (prototype (totable (tr))).to_be "table" - - it contains all non-hidden fields of object: - expect (totable (tr)).to_contain. - all_of {"foo", branch={bar="bar", baz="baz"}, "quux"} - - describe __tostring: - it returns a string: expect (prototype (tostring (tr))).to_be "string" From e56fba727b060c5394a3f1f1614a4bbb5f575a9e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 12:44:54 +0100 Subject: [PATCH 424/703] debug: argcheck accepts objects as valid table type arguments. Closes #84. Really, table is the base type of object (or strictly speaking, container), and its almost always desirable to be able to pass objects to functions that operate on tables. * specs/debug_spec.yaml (argcheck): Specify this behaviour when an object argument is given where a table is required. * lib/std/debug.lua (argcheck): Accept any object argument where a table parameter is expected. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 41 ++++++++++++++++++----------------------- specs/debug_spec.yaml | 4 ++-- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index f563ef1..86f814c 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -352,34 +352,38 @@ if _ARGCHECK then -- @treturn boolean `true` if *actual* is of type *check*, otherwise -- `false` local function checktype (check, actual) - local actualtype = prototype (actual) - if check == "#table" then - if actualtype == "table" and next (actual) then - return true - end - - elseif check == "any" then - if actual ~= nil then - return true - end + if check == "any" and actual ~= nil then + return true + elseif check == "file" and io.type (actual) == "file" then + return true + end - elseif check == "file" then - if io.type (actual) == "file" then + local actualtype = type (actual) + if check == actualtype then + return true + elseif check == "#table" then + if actualtype == "table" and next (actual) then return true end - elseif check == "function" or check == "func" then if actualtype == "function" or (getmetatable (actual) or {}).__call ~= nil then return true end - elseif check == "int" then if actualtype == "number" and actual == math.floor (actual) then return true end + elseif type (check) == "string" and check:sub (1, 1) == ":" then + if check == actual then + return true + end + end + actualtype = prototype (actual) + if check == actualtype then + return true elseif check == "list" or check == "#list" then if actualtype == "table" or actualtype == "List" then local len, count = len (actual), 0 @@ -392,19 +396,10 @@ if _ARGCHECK then return true end end - elseif check == "object" then if actualtype ~= "table" and type (actual) == "table" then return true end - - elseif type (check) == "string" and check:sub (1, 1) == ":" then - if check == actual then - return true - end - - elseif check == actualtype then - return true end return false diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 8190ca4..75c2fc6 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -247,14 +247,14 @@ specify std.debug: expect (fn ("number", {0})).to_raise "number expected, got table" expect (fn ("string", {0})).to_raise "string expected, got table" expect (fn ("table", false)).to_raise "table expected, got boolean" - expect (fn ("table", require "std.object")). - to_raise "table expected, got Object" - it matches types: expect (fn ("boolean", true)).not_to_raise "any error" expect (fn ("file", io.stderr)).not_to_raise "any error" expect (fn ("number", 1)).not_to_raise "any error" expect (fn ("string", "s")).not_to_raise "any error" expect (fn ("table", {})).not_to_raise "any error" + expect (fn ("table", require "std.object")).not_to_raise "any error" + - context with int: - it diagnoses missing types: expect (fn ("int", nil)).to_raise "int expected, got no value" From c90eef1c2c13b06e932cfaff718a145e03f1ac4f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 12:53:33 +0100 Subject: [PATCH 425/703] doc: fix a broken cross-reference. * lib/std/container.lua (std.container): Now that we're not documenting inherited methods, reference std.object.__call from the prototype object. * HACKING: Document rationale for slimming LDocs in this way. Signed-off-by: Gary V. Vaughan --- HACKING | 9 +++++++++ lib/std/container.lua | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/HACKING b/HACKING index 6739280..19c16b2 100644 --- a/HACKING +++ b/HACKING @@ -72,3 +72,12 @@ - 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 consistently + from a UI perspective even though the implementation needs to be + different to provide that same UI. diff --git a/lib/std/container.lua b/lib/std/container.lua index 3c15840..f53a7cf 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -316,7 +316,7 @@ end -- @string[opt="Container"] _type type of Container, returned by -- @{std.object.prototype} -- @tfield table|function _init a table of field names, or --- initialisation function, used by @{__call} +-- initialisation function, used by @{std.object.__call} -- @tfield[opt=nil] table _functions a table of module functions not copied -- by @{std.object.__call} return setmetatable ({ From 7636c4ff6171198b85cee8ff01273806dd71a1a9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 13:47:28 +0100 Subject: [PATCH 426/703] doc: simplify and clarify container and object LDocs. * lib/std/object.lua, lib/std/container.lua: Simplify and clarify LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 73 +++++++++---------------- lib/std/object.lua | 121 +++++++++++++++++------------------------- 2 files changed, 76 insertions(+), 118 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index f53a7cf..6ca1a9a 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -15,52 +15,18 @@ @{std.object} module functions, or anywhere else a @{std.object} is expected. - Container derived objects returned directly from a `require` statement - may also provide module functions, which can be called only from the - initial prototype object returned by `require`, but are **not** passed - on to derived objects during cloning: - - > container = require "std.container" -- module table - > Container = container {} -- prototype object - > = container:prototype () - Object - > = Container:prototype () - stdin:1: attempt to call field 'prototype' (a nil value) - ... - - To add module functions to your own prototype containers, pass a table - of those module functions in the `_functions` private field before - cloning, and they will not be inherited by subsequent clones. - - > Graph = Container { - >> _type = "Graph", - >> _functions = { - >> nodes = function (graph) - >> local n = 0 - >> for _ in pairs (graph) do n = n + 1 end - >> return n - >> end, - >> }, - >> } - > g = Graph { "node1", "node2" } - > = Graph.nodes (g) - 2 - > = g.nodes - nil - - Cloning from the module table itself is somewhat slower than cloning - derived objects -- due to the time spent skipping over the module - table's `_function` entries by the clone constructor. You can avoid - that overhead by creating an explicit *prototype object*: - - local container = require "std.container" -- module table - local Container = container {} -- prototype object - When making your own prototypes, derive from @{std.container} if you want to access the contents of your objects with the `[]` operator, or from @{std.object} if you want to access the functionality of your objects with named object methods. + Prototype Chain + --------------- + + table + `-> Object + `-> Container + @classmod std.container ]] @@ -313,12 +279,25 @@ end --- Container prototype. -- @table std.container --- @string[opt="Container"] _type type of Container, returned by --- @{std.object.prototype} --- @tfield table|function _init a table of field names, or --- initialisation function, used by @{std.object.__call} --- @tfield[opt=nil] table _functions a table of module functions not copied --- by @{std.object.__call} +-- @see std.object +-- @usage +-- local std = require "std" +-- local Container = std.container {} +-- +-- local Graph = Container { +-- _type = "Graph", +-- _functions = { +-- nodes = function (graph) +-- local n = 0 +-- for _ in std.pairs (graph) do n = n + 1 end +-- return n +-- end, +-- }, +-- } +-- local g = Graph { "node1", "node2" } +-- --> 2 +-- print (Graph.nodes (g)) + return setmetatable ({ -- Normally, these are set and wrapped automatically during cloning. diff --git a/lib/std/object.lua b/lib/std/object.lua index b2f6e8b..866b40d 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -10,64 +10,16 @@ Objects are cloned by simply calling an existing object, which then serves as a prototype from which the new object is copied. - All valid objects contain a field `_init`, which determines the syntax - required to execute the cloning process: - - 1. `_init` can be a list of keys; then the unnamed `init_1` through - `init_m` values from the argument table are assigned to the - corresponding keys in `new_object`; - - new_object = proto_object { - init_1, ..., init_m; - field_1 = value_1, - ... - field_n = value_n, - } - - 2. Or it can be a function, in which the arguments passed to the - prototype during cloning are simply handed to the `_init` function: - - new_object = proto_object (arg, ...) - - Objects, then, are essentially tables of `field\_n = value\_n` pairs: - - > object = require "std.object" -- module table - > Object = object {} -- root object - > o = Object { - >> field_1 = "value_1", - >> method_1 = function (self) return self.field_1 end, - >> } - > = o.field_1 - value_1 - > o.field_2 = 2 - > function o:method_2 (n) return self.field_2 + n end - > = o:method_2 (2) - 4 - - Normally `new_object` automatically shares a metatable with - `proto_object`. However, field names beginning with "_" are *private*, - and moved into the object metatable during cloning. So, adding new - private fields to an object during cloning will result in a new - metatable for `new_object` that also contains a copy of all the entries - in the `proto_object` metatable. - - While clones of @{std.object} inherit all properties of their prototype, - it's idiomatic to always keep separate tables for the module table and - the root object itself: That way you can't mistakenly engage the slower - clone-from-module-table process accidentally if the underlying object - later changes from being an `Object` to being a `Container`. - - local object = require "std.object" -- module table - local Object = object {} -- root object - - local prototype = object.prototype - - local Derived = Object { _type = "Derived" } - Note that Object methods are stored in the `__index` field of their metatable, and so cannot also use `__index` to lookup references with square brackets. See @{std.container} objects if you want to do that. + Prototype Chain + --------------- + + table + `-> Object + @classmod std.object ]] @@ -95,7 +47,21 @@ local getmetamethod, prototype = base.getmetamethod, base.prototype -- @tfield table|function _init a table of field names, or -- initialisation function, used by @{clone} -- @tfield[opt=nil] table _functions a table of module functions not copied --- by @{std.object.__call} +-- by @{__call} +-- @usage +-- -- `_init` can be a list of keys; then the unnamed `init_1` through +-- -- `init_m` values from the argument table are assigned to the +-- -- corresponding keys in `new_object`. +-- new_object = proto_object { +-- init_1, ..., init_m; +-- field_1 = value_1, +-- field_n = value_n, +-- } +-- @usage +-- -- Or it can be a function, in which the arguments passed to the +-- -- prototype during cloning are simply handed to the `_init` function. +-- new_object = proto_object (arg, ...) + return Container { _type = "Object", @@ -105,6 +71,21 @@ return Container { __index = { --- Clone an Object. + -- + -- Objects are essentially tables of `field_n = value_n` pairs. + -- + -- Normally `new_object` automatically shares a metatable with + -- `proto_object`. However, field names beginning with "_" are *private*, + -- and moved into the object metatable during cloning. So, adding new + -- private fields to an object during cloning will result in a new + -- metatable for `new_object` that also happens to contain a copy of all + -- the entries from the `proto_object` metatable. + -- + -- While clones of @{std.object} inherit all properties of their prototype, + -- it's idiomatic to always keep separate tables for the module table and + -- the root object itself: That way you can't mistakenly engage the slower + -- clone-from-module-table process accidentally if the underlying object + -- later changes from being an `Object` to being a `Container`. -- @static -- @function clone -- @tparam std.object obj an object @@ -113,11 +94,19 @@ return Container { -- @treturn std.object a clone of *obj* -- @see __call -- @usage - -- local object = require "std.object" - -- new = object.clone (object, {"foo", "bar"}) + -- local object = require "std.object" -- module table + -- local Object = object {} -- root object + -- local o = Object { + -- field_1 = "value_1", + -- method_1 = function (self) return self.field_1 end, + -- } + -- print (o.field_1) --> value_1 + -- o.field_2 = 2 + -- function o:method_2 (n) return self.field_2 + n end + -- print (o:method_2 (2)) --> 4 + -- os.exit (0) clone = getmetamethod (container, "__call"), - --- Type of an object, or primitive. -- -- It's conventional to organise similar objects according to a @@ -131,7 +120,6 @@ return Container { -- @function prototype -- @param x anything -- @treturn string type of *x* - -- @see std.object:prototype -- @usage -- local Stack = Object { -- _type = "Stack", @@ -153,23 +141,14 @@ return Container { -- assert (prototype (h) == io.type (h)) -- -- assert (prototype {} == type {}) - - --- Type of this object. - -- - -- Additionally, this function returns the results of `io.type` for - -- file objects, or `type` otherwise. - -- @function prototype - -- @treturn string type of this object - -- @see std.object.prototype - -- @usage if anobject:prototype () ~= "table" then ... end prototype = prototype, --- Return `obj` with references to the fields of `src` merged in. -- -- More importantly, split the fields in `src` between `obj` and its - -- metatable. If any field names begin with `\_`, attach a metatable - -- to `obj` if it doesn't have one yet, and copy the "private" `\_` + -- metatable. If any field names begin with `_`, attach a metatable + -- to `obj` if it doesn't have one yet, and copy the "private" `_` -- prefixed fields there. -- -- You might want to use this function to instantiate your derived @@ -207,7 +186,7 @@ return Container { -- -- Private fields are stored in the metatable. -- @function __call - -- @param ... arguments for `\_init` + -- @param ... arguments for `_init` -- @treturn std.object a clone of the this object. -- @see clone -- @usage From 5df1b060bf6a402a40f97dfbdea4cf8a32960cec Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 14:08:33 +0100 Subject: [PATCH 427/703] doc: add prototype chains to object LDocs. * lib/std/list.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/tree.lua, lib/std/vector.lua: LDocument prototype chains. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 10 +++++++--- lib/std/set.lua | 10 ++++++++-- lib/std/strbuf.lua | 8 ++++++++ lib/std/tree.lua | 8 ++++++++ lib/std/vector.lua | 8 ++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 2e866e5..316b5af 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -1,9 +1,6 @@ --[[-- Tables as lists. - Every List is also an Object, and thus inherits all of the `std.object` - methods, particularly use of object cloning for making new List objects. - In addition to calling methods on List objects in OO style... local list = require "std.list" -- module table @@ -22,6 +19,13 @@ => bar => quux + Prototype Chain + --------------- + + table + `-> Object + `-> List + @classmod std.list ]] diff --git a/lib/std/set.lua b/lib/std/set.lua index ee0eb06..a7b73e3 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -1,12 +1,18 @@ --[[-- Set container. - Derived from @{std.container}, and inherits Container's metamethods. - Note that Functions listed below are only available from the Set prototype returned by requiring this module, because Container objects cannot have object methods. + Prototype Chain + --------------- + + table + `-> Object + `-> Container + `-> Set + @classmod std.set @see std.container ]] diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 86e743c..440b911 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -1,5 +1,13 @@ --[[-- String buffers. + + Prototype Chain + --------------- + + table + `-> Object + `-> StrBuf + @classmod std.strbuf ]] diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 44488b7..ae17573 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -7,6 +7,14 @@ prototype return by requiring this module, because Container objects cannot have object methods. + Prototype Chain + --------------- + + table + `-> Object + `-> Container + `-> Tree + @classmod std.tree @see std.container ]] diff --git a/lib/std/vector.lua b/lib/std/vector.lua index d7d30ba..96e09fa 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -27,6 +27,14 @@ of homogenous Lua objects with an array-like, stack-like or queue-like API. + Prototype Chain + --------------- + + table + `-> Object + `-> Container + `-> Vector + @classmod std.vector ]] From 8b69709f423d0150ebe0c4c604963f4593447398 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 20:07:23 +0100 Subject: [PATCH 428/703] doc: use LDoc @object type for std.object. * build-aux/config.ld.in (object): New type for documenting objects. * lib/std/object.lua: Improve documentation. Signed-off-by: Gary V. Vaughan --- build-aux/config.ld.in | 2 + lib/std/object.lua | 118 ++++++++++++++++++++++------------------- 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index c9cd7ff..c88f3c5 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -28,6 +28,8 @@ file = { "../lib/std/vector.lua", } +new_type ("object", "Objects", false, "Fields") + format = "markdown" backtick_references = false sort = true diff --git a/lib/std/object.lua b/lib/std/object.lua index 866b40d..423507f 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -42,25 +42,36 @@ local getmetamethod, prototype = base.getmetamethod, base.prototype -- -- Changing the values of these fields in a new object will change the -- corresponding behaviour. --- @table std.object --- @string[opt="Object"] _type type of Object, returned by @{prototype} --- @tfield table|function _init a table of field names, or --- initialisation function, used by @{clone} --- @tfield[opt=nil] table _functions a table of module functions not copied --- by @{__call} +-- @object Object +-- @string[opt="Object"] _type object name +-- @tfield[opt={}] table|function _init object initialisation +-- @tfield table _functions module functions omitted when cloned +-- @see __call -- @usage -- -- `_init` can be a list of keys; then the unnamed `init_1` through -- -- `init_m` values from the argument table are assigned to the -- -- corresponding keys in `new_object`. --- new_object = proto_object { --- init_1, ..., init_m; --- field_1 = value_1, --- field_n = value_n, +-- local Process = Object { +-- _type = "Process", +-- _init = { "status", "out", "err" }, +-- } +-- local process = Process { +-- procs[pid].status, procs[pid].out, procs[pid].err, -- auto assigned +-- command = pipeline[pid], -- manual assignment -- } -- @usage -- -- Or it can be a function, in which the arguments passed to the -- -- prototype during cloning are simply handed to the `_init` function. --- new_object = proto_object (arg, ...) +-- local Bag = Object { +-- _type = "Bag", +-- _init = function (obj, ...) +-- for e in std.elems {...} do +-- obj[#obj + 1] = e +-- end +-- return obj +-- end, +-- } +-- local bag = Bag ("function", "arguments", "sent", "to", "_init") return Container { _type = "Object", @@ -81,17 +92,16 @@ return Container { -- metatable for `new_object` that also happens to contain a copy of all -- the entries from the `proto_object` metatable. -- - -- While clones of @{std.object} inherit all properties of their prototype, + -- While clones of @{Object} inherit all properties of their prototype, -- it's idiomatic to always keep separate tables for the module table and -- the root object itself: That way you can't mistakenly engage the slower - -- clone-from-module-table process accidentally if the underlying object - -- later changes from being an `Object` to being a `Container`. + -- clone-from-module-table process unnecessarily. -- @static -- @function clone - -- @tparam std.object obj an object - -- @param ... a list of arguments if `obj._init` is a function, or a - -- single table if `obj._init` is a table. - -- @treturn std.object a clone of *obj* + -- @tparam Object obj an object + -- @param ... a list of arguments if *obj.\_init* is a function, or a + -- single table if *obj.\_init* is a table. + -- @treturn Object a clone of *obj* -- @see __call -- @usage -- local object = require "std.object" -- module table @@ -110,65 +120,65 @@ return Container { --- Type of an object, or primitive. -- -- It's conventional to organise similar objects according to a - -- string valued `_type` field, which can then be queried using this + -- string valued *\_type* field, which can then be queried using this -- function. -- - -- Additionally, this function returns the results of `io.type` for - -- file objects, or `type` otherwise. + -- Additionally, this function returns the results of @{io.type} for + -- file objects, or @{type} otherwise. -- -- @static -- @function prototype -- @param x anything -- @treturn string type of *x* -- @usage - -- local Stack = Object { - -- _type = "Stack", + -- local Stack = Object { + -- _type = "Stack", -- - -- __tostring = function (self) ... end, + -- __tostring = function (self) ... end, -- - -- __index = { - -- push = function (self) ... end, - -- pop = function (self) ... end, - -- }, - -- } - -- local stack = Stack {} - -- assert (stack:prototype () == getmetatable (stack)._type) + -- __index = { + -- push = function (self) ... end, + -- pop = function (self) ... end, + -- }, + -- } + -- local stack = Stack {} + -- assert (stack:prototype () == getmetatable (stack)._type) -- - -- local prototype = Object.prototype - -- assert (prototype (stack) == getmetatable (stack)._type) + -- local prototype = Object.prototype + -- assert (prototype (stack) == getmetatable (stack)._type) -- - -- local h = io.open (os.tmpname (), "w") - -- assert (prototype (h) == io.type (h)) + -- local h = io.open (os.tmpname (), "w") + -- assert (prototype (h) == io.type (h)) -- - -- assert (prototype {} == type {}) + -- assert (prototype {} == type {}) prototype = prototype, - --- Return `obj` with references to the fields of `src` merged in. + --- Return *obj* with references to the fields of *src* merged in. -- - -- More importantly, split the fields in `src` between `obj` and its - -- metatable. If any field names begin with `_`, attach a metatable - -- to `obj` if it doesn't have one yet, and copy the "private" `_` - -- prefixed fields there. + -- More importantly, split the fields in *src* between *obj* and its + -- metatable. If any field names begin with "_", attach a metatable + -- to *obj* by cloning the metatable from *src*, and then copy the + -- "private" `_` prefixed fields there. -- -- You might want to use this function to instantiate your derived - -- objct clones when the prototype's `_init` is a function -- when - -- `_init` is a table, the default (inherited unless you overwrite - -- it) clone method calls `mapfields` automatically. When you're - -- using a function `_init` setting, `clone` doesn't know what to + -- object clones when the *src.\_init* is a function -- when + -- *src.\_init* is a table, the default (inherited unless you overwrite + -- it) clone method calls @{mapfields} automatically. When you're + -- using a function `_init` setting, @{clone} doesn't know what to -- copy into a new object from the `_init` function's arguments... - -- so you're on your own. Except that calling `mapfields` inside + -- so you're on your own. Except that calling @{mapfields} inside -- `_init` is safer than manually splitting `src` into `obj` and - -- its metatable, because you'll pick up fixes and changes when you - -- upgrade stdlib. + -- its metatable, because you'll pick up any fixes and changes when + -- you upgrade stdlib. -- @static -- @function mapfields -- @tparam table obj destination object -- @tparam table src fields to copy int clone - -- @tparam[opt={}] table map `{old_key=new_key, ...}` - -- @treturn table `obj` with non-private fields from `src` merged, + -- @tparam[opt={}] table map key renames as `{old_key=new_key, ...}` + -- @treturn table *obj* with non-private fields from *src* merged, -- and a metatable with private fields (if any) merged, both sets - -- of keys renamed according to `map` + -- of keys renamed according to *map* -- @usage -- myobject.mapfields = function (obj, src, map) -- object.mapfields (obj, src, map) @@ -186,8 +196,8 @@ return Container { -- -- Private fields are stored in the metatable. -- @function __call - -- @param ... arguments for `_init` - -- @treturn std.object a clone of the this object. + -- @param ... arguments for prototype's *\_init* + -- @treturn Object a clone of the this object. -- @see clone -- @usage -- local Object = require "std.object" {} -- not a typo! @@ -197,7 +207,7 @@ return Container { --- Return an in-order iterator over public object fields. -- @function __pairs -- @treturn function iterator function - -- @treturn object *self* + -- @treturn Object *self* -- @usage -- for k, v in std.pairs (anobject) do process (k, v) end From 375f14da713a21b13a91f0f6b0b6b572578c94f1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 20:16:43 +0100 Subject: [PATCH 429/703] doc: use LDoc @object type for std.container. * lib/std/container.lua: Improve documentation. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 6ca1a9a..302fb31 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -1,5 +1,5 @@ --[[-- - Container object. + Container prototype. A container is a @{std.object} with no methods. It's functionality is instead defined by its *meta*methods. @@ -278,8 +278,13 @@ end --- Container prototype. --- @table std.container +-- +-- Container also inherits all the fields and methods from +-- @{std.object.Object}. +-- @object Container +-- @string[opt="Container"] _type object name -- @see std.object +-- @see std.object.__call -- @usage -- local std = require "std" -- local Container = std.container {} From f501da4503ade02e1395ac6d65475ef608097f5d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 20:23:24 +0100 Subject: [PATCH 430/703] doc: use LDoc @object type for std.set. * lib/std/set.lua: Improve documentation. Signed-off-by: Gary V. Vaughan --- lib/std/set.lua | 175 ++++++++++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 65 deletions(-) diff --git a/lib/std/set.lua b/lib/std/set.lua index a7b73e3..4515fa6 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -1,5 +1,5 @@ --[[-- - Set container. + Set container prototype. Note that Functions listed below are only available from the Set prototype returned by requiring this module, because Container @@ -39,19 +39,23 @@ local Set -- forward declaration --- Say whether an element is in a set. --- @tparam set set a set +-- @tparam Set set a set -- @param e element --- @return `true` if `e` is in `set`, otherwise `false` +-- @return `true` if *e* is in *set*, otherwise `false` -- otherwise +-- @usage +-- if not set.member (keyset, pressed) then return nil end local function member (set, e) return rawget (set, e) == true end --- Insert an element into a set. --- @tparam set set a set +-- @tparam Set set a set -- @param e element --- @return the modified set +-- @treturn Set the modified *set* +-- @usage +-- for byte = 32,126 do set.insert (isprintable, string.char (byte)) end local function insert (set, e) rawset (set, e, true) return set @@ -59,9 +63,11 @@ end --- Delete an element from a set. --- @tparam set set a set +-- @tparam Set set a set -- @param e element --- @return the modified set +-- @treturn Set the modified *set* +-- @usage +-- set.delete (available, found) local function delete (set, e) rawset (set, e, nil) return set @@ -69,8 +75,10 @@ end --- Iterator for sets. --- @tparam set set a set +-- @tparam Set set a set -- @todo Make the iterator return only the key +-- @usage +-- for code in set.elems (isprintable) do print (code) end local function elems (set) return pairs (set) end @@ -90,9 +98,11 @@ local difference, symmetric_difference, intersection, union, subset, --- Find the difference of two sets. --- @tparam set set1 a set --- @tparam table|set set2 another set, or table --- @return `set1` with elements of s removed +-- @tparam Set set1 a set +-- @tparam table|Set set2 another set, or table +-- @treturn Set a copy of *set1* with elements of *set2* removed +-- @usage +-- all = set.difference (all, {32, 49, 56}) function difference (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -108,9 +118,11 @@ end --- Find the symmetric difference of two sets. --- @tparam set set1 a set --- @tparam table|set set2 another set, or table --- @return elements of `set1` and `set2` that are in `set1` or `set2` but not both +-- @tparam Set set1 a set +-- @tparam table|Set set2 another set, or table +-- @treturn Set a new set with elements that are in *set1* or *set2* but not both +-- @usage +-- unique = set.symmetric_difference (a, b) function symmetric_difference (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -120,9 +132,11 @@ end --- Find the intersection of two sets. --- @tparam set set1 a set --- @tparam table|set set2 another set, or table --- @return set intersection of `set1` and `set2` +-- @tparam Set set1 a set +-- @tparam table|Set set2 another set, or table +-- @treturn Set a new set with elements in both *set1* and *set2* +-- @usage +-- common = set.intersection (a, b) function intersection (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -138,9 +152,11 @@ end --- Find the union of two sets. --- @tparam set set1 a set --- @tparam table|set set2 another set, or table --- @return set union of `set1` and `set2` +-- @tparam Set set1 a set +-- @tparam table|Set set2 another set, or table +-- @treturn Set a copy of *set1* with elements in *set2* merged in +-- @usage +-- all = set.union (a, b) function union (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -157,9 +173,10 @@ end --- Find whether one set is a subset of another. --- @tparam set set1 a set --- @tparam table|set set2 another set, or table --- @return `true` if `set1` is a subset of `set2`, `false` otherwise +-- @tparam Set set1 a set +-- @tparam table|Set set2 another set, or table +-- @treturn boolean `true` if all elements in *set1* are also in *set2*, +-- `false` otherwise function subset (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -174,9 +191,10 @@ end --- Find whether one set is a proper subset of another. --- @tparam set set1 a set --- @tparam table|set set2 another set, or table --- @return `true` if `set1` is a proper subset of `set2`, `false` otherwise +-- @tparam Set set1 a set +-- @tparam table|Set set2 another set, or table +-- @treturn boolean `true` if *set2* contains all elements in *set1* but +-- not only those elements, `false` otherwise function proper_subset (set1, set2) if prototype (set2) == "table" then t = Set (set2) @@ -186,9 +204,12 @@ end --- Find whether two sets are equal. --- @tparam set set1 a set --- @tparam table|set set2 another set, or table --- @return `true` if `set1` and `set2` are equal, `false` otherwise +-- @tparam Set set1 a set +-- @tparam table|Set set2 another set, or table +-- @treturn boolean `true` if *set1* and *set2* each contain identical +-- elements, `false` otherwise +-- @usage +-- if set.equal (keys, {META, CTRL, "x"}) then process (keys) end function equal (set1, set2) return subset (set1, set2) and subset (set2, set1) end @@ -200,14 +221,32 @@ end --[[ =========== ]]-- +--- Signature for cloning Set prototype object. +-- @static +-- @function Set_Clone +-- @tparam table elements a list of additional elements +-- @treturn Set clone of prototype, with *elements* merged in +-- @usage +-- local Set = require "std.set" {} +-- local set_a = Set {1, 2, 3, 4} +-- local set_b = set_a {2, 4, 6, 8} +-- print (set_b) --> Set {1, 2, 3, 4, 6, 8} +-- os.exit (0) + + --- Set prototype object. --- @table std.set --- @string[opt="Set"] _type type of Set, returned by --- @{std.object.prototype} --- @tfield table|function _init a table of field names, or --- initialisation function, see @{std.object.__call} --- @tfield nil|table _functions a table of module functions not copied --- by @{std.object.__call} +-- +-- Set also inherits all the fields and methods from +-- @{std.container.Container}. +-- @object Set +-- @string[opt="Set"] _type object name +-- @tfield Set_Clone _init initialisation function +-- @see std.container +-- @see std.object.__call +-- @usage +-- local std = require "std" +-- std.prototype (std.set) --> "Set" +-- os.exit (0) Set = Container { _type = "Set", @@ -220,68 +259,74 @@ Set = Container { --- Union operator. - -- union = set + table - -- @function __add -- @static - -- @tparam set set set - -- @tparam table|set table another set or table - -- @treturn set union + -- @function __add + -- @tparam Set s a set + -- @tparam table|Set t another set, or table + -- @treturn Set union of *s* and *t* -- @see union + -- @usage + -- union = set + {"table"} __add = union, --- Difference operator. - -- difference = set - table - -- @function __sub -- @static - -- @tparam set set set - -- @tparam table|set table another set or table - -- @treturn set difference + -- @function __sub + -- @tparam Set s a set + -- @tparam table|Set t another set, or table + -- @treturn Set difference between *s* and *t* -- @see difference + -- @usage + -- difference = set - {"table"} __sub = difference, --- Intersection operator. - -- intersection = set * table - -- @function __mul -- @static - -- @tparam set set set - -- @tparam table|set table another set or table - -- @treturn set intersection + -- @function __mul + -- @tparam Set s a set + -- @tparam table|Set t another set, or table + -- @treturn Set intersection of *s* and *t* -- @see intersection + -- @usage + -- intersection = set * {"table"} __mul = intersection, --- Symmetric difference operator. - -- symmetric_difference = set / table -- @function __div -- @static - -- @tparam set set set - -- @tparam table|set table another set or table - -- @treturn set symmetric_difference + -- @tparam Set s a set + -- @tparam table|Set t another set, or table + -- @treturn Set symmetric difference between *s* and *t* -- @see symmetric_difference + -- @usage + -- symmetric_difference = set / {"table"} __div = symmetric_difference, --- Subset operator. - -- set = set <= table - -- @function __le -- @static - -- @tparam set set set - -- @tparam table|set table another set or table - -- @treturn set subset + -- @function __le + -- @tparam Set s a set + -- @tparam table|Set t another set, or table + -- @treturn boolean `true` if *s* is a subset of *t* -- @see subset + -- @usage + -- set = set <= {"table"} __le = subset, --- Proper subset operator. - -- proper_subset = set < table - -- @function __lt -- @static - -- @tparam set set set - -- @tparam table|set table another set or table - -- @treturn set proper_subset + -- @function __lt + -- @tparam Set s set + -- @tparam table|Set t another set or table + -- @treturn boolean `true` if *s* is a proper subset of *t* -- @see proper_subset + -- @usage + -- proper_subset = set < {"table"} __lt = proper_subset, From 5f701ee9cb744d53f7285aab99197eedfeccfebb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 21:40:52 +0100 Subject: [PATCH 431/703] refactor: upgrade OptionParser implementation to a std.object. * lib/std/optparse.lua (OptionParser): Reimplement as a std.object, and simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/optparse.lua | 302 +++++++++++++++++++++++-------------------- 1 file changed, 159 insertions(+), 143 deletions(-) diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index e46bbe3..29ebe93 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -1,74 +1,12 @@ --[=[-- Parse and process command line options. - local OptionParser = require "std.optparse" + Prototype Chain + --------------- - local parser = OptionParser [[ - any text VERSION - Additional lines of text to show when the --version - option is passed. - - Several lines or paragraphs are permitted. - - Usage: PROGNAME - - Banner text. - - Optional long description text to show when the --help - option is passed. - - Several lines or paragraphs of long description are permitted. - - Options: - - -b a short option with no long option - --long a long option with no short option - --another-long a long option with internal hypen - -v, --verbose a combined short and long option - -n, --dryrun, --dry-run several spellings of the same option - -u, --name=USER require an argument - -o, --output=[FILE] accept an optional argument - --version display version information, then exit - --help display this help, then exit - - Footer text. Several lines or paragraphs are permitted. - - Please report bugs at bug-list@yourhost.com - ]] - - _G.arg, _G.opts = parser:parse (_G.arg) - - Most often, everything else is handled automatically. After calling - `parser:parse` as shown above, `_G.arg` will contain unparsed arguments, - usually filenames or similar, and `_G.opts` will be a table of parsed - option values. The keys to the table are the long-options with leading - hyphens stripped, and non-word characters turned to `_`. For example - if `--another-long` had been found in `_G.arg` then `_G.opts` would - have a key named `another_long`. If there is no long option name, then - the short option is used, e.g. `_G.opts.b` will be set. The values - saved in those keys are controlled by the option handler, usually just - `true` or the option argument string as appropriate. - - On those occasions where more complex processing is required, handlers - can be replaced or added using parser:@{on}. A good option to always - add, is to make `--` signal the end of processed options, so that any - options following `--` on the command line, even if they begin with a - hyphen and look like options otherwise, are not processed but instead - left in the modified `_G.arg` returned by `parser:parse`: - - parser:on ('--', parser.finished) - - See the documentation for @{std.optparse:on} for more details of how to - use this powerful method. - - When writing your own handlers for @{std.optparse:on}, you only need - to deal with normalised arguments, because combined short arguments - (`-xyz`), equals separators to long options (`--long=ARG`) are fully - expanded before any handler is called. - - Note that @{std.io.die} and @{std.io.warn} will only prefix messages - with `parser.program` if the parser options are assigned back to - `_G.opts` as shown in the example above. + table + `-> Object + `-> OptionParser @classmod std.optparse ]=] @@ -76,30 +14,11 @@ local base = require "std.base" +local Object = require "std.object" {} + local ipairs, pairs = base.ipairs, base.pairs local insert, last, len = base.insert, base.last, base.len -local OptionParser -- forward declaration - - ------- --- Customized parser for your options. --- --- This table is returned by @{OptionParser}, and most importantly has --- the @{parse} method you call to fill the `opts` table according to --- what command-line options were passed to your program. --- @table parser --- @string program the first word following `Usage:` in @{OptionParser} --- spec string --- @string version the last white-space delimited word on the first line --- of text in the spec string --- @string versiontext everything preceding `Usage:` in the spec string, --- and which will be displayed by the @{version} @{on_handler} --- @string helptext everything including and following `Usage:` in the --- spec string and which will be displayed by the @{help} --- @{on_handler} --- @func parse see @{parse} --- @func on see @{on} --[[ ================= ]]-- @@ -223,10 +142,12 @@ end -- command-line option. -- @static -- @tparam table arglist list of arguments --- @int i index of last processed element of `arglist` +-- @int i index of last processed element of *arglist* -- @param[opt=true] value either a function to process the option -- argument, or a default value if encountered without an optarg --- @treturn int index of next element of `arglist` to process +-- @treturn int index of next element of *arglist* to process +-- @usage +-- parser:on ("--enable-nls", parser.option, parser.boolean) function optional (self, arglist, i, value) if i + 1 <= len (arglist) and arglist[i + 1]:sub (1, 1) ~= "-" then return self:required (arglist, i, value) @@ -267,10 +188,12 @@ end -- {1=(foo bar),2=(foo baz)} -- @static -- @tparam table arglist list of arguments --- @int i index of last processed element of `arglist` +-- @int i index of last processed element of *arglist* -- @param[opt] value either a function to process the option argument, -- or a forced value to replace the user's option argument. --- @treturn int index of next element of `arglist` to process +-- @treturn int index of next element of *arglist* to process +-- @usage +-- parser:on ({"-o", "--output"}, parser.required) function required (self, arglist, i, value) local opt = arglist[i] if i + 1 > len (arglist) then @@ -303,6 +226,8 @@ end -- @tparam table arglist list of arguments -- @int i index of last processed element of `arglist` -- @treturn int index of next element of `arglist` to process +-- @usage +-- parser:on ("--", parser.finished) local function finished (self, arglist, i) for opt = i + 1, len (arglist) do insert (self.unrecognised, arglist[opt]) @@ -326,10 +251,12 @@ end -- times on the command-line. -- @static -- @tparam table arglist list of arguments --- @int i index of last processed element of `arglist` +-- @int i index of last processed element of *arglist* -- @param[opt] value either a function to process the option argument, -- or a value to store when this flag is encountered --- @treturn int index of next element of `arglist` to process +-- @treturn int index of next element of *arglist* to process +-- @usage +-- parser:on ({"--long-opt", "-x"}, parser.flag) local function flag (self, arglist, i, value) local opt = arglist[i] if type (value) == "function" then @@ -349,6 +276,8 @@ end -- `--help` in the specification, e.g. `-h, -?, --help`. -- @static -- @function help +-- @usage +-- parser:on ("-?", parser.version) local function help (self) print (self.helptext) os.exit (0) @@ -361,6 +290,8 @@ end -- `--version` in the specification, e.g. `-V, --version`. -- @static -- @function version +-- @usage +-- parser:on ("-V", parser.version) local function version (self) print (self.versiontext) os.exit (0) @@ -391,16 +322,18 @@ local boolvals = { } ---- Return a Lua boolean equivalent of various `optarg` strings. --- Report an option parse error if `optarg` is not recognised. +--- Return a Lua boolean equivalent of various *optarg* strings. +-- Report an option parse error if *optarg* is not recognised. -- -- Pass this as the `value` function to @{on} when you want various --- *truthy* or *falsey* option arguments to be coerced to a Lua `true` +-- "truthy" or "falsey" option arguments to be coerced to a Lua `true` -- or `false` respectively in the options table. -- @static -- @string opt option name -- @string[opt="1"] optarg option argument, must be a key in @{boolvals} -- @treturn bool `true` or `false` +-- @usage +-- parser:on ("--enable-nls", parser.optional, parser.boolean) local function boolean (self, opt, optarg) if optarg == nil then optarg = "1" end -- default to truthy local b = boolvals[tostring (optarg):lower ()] @@ -411,7 +344,7 @@ local function boolean (self, opt, optarg) end ---- Report an option parse error unless `optarg` names an +--- Report an option parse error unless *optarg* names an -- existing file. -- -- Pass this as the `value` function to @{on} when you want to accept @@ -421,6 +354,8 @@ end -- @string opt option name -- @string optarg option argument, must be an existing file -- @treturn string *optarg* +-- @usage +-- parser:on ("--config-file", parser.required, parser.file) local function file (self, opt, optarg) local h, errmsg = io.open (optarg, "r") if h == nil then @@ -440,7 +375,7 @@ end --- Report an option parse error, then exit with status 2. -- -- Use this in your custom option handlers for consistency with the --- error output from built-in `optparse` error messages. +-- error output from built-in @{std.optparse} error messages. -- @static -- @string msg error message local function opterr (self, msg) @@ -457,9 +392,9 @@ end -- Function signature of an option handler for @{on}. -- @function on_handler -- @tparam table arglist list of arguments --- @int i index of last processed element of `arglist` +-- @int i index of last processed element of *arglist* -- @param[opt=nil] value additional `value` registered with @{on} --- @treturn int index of next element of `arglist` to process +-- @treturn int index of next element of *arglist* to process --- Add an option handler. @@ -471,15 +406,18 @@ end -- calling this function will replace the automatically assigned handler -- with your own. -- --- parser:on ("--", parser.finished) --- parser:on ("-V", parser.version) --- parser:on ("--config-file", parser.required, parser.file) --- parser:on ("--enable-nls", parser.optional, parser.boolean) +-- When writing your own handlers for @{std.optparse:on}, you only need +-- to deal with normalised arguments, because combined short arguments +-- (`-xyz`), equals separators to long options (`--long=ARG`) are fully +-- expanded before any handler is called. -- @function on -- @tparam[string|table] opts name of the option, or list of option names --- @tparam on_handler handler function to call when any of `opts` is +-- @tparam on_handler handler function to call when any of *opts* is -- encountered -- @param value additional value passed to @{on_handler} +-- @usage +-- -- Don't process any arguments after `--` +-- parser:on ('--', parser.finished) local function on (self, opts, handler, value) if type (opts) == "string" then opts = { opts } end handler = handler or flag -- unspecified options behave as flags @@ -550,10 +488,10 @@ end -- @table opts ---- Parse `arglist`. +--- Parse an argument list. -- @tparam table arglist list of arguments -- @tparam[opt] table defaults table of default option values --- @treturn table a list of unrecognised `arglist` elements +-- @treturn table a list of unrecognised *arglist* elements -- @treturn opts parsing results local function parse (self, arglist, defaults) self.unrecognised, self.opts = {}, {} @@ -593,24 +531,6 @@ local function parse (self, arglist, defaults) end ---- @export -local methods = { - boolean = boolean, - file = file, - finished = finished, - flag = flag, - help = help, - optional = optional, - required = required, - version = version, - - on = on, - opterr = opterr, - parse = parse, -} - - - --- Take care not to register duplicate handlers. -- @param current current handler value -- @param new new handler value @@ -621,16 +541,8 @@ local function set_handler (current, new) end ---- Instantiate a new parser. --- Read the documented options from `spec` and return a new parser that --- can be passed to @{parse} for parsing those options from an argument --- list. Options are recognised as lines that begin with at least two --- spaces, followed by a hyphen. --- @static --- @string spec option parsing specification --- @treturn parser a parser for options described by `spec` -function OptionParser (spec) - local parser = setmetatable ({ opts = {} }, { __index = methods }) +local function _init (_, spec) + local parser = {} parser.versiontext, parser.version, parser.helptext, parser.program = spec:match ("^([^\n]-(%S+)\n.-)%s*([Uu]sage: (%S+).-)%s*$") @@ -697,16 +609,120 @@ function OptionParser (spec) end -- Unless specified otherwise, treat each option as a flag. - parser:on (options, handler or flag) + on (parser, options, handler or flag) end return parser end --- Support calling the returned table: -return setmetatable (methods, { - __call = function (_, ...) - return OptionParser (...) - end, -}) +--- Signature for initialising a custom OptionParser. +-- +-- Read the documented options from *spec* and return custom parser that +-- can be used for parsing the options described in *spec* from a run-time +-- argument list. Options in *spec* are recognised as lines that begin +-- with at least two spaces, followed by a hyphen. +-- @static +-- @function OptionParser_Init +-- @string spec option parsing specification +-- @treturn OptionParser a parser for options described by *spec* +-- @usage +-- customparser = std.optparse (optparse_spec) + + +--- OptionParser prototype object. +-- +-- Most often, after instantiating an @{OptionParser}, everything else +-- is handled automatically. +-- +-- Then, calling `parser:parse` as shown below saves unparsed arguments +-- into `_G.arg` (usually filenames or similar), and `_G.opts` will be a +-- table of successfully parsed option values. The keys into this table +-- are the long-options with leading hyphens stripped, and non-word +-- characters turned to `_`. For example if `--another-long` had been +-- found in the initial `_G.arg`, then `_G.opts` will have a key named +-- `another_long`, with an appropriate value. If there is no long +-- option name, then the short option is used, i.e. `_G.opts.b` will be +-- set. +-- +-- The values saved against those keys are controlled by the option +-- handler, usually just `true` or the option argument string as +-- appropriate. +-- @object OptionParser +-- @tparam OptionParser_Init _init initialisation function +-- @string program the first word following "Usage:" from *spec* +-- @string version the last white-space delimited word on the first line +-- of text from *spec* +-- @string versiontext everything preceding "Usage:" from *spec*, and +-- which will be displayed by the @{version} @{on_handler} +-- @string helptext everything including and following "Usage:" from +-- *spec* string and which will be displayed by the @{help} +-- @{on_handler} +-- @usage +-- local std = require "std" +-- +-- local optparser = std.optparse [[ +-- any text VERSION +-- Additional lines of text to show when the --version +-- option is passed. +-- +-- Several lines or paragraphs are permitted. +-- +-- Usage: PROGNAME +-- +-- Banner text. +-- +-- Optional long description text to show when the --help +-- option is passed. +-- +-- Several lines or paragraphs of long description are permitted. +-- +-- Options: +-- +-- -b a short option with no long option +-- --long a long option with no short option +-- --another-long a long option with internal hypen +-- -v, --verbose a combined short and long option +-- -n, --dryrun, --dry-run several spellings of the same option +-- -u, --name=USER require an argument +-- -o, --output=[FILE] accept an optional argument +-- --version display version information, then exit +-- --help display this help, then exit +-- +-- Footer text. Several lines or paragraphs are permitted. +-- +-- Please report bugs at bug-list@yourhost.com +-- ]] +-- +-- -- Note that @{std.io.die} and @{std.io.warn} will only prefix messages +-- -- with `parser.program` if the parser options are assigned back to +-- -- `_G.opts`: +-- _G.arg, _G.opts = optparser:parse (_G.arg) +return Object { + _type = "OptionParser", + + _init = _init, + + -- Prototype initial values. + opts = {}, + helptext = "", + program = "", + versiontext = "", + version = 0, + + --- @export + __index = { + boolean = boolean, + file = file, + finished = finished, + flag = flag, + help = help, + optional = optional, + required = required, + version = version, + + on = on, + opterr = opterr, + parse = parse, + }, +} From afb07601764cdb763059c8212c03fa79829814e4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 22:16:08 +0100 Subject: [PATCH 432/703] doc: use LDoc @object type for std.list * lib/std/list.lua: Improve documentation. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 117 +++++++++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 316b5af..d8bd9ec 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -1,24 +1,6 @@ --[[-- Tables as lists. - In addition to calling methods on List objects in OO style... - - local list = require "std.list" -- module table - local List = list {} -- prototype object - local l = List {"foo", "bar"} - for e in ielems (l:cons ("baz")) do print (e) end - => foo - => bar - => baz - - ...they can also be called as module functions with an explicit list - argument in the first parameter: - - for e in ielems (list.cons (l, "quux")) do print (e) end - => foo - => bar - => quux - Prototype Chain --------------- @@ -101,51 +83,82 @@ end M = { --- Append an item to a list. + -- @static + -- @function append -- @tparam List l a list -- @param x item - -- @treturn List new list containing `{l[1], ..., l[#l], x}` + -- @treturn List new list with *x* appended + -- @usage + -- longer = append (short, "last") append = X ("append (List, any)", append), --- Compare two lists element-by-element, from left-to-right. - -- - -- if a_list:compare (another_list) == 0 then print "same" end + -- @static + -- @function compare -- @tparam List l a list - -- @tparam table m another list - -- @return -1 if `l` is less than `m`, 0 if they are the same, and 1 - -- if `l` is greater than `m` + -- @tparam List|table m another list, or table + -- @return -1 if *l* is less than *m*, 0 if they are the same, and 1 + -- if *l* is greater than *m* + -- @usage + -- if a_list:compare (another_list) == 0 then print "same" end compare = X ("compare (List, List|table)", compare), - --- Concatenate arguments into a list. + --- Concatenate the elements from any number of lists. + -- @static + -- @function concat -- @tparam List l a list -- @param ... tuple of lists - -- @treturn List new list containing - -- `{l[1], ..., l[#l], l\_1[1], ..., l\_1[#l\_1], ..., l\_n[1], ..., l\_n[#l\_n]}` + -- @treturn List new list with elements from arguments + -- @usage + -- --> {1, 2, 3, {4, 5}, 6, 7} + -- list.concat ({1, 2, 3}, {{4, 5}, 6, 7}) concat = X ("concat (List, List|table*)", concat), --- Prepend an item to a list. + -- @static + -- @function cons -- @tparam List l a list -- @param x item - -- @treturn List new list containing `{x, unpack (l)}` + -- @treturn List new list with *x* followed by elements of *l* + -- @usage + -- --> {"x", 1, 2, 3} + -- list.cons ({1, 2, 3}, "x") cons = X ("cons (List, any)", function (l, x) return List {x, unpack (l)} end), --- Repeat a list. + -- @static + -- @function rep -- @tparam List l a list -- @int n number of times to repeat - -- @treturn List `n` copies of `l` appended together + -- @treturn List *n* copies of *l* appended together + -- @usage + -- --> {1, 2, 3, 1, 2, 3, 1, 2, 3} + -- list.rep ({1, 2, 3}, 3) rep = X ("rep (List, int)", rep), --- Return a sub-range of a list. - -- (The equivalent of `string.sub` on strings; negative list indices + -- (The equivalent of @{string.sub} on strings; negative list indices -- count from the end of the list.) + -- @static + -- @function sub -- @tparam List l a list - -- @int from start of range (default: 1) - -- @int to end of range (default: `#l`) - -- @treturn List new list containing `{l[from], ..., l[to]}` + -- @int[opt=1] from start of range + -- @int[opt=#l] to end of range + -- @treturn List new list containing elements between *from* and *to* + -- inclusive + -- @usage + -- --> {3, 4, 5} + -- list.sub ({1, 2, 3, 4, 5, 6}, 3, 5) sub = X ("sub (List, int?, int?)", sub), --- Return a list with its first element removed. + -- @static + -- @function tail -- @tparam List l a list - -- @treturn List new list containing `{l[2], ..., l[#l]}` + -- @treturn List new list with all but the first element of *l* + -- @usage + -- --> {3, {4, 5}, 6, 7} + -- list.tail {{1, 2}, 3, {4, 5}, 6, 7} tail = X ("tail (List)", function (l) return sub (l, 2) end), } @@ -448,7 +461,7 @@ M.zip_with = DEPRECATED ("41", "'std.list.zip_with'", --- An Object derived List. --- @table List +-- @object List List = Object { -- Derived object type. @@ -458,36 +471,42 @@ List = Object { ------ -- Concatenate lists. - -- new = list .. table -- @function __concat - -- @tparam List list a list - -- @tparam table table another list, hash part is ignored + -- @tparam List l a list + -- @tparam List|table m another list, or table (hash part is ignored) -- @see concat + -- @usage + -- new = alist .. {"append", "these", "elements"} __concat = concat, ------ -- Append element to list. - -- list = list + element -- @function __add - -- @tparam List list a list - -- @param element element to append + -- @tparam List l a list + -- @param e element to append -- @see append + -- @usage + -- list = list + "element" __add = append, ------ -- List order operator. - -- max = list1 > list2 and list1 or list2 - -- @tparam List list1 a list - -- @tparam List list2 another list - -- @see std.list:compare + -- @function __lt + -- @tparam List l a list + -- @tparam List m another list + -- @see compare + -- @usage + -- max = list1 > list2 and list1 or list2 __lt = function (list1, list2) return compare (list1, list2) < 0 end, ------ -- List equality or order operator. - -- min = list1 <= list2 and list1 or list2 - -- @tparam List list1 a list - -- @tparam List list2 another list - -- @see std.list:compare + -- @function __le + -- @tparam List l a list + -- @tparam List m another list + -- @see compare + -- @usage + -- min = list1 <= list2 and list1 or list2 __le = function (list1, list2) return compare (list1, list2) <= 0 end, } From 5c6ed64d9aaa20c296a6a02edfb3b75bd63b05c6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 22:53:09 +0100 Subject: [PATCH 433/703] doc: use LDoc @object type for std.strbuf. * lib/std/strbuf.lua: Improve documentation. Signed-off-by: Gary V. Vaughan --- lib/std/strbuf.lua | 50 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 440b911..95b84cd 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -19,42 +19,62 @@ local insert = base.insert --- Add a string to a buffer. --- @tparam string s string to add --- @treturn std.strbuf modified buffer +-- @static +-- @function concat +-- @string s string to add +-- @treturn StrBuf modified buffer +-- @usage +-- buf = concat (buf, "append this") local function concat (self, s) return insert (self, s) end --- Convert a buffer to a string. --- @treturn string stringified `self` -local function tostring (self) - return table.concat (self) +-- @static +-- @function tostring +-- @treturn string stringified `buf` +-- @usage +-- string = buf:tostring () +local function tostring (buf) + return table.concat (buf) end +--- StrBuf prototype object. +-- +-- Set also inherits all the fields and methods from +-- @{std.object.Object}. +-- @object StrBuf +-- @string[opt="StrBuf"] _type object name +-- @see std.container +-- @see std.object.__call +-- @usage +-- local std = require "std" +-- std.prototype (std.strbuf) --> "StrBuf" +-- os.exit (0) return Object { -- Derived object type. _type = "StrBuf", - ------ - -- Support concatenation of StrBuf objects. - -- buffer = buffer .. str + --- Support concatenation to StrBuf objects. -- @function __concat - -- @tparam std.strbuf buffer StrBuf object - -- @tparam string str a string or string-like object - -- @treturn std.strbuf modified `buffer` + -- @tparam StrBuf buffer object + -- @string s a string + -- @treturn StrBuf modified *buf* -- @see concat + -- @usage + -- buf = buf .. str __concat = concat, - ------ - -- Support fast conversion to Lua string. - -- str = tostring (buffer) + --- Support fast conversion to Lua string. -- @function __tostring - -- @tparam std.strbuf buffer Strbuf object + -- @tparam StrBuf buffer object -- @treturn string concatenation of buffer contents -- @see tostring + -- @usage + -- str = tostring (buf) __tostring = tostring, From b9bd58e9a52b4152f705f93a57851a914c484a26 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Aug 2014 23:41:04 +0100 Subject: [PATCH 434/703] doc: use LDoc @object type for std.tree. * lib/std/tree.lua: Improve documentation. Signed-off-by: Gary V. Vaughan --- lib/std/tree.lua | 190 ++++++++++++++++++++++++++--------------------- 1 file changed, 104 insertions(+), 86 deletions(-) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index ae17573..f60a333 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -1,7 +1,5 @@ --[[-- - Tree container. - - Derived from @{std.container}, and inherits Container's metamethods. + Tree container prototype. Note that Functions listed below are only available from the Tree prototype return by requiring this module, because Container objects @@ -39,11 +37,11 @@ local Tree -- forward declaration --- Tree iterator. --- @tparam function it iterator function --- @tparam tree|table tr tree or tree-like table --- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order)) --- @treturn table path to node ({i\_1...i\_k}) --- @return node +-- @tparam function it iterator function +-- @tparam tree|table tr tree or tree-like table +-- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order)) +-- @treturn table path to node (`{i1, ...in}`) +-- @treturn node node local function _nodes (it, tr) local p = {} local function visit (n) @@ -70,11 +68,19 @@ end --- Tree iterator which returns just numbered leaves, in order. --- @function ileaves -- @static --- @tparam tree|table tr tree or tree-like table +-- @function ileaves +-- @tparam Tree|table tr tree or tree-like table -- @treturn function iterator function --- @treturn tree|table the tree `tr` +-- @treturn Tree|table the tree *tr* +-- @see inodes +-- @see leaves +-- @usage +-- --> t = {"one", "three", "five"} +-- for leaf in ileaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} +-- do +-- t[#t + 1] = leaf +-- end local function ileaves (tr) assert (type (tr) == "table", "bad argument #1 to 'ileaves' (table expected, got " .. type (tr) .. ")") @@ -83,11 +89,20 @@ end --- Tree iterator which returns just leaves. --- @function leaves -- @static --- @tparam tree|table tr tree or tree-like table +-- @function leaves +-- @tparam Tree|table tr tree or tree-like table -- @treturn function iterator function --- @treturn tree|table the tree, `tr` +-- @treturn Tree|table the tree, *tr* +-- @see ileaves +-- @see nodes +-- @usage +-- for leaf in leaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} +-- do +-- t[#t + 1] = leaf +-- end +-- --> t = {2, 4, "five", "foo", "one", "three"} +-- table.sort (t, lambda "=tostring(_1) < tostring(_2)") local function leaves (tr) assert (type (tr) == "table", "bad argument #1 to 'leaves' (table expected, got " .. type (tr) .. ")") @@ -96,19 +111,23 @@ end --- Make a deep copy of a tree, including any metatables. --- --- To make fast shallow copies, use @{std.table.clone}. --- @tparam table|tree t table or tree to be cloned --- @tparam boolean nometa if non-nil don't copy metatables --- @treturn table|tree a deep copy of `t` -local function clone (t, nometa) - assert (type (t) == "table", - "bad argument #1 to 'clone' (table expected, got " .. type (t) .. ")") +-- @tparam Tree|table tr tree or tree-like table +-- @tparam boolean nometa if non-`nil` don't copy metatables +-- @treturn Tree|table a deep copy of *tr* +-- @see std.table.clone +-- @usage +-- tr = {"one", {two=2}, {{"three"}, four=4}} +-- copy = clone (tr) +-- copy[2].two=5 +-- assert (tr[2].two == 2) +local function clone (tr, nometa) + assert (type (tr) == "table", + "bad argument #1 to 'clone' (table expected, got " .. type (tr) .. ")") local r = {} if not nometa then - setmetatable (r, getmetatable (t)) + setmetatable (r, getmetatable (tr)) end - local d = {[t] = r} + local d = {[tr] = r} local function copy (o, x) for i, v in pairs (x) do if type (v) == "table" then @@ -127,7 +146,7 @@ local function clone (t, nometa) end return o end - return copy (r, t) + return copy (r, tr) end @@ -139,34 +158,31 @@ end -- list of keys used to reach this node, and `tree-node` is the current -- node. -- --- Given a `tree` to represent: --- --- + root --- +-- node1 --- | +-- leaf1 --- | '-- leaf2 --- '-- leaf 3 --- --- tree = std.tree { std.tree { "leaf1", "leaf2"}, "leaf3" } --- --- A series of calls to `tree.nodes` will return: --- --- "branch", {}, {{"leaf1", "leaf2"}, "leaf3"} --- "branch", {1}, {"leaf1", "leaf"2") --- "leaf", {1,1}, "leaf1" --- "leaf", {1,2}, "leaf2" --- "join", {1}, {"leaf1", "leaf2"} --- "leaf", {2}, "leaf3" --- "join", {}, {{"leaf1", "leaf2"}, "leaf3"} --- -- Note that the `tree-path` reuses the same table on each iteration, so -- you must `table.clone` a copy if you want to take a snap-shot of the -- current state of the `tree-path` list before the next iteration -- changes it. --- @tparam tree|table tr tree or tree-like table to iterate over +-- @tparam Tree|table tr tree or tree-like table to iterate over -- @treturn function iterator function --- @treturn tree|table the tree, `tr` +-- @treturn Tree|table the tree, *tr* -- @see inodes +-- @usage +-- -- tree = +-- node1 +-- -- | +-- leaf1 +-- -- | '-- leaf2 +-- -- '-- leaf 3 +-- tree = Tree { Tree { "leaf1", "leaf2"}, "leaf3" } +-- for node_type, path, node in nodes (tree) do +-- print (node_type, path, node) +-- end +-- --> "branch" {} {{"leaf1", "leaf2"}, "leaf3"} +-- --> "branch" {1} {"leaf1", "leaf"2") +-- --> "leaf" {1,1} "leaf1" +-- --> "leaf" {1,2} "leaf2" +-- --> "join" {1} {"leaf1", "leaf2"} +-- --> "leaf" {2} "leaf3" +-- --> "join" {} {{"leaf1", "leaf2"}, "leaf3"} +-- os.exit (0) local function nodes (tr) assert (type (tr) == "table", "bad argument #1 to 'nodes' (table expected, got " .. type (tr) .. ")") @@ -177,10 +193,10 @@ end --- Tree iterator over numbered nodes, in order. -- -- The iterator function behaves like @{nodes}, but only traverses the --- array part of the nodes of `tr`, ignoring any others. --- @tparam tree|table tr tree to iterate over +-- array part of the nodes of *tr*, ignoring any others. +-- @tparam Tree|table tr tree or tree-like table to iterate over -- @treturn function iterator function --- @treturn tree|table the tree, `tr` +-- @treturn tree|table the tree, *tr* -- @see nodes local function inodes (tr) assert (type (tr) == "table", @@ -190,21 +206,23 @@ end --- Destructively deep-merge one tree into another. --- @tparam tree|table t destination tree or table --- @tparam tree|table u tree or table with nodes to merge --- @treturn tree|table `t` with nodes from `u` merged in +-- @tparam Tree|table tr destination tree or table +-- @tparam Tree|table ur tree or table with nodes to merge +-- @treturn Tree|table *tr* with nodes from *ur* merged in -- @see std.table.merge -local function merge (t, u) - assert (type (t) == "table", - "bad argument #1 to 'merge' (table expected, got " .. type (t) .. ")") - assert (type (u) == "table", - "bad argument #2 to 'merge' (table expected, got " .. type (u) .. ")") - for ty, p, n in nodes (u) do +-- @usage +-- merge (dest, {{exists=1}, {{not = {present = { inside = "dest" }}}}}) +local function merge (tr, ur) + assert (type (tr) == "table", + "bad argument #1 to 'merge' (table expected, got " .. type (tr) .. ")") + assert (type (ur) == "table", + "bad argument #2 to 'merge' (table expected, got " .. type (ur) .. ")") + for ty, p, n in nodes (ur) do if ty == "leaf" then - t[p] = n + tr[p] = n end end - return t + return tr end @@ -215,48 +233,48 @@ end --- Tree prototype object. --- @table std.tree --- @string[opt="Tree"] _type type of Tree, returned by --- @{std.object.prototype} --- @tfield[opt={}] table|function _init a table of field names, or --- initialisation function, see @{std.object.__call} --- @tfield nil|table _functions a table of module functions not copied --- by @{std.object.__call} +-- @object Tree +-- @string[opt="Tree"] _type object name Tree = Container { - -- Derived object type. _type = "Tree", - --- Tree `__index` metamethod. + --- Deep retrieval. + -- @static -- @function __index - -- @param i non-table, or list of keys `{i\_1 ... i\_n}` - -- @return `self[i]...[i\_n]` if i is a table, or `self[i]` otherwise + -- @tparam Tree tr a tree + -- @param i non-table, or list of keys `{i1, ...i_n}` + -- @return `tr[i1]...[i_n]` if *i* is a key list, `tr[i]` otherwise -- @todo the following doesn't treat list keys correctly - -- e.g. self[{{1, 2}, {3, 4}}], maybe flatten first? - __index = function (self, i) + -- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? + -- @usage + -- del_other_window = keymap[{"C-x", "4", KEY_DELETE}] + __index = function (tr, i) if prototype (i) == "table" then - return reduce (operator.deref, self, ielems, i) + return reduce (operator.deref, tr, ielems, i) else - return rawget (self, i) + return rawget (tr, i) end end, - --- Tree `__newindex` metamethod. - -- - -- Sets `self[i\_1]...[i\_n] = v` if i is a table, or `self[i] = v` otherwise + --- Deep insertion. + -- @static -- @function __newindex - -- @param i non-table, or list of keys `{i\_1 ... i\_n}` + -- @tparam Tree tr a tree + -- @param i non-table, or list of keys `{i1, ...i_n}` -- @param v value - __newindex = function (self, i, v) + -- @usage + -- function bindkey (keylist, fn) keymap[keylist] = fn end + __newindex = function (tr, i, v) if prototype (i) == "table" then for n = 1, len (i) - 1 do - if prototype (self[i[n]]) ~= "Tree" then - rawset (self, i[n], Tree {}) + if prototype (tr[i[n]]) ~= "Tree" then + rawset (tr, i[n], Tree {}) end - self = self[i[n]] + tr = tr[i[n]] end - rawset (self, last (i), v) + rawset (tr, last (i), v) else - rawset (self, i, v) + rawset (tr, i, v) end end, From a2bc7765d5d9e71cf0d960a3365423643333ac70 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 1 Sep 2014 00:18:54 +0100 Subject: [PATCH 435/703] doc: Use LDoc @object type for std.vector. Closes #65. * lib/std/vector.lua: Improve documentation. Signed-off-by: Gary V. Vaughan --- lib/std/vector.lua | 68 ++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/lib/std/vector.lua b/lib/std/vector.lua index 96e09fa..6be4789 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -1,29 +1,19 @@ --[[-- - Vector of homogenous objects. + Vector container prototype. A vector is usually a block of contiguous memory, divided into equal sized elements that can be indexed quickly. - Create a new vector with: - - > vector = require "std.vector" - > Vector = vector () - > a = Vector ("int", {0xdead, 0xbeef, 0xfeed}) - > =a[1], a[2], a[3], a[-3], a[-4] - 57005 48879 65261 57005 nil - - All the indices passed to vector methods use 1-based counting. - - If the Lua alien module is installed, and the `type` argument passed + If the Lua alien module is installed, and the *type* argument passed when cloning a new vector object is suitable (i.e. the name of a numeric C type that `alien.sizeof` understands), then the vector contents are managed in an `alien.buffer`. - If alien is not installed, or does not understand the `type` argument + If alien is not installed, or does not understand the *type* argument given when cloning, then a much slower (but API compatible) Lua table is transparently used to manage elements instead. - In either case, `std.vector` provides a means for managing collections + In either case, @{Vector} provides a means for managing collections of homogenous Lua objects with an array-like, stack-like or queue-like API. @@ -33,7 +23,7 @@ table `-> Object `-> Container - `-> Vector + `-> Vector @classmod std.vector ]] @@ -132,7 +122,7 @@ local core_functions = { end, - --- Change the number of elements allocated to be at least `n`. + --- Change the number of elements allocated to be at least *n*. -- @function realloc -- @int n the number of elements required -- @treturn std.vector the vector @@ -150,7 +140,7 @@ local core_functions = { end, - --- Set `n` elements starting at `from` to `v`. + --- Set *n* elements starting at *from* to *v*. -- @function set -- @int from index of first element to set -- @param v value to store @@ -188,7 +178,7 @@ local core_functions = { --- Shift the whole vector to the right by inserting a new left-most element. -- @function unshift -- @param elem new element to be pushed - -- @treturn elem + -- @return *elem* -- @usage added = avector:unshift (anelement) unshift = function (self, elem) self.length = self.length + 1 @@ -208,12 +198,14 @@ core_metatable = { -- `alien.sizeof` will use the fast `alien.buffer` managed memory buffer -- for vector contents; otherwise, a much slower Lua emulation is used. -- @function __call - -- @string type element type name - -- @tparam[opt] int|table init initial size or list of initial elements + -- @string[opt="any"] type element type name + -- @tparam[opt={}] int|table init initial size or list of initial elements -- @treturn std.vector a new vector object -- @usage -- local Vector = require "std.vector" {} -- not a typo! - -- local new = Vector ("int", {1, 2, 3}) + -- local new = Vector ("int", {0xdead, 0xbeef, 0xfeed}) + -- --> 57005 48879 65261 57005 nil + -- print (a[1], a[2], a[3], a[-3], a[-4]) __call = function (self, type, init) if _ARGCHECK then if init ~= nil then @@ -306,10 +298,14 @@ core_metatable = { end, - --- Return the `n`th element in this vector. + --- Return the *n*th element in this vector. + -- + -- Unlike normal @{std.container.Container}, access is not limited to + -- only metamethods, integer keys are used to fetch element, and + -- string keys for method names. -- @function __index -- @int n 1-based index, or negative to index starting from the right - -- @treturn string the element at index `n` + -- @treturn string the element at index *n* -- @usage rightmost = avector[avector.length] __index = function (self, n) argcheck ("__index", 2, "int|string", n) @@ -325,7 +321,7 @@ core_metatable = { end, - --- Set the `n`th element of this vector to `elem`. + --- Set the *n*th element of this vector to *elem*. -- @function __newindex -- @int n 1-based index -- @param elem value to store at index n @@ -351,11 +347,11 @@ core_metatable = { --- Return the number of elements in this vector. -- - -- Beware that Lua 5.1 does not respect this metamethod; use - -- `vector.length` if you care about portability. + -- Beware that Lua 5.1 `#` operator does not respect this metamethod; + -- use `vector.length` or @{std.table.len} if you care about portability. -- @function __len -- @treturn int number of elements - -- @usage length = #avector + -- @usage length = table.len (avector) __len = function (self) argcheck ("__len", 1, "Vector", self) @@ -539,9 +535,15 @@ local function dispatch (name) end ------- --- An efficient vector of homogenous objects. --- @table std.vector +--- Vector prototype object. +-- +-- Vector also inherits all the fields and methods from +-- @{std.container.Container}, however the api is not limited to only +-- metamethods like other Containers, because element access uses only +-- integer keys, and so method name strings work too! +-- @object Vector +-- @string[opt="Vector"] _type object name +-- @tfield __call __call instantiation function -- @int allocated number of allocated element slots, for `alien.buffer` -- managed elements -- @tfield alien.buffer|table buffer a block of indexable memory @@ -549,6 +551,12 @@ end -- @int size length of each stored element, or 0 when `alien.buffer` is -- not managing this vector -- @string type type name for elements +-- @see std.container +-- @see __index +-- @usage +-- local std = require "std" +-- std.prototype (std.vector) --> "Vector" +-- os.exit (0) local Vector = Container { _type = "Vector", From c7a76a94b5d3328cf15ca5103f2fda6ab983e85c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 2 Sep 2014 13:09:56 +0100 Subject: [PATCH 436/703] std: use numeric keys first ordering when stringifying a table. Closes #81. * specs/table_spec.yaml (okeys): Specify behaviours of a new ordered keys function. * lib/std/container.lua (__pairs): Factor key sorting algorithm from here... * lib/std/base.lua (keysort): New function. ...to here. (okeys): Use keysort to extract a sorted key list from a table. * lib/std/table.lua (okeys): Re-export from here. * lib/std/base.lua (render): Simplify accordingly, and always sort keys nicely as a pleasant side-effect. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 ++++++++ lib/std/base.lua | 32 +++++++++++++++++++++++--------- lib/std/container.lua | 11 ++--------- lib/std/table.lua | 9 +++++++++ specs/table_spec.yaml | 22 +++++++++++++++++++++- 5 files changed, 63 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index 772702a..c8c5942 100644 --- a/NEWS +++ b/NEWS @@ -143,6 +143,14 @@ Stdlib NEWS - User visible changes - 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 diff --git a/lib/std/base.lua b/lib/std/base.lua index a1215b8..9d4ce15 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -219,6 +219,24 @@ local function ireverse (t) end +-- Sort numbers first then asciibetically +local function keysort (a, b) + if type (a) == "number" then + return type (b) ~= "number" or a < b + else + return type (b) ~= "number" and tostring (a) < tostring (b) + end +end + + +local function okeys (t) + local r = {} + for k in pairs (t) do r[#r + 1] = k end + table.sort (r, keysort) + return r +end + + local function last (t) return t[len (t)] end @@ -305,14 +323,8 @@ local function render (x, open, close, elem, pair, sep, roots) r[#r + 1] = open (x) roots[x] = elem (x) - -- create a sorted list of keys - local ord = {} - for k, _ in pairs (x) do ord[#ord + 1] = k end - table.sort (ord, function (a, b) return tostring (a) < tostring (b) end) - - -- render x elements in order local i, v = nil, nil - for _, j in ipairs (ord) do + for _, j in ipairs (okeys (x)) do local w = x[j] r[#r + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) i, v = j, w @@ -387,8 +399,10 @@ end return { - copy = copy, - merge = merge, + copy = copy, + keysort = keysort, + merge = merge, + okeys = okeys, -- std.lua -- assert = assert, diff --git a/lib/std/container.lua b/lib/std/container.lua index 302fb31..0bd1836 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -36,7 +36,7 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" local debug = require "std.debug" -local ipairs, pairs = base.ipairs, base.pairs +local ipairs, pairs, keysort = base.ipairs, base.pairs, base.keysort local insert, len, maxn = base.insert, base.len, base.maxn local prototype = base.prototype local argcheck = debug.argcheck @@ -238,14 +238,7 @@ function M.__pairs (self) k = next (self, k) end - -- Sort numbers first then asciibetically - table.sort (keys, function (a, b) - if type (a) == "number" then - return type (b) ~= "number" or a < b - else - return type (b) ~= "number" and tostring (a) < tostring (b) - end - end) + table.sort (keys, keysort) local n, lenkeys = 0, #keys return function (t, k) diff --git a/lib/std/table.lua b/lib/std/table.lua index deacbe4..cdb32fd 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -295,6 +295,7 @@ M = { -- @function keys -- @tparam table t a table -- @treturn table list of keys from *t* + -- @see okeys -- @see values -- @usage globals = keys (_G) keys = X ("keys (table)", keys), @@ -358,6 +359,14 @@ M = { -- @usage t = new (0) new = X ("new (any?, table?)", new), + --- Make an ordered list of keys in table. + -- @function okeys + -- @tparam table t a table + -- @treturn table ordered list of keys from *t* + -- @see keys + -- @usage globals = keys (_G) + okeys = X ("okeys (table)", base.okeys), + --- Turn a tuple into a list. -- @function pack -- @param ... tuple diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 022b7dd..f84dd27 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -6,7 +6,7 @@ before: | extend_base = { "clone", "clone_select", "depair", "empty", "enpair", "flatten", "insert", "invert", "keys", "len", "maxn", "merge", "merge_select", - "monkey_patch", "new", "pack", "project", + "monkey_patch", "new", "okeys", "pack", "project", "remove", "shape", "size", "sort", "values" } deprecations = { "clone_rename", "metamethod", "ripairs", "totable" } @@ -591,6 +591,26 @@ specify std.table: expect (t[1]).to_be (default) +- describe okeys: + - before: + subject = { "v1", k1 = 1, "v2", k2 = 2, "v3", k3 = 3, [10]="v10" } + + f, badarg = init (M, this_module, "okeys") + + - it diagnoses missing arguments: + expect (f ()).to_raise (badarg (1, "table")) + - it diagnoses wrong argument types: + expect (f (false)).to_raise (badarg (1, "table", "boolean")) + - it diagnoses too many arguments: + expect (f ({}, false)).to_raise (badarg (2)) + + - it returns an empty list when subject is empty: + expect (f {}).to_equal {} + - it makes an ordered list of table keys: + expect (f (subject)). + to_equal {1, 2, 3, 10, "k1", "k2", "k3"} + + - describe pack: - before: unpack = unpack or table.unpack From b5d6c6d8549670e94975a623692b0efdeed14fb4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 2 Sep 2014 14:33:01 +0100 Subject: [PATCH 437/703] refactor: factor away std.container.__pairs. Object pairs iteration shouldn't rely on slow in-order traversal, we only care about key order when printing. * lib/std/container.lua (__pairs): Remove. By the time this is called, private keys are already moved into the metatable, and the client would be using ipairs and okeys for ordered traversal. (__tostring): Use ipairs and okeys here, because we do care about ordering. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 0bd1836..0342831 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -36,7 +36,7 @@ local _ARGCHECK = require "std.debug_init"._ARGCHECK local base = require "std.base" local debug = require "std.debug" -local ipairs, pairs, keysort = base.ipairs, base.pairs, base.keysort +local ipairs, pairs, okeys = base.ipairs, base.pairs, base.okeys local insert, len, maxn = base.insert, base.len, base.maxn local prototype = base.prototype local argcheck = debug.argcheck @@ -230,30 +230,10 @@ else end -function M.__pairs (self) - local keys = {} - local k = next (self) - while k do - keys[#keys + 1] = k - k = next (self, k) - end - - table.sort (keys, keysort) - - local n, lenkeys = 0, #keys - return function (t, k) - n = n + 1 - if n <= lenkeys then - local key = keys[n] - return key, self[key] - end - end, self, nil -end - - function M.__tostring (self) local n, ibuf, kbuf = 1, {}, {} - for k, v in pairs (self) do + for _, k in ipairs (okeys (self)) do + local v = self[k] if type (k) == "number" and k == n then ibuf[#ibuf + 1] = tostring (v) n = n + 1 From ede36580d988d78fa1dcf59c1214bb85f86136d6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 3 Sep 2014 14:02:05 +0100 Subject: [PATCH 438/703] refactor: simplify string.render and clients. * lib/std/base.lua (render): Use `cb` suffix for callback parameter names. Rename j and w locals to k_ and v_, as they represent the values of k and v resp. from the previous iteration. Remove unrelated Haskell comments. * lib/std/string.lua (prettytostring): Likewise. * lib/std/container.lua (__tostring): Manually unroll render invocation with base.tostring functions plugged in. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 54 +++++++++++++++++-------------------------- lib/std/container.lua | 37 +++++++++++++++++++---------- lib/std/string.lua | 16 ++++++------- 3 files changed, 54 insertions(+), 53 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 9d4ce15..d0d99ca 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -295,42 +295,30 @@ end -- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925 -- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps -- Heavily modified by Simon Peyton Jones, Dec 96 --- --- Haskell types: --- data Doc list of lines --- quote :: Char -> Char -> Doc -> Doc Wrap document in ... --- (<>) :: Doc -> Doc -> Doc Beside --- (<+>) :: Doc -> Doc -> Doc Beside, separated by space --- ($$) :: Doc -> Doc -> Doc Above; if there is no overlap it "dovetails" the two --- nest :: Int -> Doc -> Doc Nested --- punctuate :: Doc -> [Doc] -> [Doc] punctuate p [d1, ... dn] = [d1 <> p, d2 <> p, ... dn-1 <> p, dn] --- render :: Int Line length --- -> Float Ribbons per line --- -> (TextDetails -> a -> a) What to do with text --- -> a What to do at the end --- -> Doc The document --- -> a Result - -local function render (x, open, close, elem, pair, sep, roots) + +local function render (x, opencb, closecb, elemcb, paircb, sepcb, roots) + roots = roots or {} local function stop_roots (x) - return roots[x] or render (x, open, close, elem, pair, sep, copy (roots)) + return roots[x] or render (x, opencb, closecb, elemcb, paircb, sepcb, copy (roots)) end - roots = roots or {} - if type (x) ~= "table" or type ((getmetatable (x) or {}).__tostring) == "function" then - return elem (x) + + if type (x) ~= "table" or getmetamethod (x, "__tostring") then + return elemcb (x) else - local r = {} - r[#r + 1] = open (x) - roots[x] = elem (x) - - local i, v = nil, nil - for _, j in ipairs (okeys (x)) do - local w = x[j] - r[#r + 1] = sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w)) - i, v = j, w + local buf, k_, v_ = { opencb (x) } -- pre-buffer table open + roots[x] = elemcb (x) -- initialise recursion protection + + for _, k in ipairs (okeys (x)) do -- for ordered table members + local v = x[k] + buf[#buf + 1] = sepcb (x, k_, v_, k, v) -- | buffer separator + buf[#buf + 1] = paircb (x, k, v, stop_roots (k), stop_roots (v)) + -- | buffer key/value pair + k_, v_ = k, v end - r[#r + 1] = sep (x, i, v, nil, nil) .. close (x) - return table.concat (r) + buf[#buf + 1] = sepcb (x, k_, v_) -- buffer trailing separator + buf[#buf + 1] = closecb (x) -- buffer table close + + return table.concat (buf) -- stringify buffer end end @@ -393,7 +381,7 @@ local function tostring (x) function () return "}" end, _tostring, function (_, _, _, is, vs) return is .."=".. vs end, - function (_, i, _, j) return i and j and "," or "" end) + function (_, i, _, k) return i and k and "," or "" end) end diff --git a/lib/std/container.lua b/lib/std/container.lua index 0342831..dec3503 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -38,8 +38,8 @@ local debug = require "std.debug" local ipairs, pairs, okeys = base.ipairs, base.pairs, base.okeys local insert, len, maxn = base.insert, base.len, base.maxn -local prototype = base.prototype -local argcheck = debug.argcheck +local okeys, prototype, tostring = base.okeys, base.prototype, base.tostring +local argcheck = debug.argcheck @@ -231,22 +231,35 @@ end function M.__tostring (self) - local n, ibuf, kbuf = 1, {}, {} - for _, k in ipairs (okeys (self)) do + local n, k_ = 1, nil + local buf = { prototype (self), " {" } -- pre-buffer object open + for _, k in ipairs (okeys (self)) do -- for ordered public members local v = self[k] - if type (k) == "number" and k == n then - ibuf[#ibuf + 1] = tostring (v) + + if k_ ~= nil then -- | buffer separator + if k ~= n and type (k_) == "number" and k_ == n - 1 then + -- `;` separates `v` elements from `k=v` elements + buf[#buf + 1] = "; " + elseif k ~= nil then + -- `,` separator everywhere else + buf[#buf + 1] = ", " + end + end + + if type (k) == "number" and k == n then -- | buffer key/value pair + -- render initial array-like elements as just `v` + buf[#buf + 1] = tostring (v) n = n + 1 else - kbuf[#kbuf + 1] = tostring (k) .. "=" .. tostring (v) + -- render remaining elements as `k=v` + buf[#buf + 1] = tostring (k) .. "=" .. tostring (v) end - end - local buf = {} - if next (ibuf) then buf[#buf + 1] = table.concat (ibuf, ", ") end - if next (kbuf) then buf[#buf + 1] = table.concat (kbuf, ", ") end + k_ = k -- maintain loop invariant: k_ is previous key + end + buf[#buf + 1] = "}" -- buffer object close - return prototype (self) .. " {" .. table.concat (buf, "; ") .. "}" + return table.concat (buf) -- stringify buffer end diff --git a/lib/std/string.lua b/lib/std/string.lua index 3e20d79..41ebc33 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -192,20 +192,20 @@ local function prettytostring (x, indent, spacing) return tostring (x) end end, - function (x, i, v, is, vs) + function (x, k, v, ks, vs) local s = spacing - if type (i) ~= "string" or i:match "[^%w_]" then + if type (k) ~= "string" or k:match "[^%w_]" then s = s .. "[" - if type (i) == "table" then + if type (k) == "table" then s = s .. "\n" end - s = s .. is - if type (i) == "table" then + s = s .. ks + if type (k) == "table" then s = s .. "\n" end s = s .. "]" else - s = s .. i + s = s .. k end s = s .. " =" if type (v) == "table" then @@ -216,9 +216,9 @@ local function prettytostring (x, indent, spacing) s = s .. vs return s end, - function (_, i) + function (_, k) local s = "\n" - if i then + if k then s = "," .. s end return s From b9accc71ab88c7f903060062448bbe4cfc20e45a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 3 Sep 2014 21:34:59 +0100 Subject: [PATCH 439/703] refactor: simplify primitive std.set functions. * lib/std/set.lua (insert, delete): Return results of calling rawset. Signed-off-by: Gary V. Vaughan --- lib/std/set.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/std/set.lua b/lib/std/set.lua index 4515fa6..e0b1048 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -57,8 +57,7 @@ end -- @usage -- for byte = 32,126 do set.insert (isprintable, string.char (byte)) end local function insert (set, e) - rawset (set, e, true) - return set + return rawset (set, e, true) end @@ -69,8 +68,7 @@ end -- @usage -- set.delete (available, found) local function delete (set, e) - rawset (set, e, nil) - return set + return rawset (set, e, nil) end From ca5eaad4b74acb3c8fd9ae2abd02deb8b45da074 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 4 Sep 2014 08:39:39 +0100 Subject: [PATCH 440/703] set: use argscheck on exported apis. * lib/std/set.lua: Move LDocs and add argschecks to returned Set object declaration. * HACKING: Add items about best practices for `debug.argscheck`. Signed-off-by: Gary V. Vaughan --- HACKING | 15 ++++ lib/std/set.lua | 209 ++++++++++++++++++++++++++++-------------------- 2 files changed, 136 insertions(+), 88 deletions(-) diff --git a/HACKING b/HACKING index 19c16b2..90d071e 100644 --- a/HACKING +++ b/HACKING @@ -61,8 +61,23 @@ s a string t a table + - Do argument check all module functions (functions available in the table + returned from requiring that module -- or listed in the `_functions` + subtable of an object module), preferably using a `debug.argscheck` + wrapper from the module export table, so that functions can call each + other inside the implementation without excessive rechecking of argument + types. + + - Don't argument check metamethods, rely on the Lua runtime to diagnose any + errors there. + ## 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 + `debug.argscheck` or equivalent. + - `backtick_references` is disabled for stdlib, if you want an inline cross-reference, use `@{reference}`. diff --git a/lib/std/set.lua b/lib/std/set.lua index e0b1048..56b4aeb 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -38,47 +38,16 @@ local Set -- forward declaration -- whose values are true. ---- Say whether an element is in a set. --- @tparam Set set a set --- @param e element --- @return `true` if *e* is in *set*, otherwise `false` --- otherwise --- @usage --- if not set.member (keyset, pressed) then return nil end -local function member (set, e) - return rawget (set, e) == true -end +local elems = base.pairs ---- Insert an element into a set. --- @tparam Set set a set --- @param e element --- @treturn Set the modified *set* --- @usage --- for byte = 32,126 do set.insert (isprintable, string.char (byte)) end local function insert (set, e) return rawset (set, e, true) end ---- Delete an element from a set. --- @tparam Set set a set --- @param e element --- @treturn Set the modified *set* --- @usage --- set.delete (available, found) -local function delete (set, e) - return rawset (set, e, nil) -end - - ---- Iterator for sets. --- @tparam Set set a set --- @todo Make the iterator return only the key --- @usage --- for code in set.elems (isprintable) do print (code) end -local function elems (set) - return pairs (set) +local function member (set, e) + return rawget (set, e) == true end @@ -95,12 +64,6 @@ local difference, symmetric_difference, intersection, union, subset, proper_subset, equal ---- Find the difference of two sets. --- @tparam Set set1 a set --- @tparam table|Set set2 another set, or table --- @treturn Set a copy of *set1* with elements of *set2* removed --- @usage --- all = set.difference (all, {32, 49, 56}) function difference (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -115,12 +78,6 @@ function difference (set1, set2) end ---- Find the symmetric difference of two sets. --- @tparam Set set1 a set --- @tparam table|Set set2 another set, or table --- @treturn Set a new set with elements that are in *set1* or *set2* but not both --- @usage --- unique = set.symmetric_difference (a, b) function symmetric_difference (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -129,12 +86,6 @@ function symmetric_difference (set1, set2) end ---- Find the intersection of two sets. --- @tparam Set set1 a set --- @tparam table|Set set2 another set, or table --- @treturn Set a new set with elements in both *set1* and *set2* --- @usage --- common = set.intersection (a, b) function intersection (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -149,12 +100,6 @@ function intersection (set1, set2) end ---- Find the union of two sets. --- @tparam Set set1 a set --- @tparam table|Set set2 another set, or table --- @treturn Set a copy of *set1* with elements in *set2* merged in --- @usage --- all = set.union (a, b) function union (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -170,11 +115,6 @@ function union (set1, set2) end ---- Find whether one set is a subset of another. --- @tparam Set set1 a set --- @tparam table|Set set2 another set, or table --- @treturn boolean `true` if all elements in *set1* are also in *set2*, --- `false` otherwise function subset (set1, set2) if prototype (set2) == "table" then set2 = Set (set2) @@ -188,11 +128,6 @@ function subset (set1, set2) end ---- Find whether one set is a proper subset of another. --- @tparam Set set1 a set --- @tparam table|Set set2 another set, or table --- @treturn boolean `true` if *set2* contains all elements in *set1* but --- not only those elements, `false` otherwise function proper_subset (set1, set2) if prototype (set2) == "table" then t = Set (set2) @@ -201,13 +136,6 @@ function proper_subset (set1, set2) end ---- Find whether two sets are equal. --- @tparam Set set1 a set --- @tparam table|Set set2 another set, or table --- @treturn boolean `true` if *set1* and *set2* each contain identical --- elements, `false` otherwise --- @usage --- if set.equal (keys, {META, CTRL, "x"}) then process (keys) end function equal (set1, set2) return subset (set1, set2) and subset (set2, set1) end @@ -219,8 +147,12 @@ end --[[ =========== ]]-- +local function X (decl, fn) + return require "std.debug".argscheck ("std.set." .. decl, fn) +end + + --- Signature for cloning Set prototype object. --- @static -- @function Set_Clone -- @tparam table elements a list of additional elements -- @treturn Set clone of prototype, with *elements* merged in @@ -341,19 +273,120 @@ Set = Container { end, - --- @export _functions = { - delete = delete, - difference = difference, - elems = elems, - equal = equal, - insert = insert, - intersection = intersection, - member = member, - proper_subset = proper_subset, - subset = subset, - symmetric_difference = symmetric_difference, - union = union, + --- Delete an element from a set. + -- @static + -- @function delete + -- @tparam Set set a set + -- @param e element + -- @treturn Set the modified *set* + -- @usage + -- set.delete (available, found) + delete = X ("delete (Set, any)", + function (set, e) return rawset (set, e, nil) end), + + --- Find the difference of two sets. + -- @static + -- @function difference + -- @tparam Set set1 a set + -- @tparam table|Set set2 another set, or table + -- @treturn Set a copy of *set1* with elements of *set2* removed + -- @usage + -- all = set.difference (all, {32, 49, 56}) + difference = X ("difference (Set, Set|table)", difference), + + --- Iterator for sets. + -- @static + -- @function elems + -- @tparam Set set a set + -- @todo Make the iterator return only the key + -- @usage + -- for code in set.elems (isprintable) do print (code) end + elems = X ("elems (Set)", elems), + + --- Find whether two sets are equal. + -- @static + -- @function equal + -- @tparam Set set1 a set + -- @tparam table|Set set2 another set, or table + -- @treturn boolean `true` if *set1* and *set2* each contain identical + -- elements, `false` otherwise + -- @usage + -- if set.equal (keys, {META, CTRL, "x"}) then process (keys) end + equal = X ( "equal (Set, Set|table)", equal), + + --- Insert an element into a set. + -- @static + -- @function insert + -- @tparam Set set a set + -- @param e element + -- @treturn Set the modified *set* + -- @usage + -- for byte = 32,126 do + -- set.insert (isprintable, string.char (byte)) + -- end + insert = X ("insert (Set, any)", insert), + + --- Find the intersection of two sets. + -- @static + -- @function intersection + -- @tparam Set set1 a set + -- @tparam table|Set set2 another set, or table + -- @treturn Set a new set with elements in both *set1* and *set2* + -- @usage + -- common = set.intersection (a, b) + intersection = X ("intersection (Set, Set|table)", intersection), + + --- Say whether an element is in a set. + -- @static + -- @function difference + -- @tparam Set set a set + -- @param e element + -- @return `true` if *e* is in *set*, otherwise `false` + -- otherwise + -- @usage + -- if not set.member (keyset, pressed) then return nil end + member = X ("member (Set, any)", member), + + --- Find whether one set is a proper subset of another. + -- @static + -- @function proper_subset + -- @tparam Set set1 a set + -- @tparam table|Set set2 another set, or table + -- @treturn boolean `true` if *set2* contains all elements in *set1* + -- but not only those elements, `false` otherwise + proper_subset = X ("proper_subset (Set, Set|table)", proper_subset), + + --- Find whether one set is a subset of another. + -- @static + -- @function subset + -- @tparam Set set1 a set + -- @tparam table|Set set2 another set, or table + -- @treturn boolean `true` if all elements in *set1* are also in *set2*, + -- `false` otherwise + subset = X ("subset (Set, Set|table)", subset), + + --- Find the symmetric difference of two sets. + -- @static + -- @function symmetric_difference + -- @tparam Set set1 a set + -- @tparam table|Set set2 another set, or table + -- @treturn Set a new set with elements that are in *set1* or *set2* + -- but not both + -- @usage + -- unique = set.symmetric_difference (a, b) + symmetric_difference = X ("symmetric_difference (Set, Set|table)", + symmetric_difference), + + --- Find the union of two sets. + -- @static + -- @function union + -- @tparam Set set1 a set + -- @tparam table|Set set2 another set, or table + -- @treturn Set a copy of *set1* with elements in *set2* merged in + -- @usage + -- all = set.union (a, b) + union = X ("union (Set, Set|table)", union), }, } From c61c098a935ec729948b1cd24d2ebe58c009ef2e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 4 Sep 2014 10:28:28 +0100 Subject: [PATCH 441/703] strbuf: use argscheck on exported apis. * lib/std/strbuf.lua: Move LDocs and add argschecks to returned StrBuf object declarations. * HACKING: Update. Signed-off-by: Gary V. Vaughan --- HACKING | 84 ++++++++++++++++++++++++---------------------- lib/std/strbuf.lua | 55 +++++++++++++----------------- 2 files changed, 68 insertions(+), 71 deletions(-) diff --git a/HACKING b/HACKING index 90d071e..7803b86 100644 --- a/HACKING +++ b/HACKING @@ -1,16 +1,16 @@ ## Lua - - Requiring any stdlib module must not leak any symbols into the - global namespace. To help users who want to do that, there are - monkey_patch functions in the relevant modules. For convenience - when writing throw-away scripts, there's also `std.barrel()`, to - replicate the behaviour of pre-hygienic stdlib. - - - 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` + - Requiring any stdlib module must not leak any symbols into the global + namespace. To help users who want to do that, there are monkey_patch + functions in the relevant modules. For convenience when writing + throw-away scripts, there's also `std.barrel()`, to replicate the + behaviour of pre-hygienic stdlib. + + - 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. @@ -33,21 +33,20 @@ 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 - declarations. In that case use topological ordering to avoid the - forward declarations. + - 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 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. + (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: @@ -61,22 +60,27 @@ s a string t a table - - Do argument check all module functions (functions available in the table - returned from requiring that module -- or listed in the `_functions` - subtable of an object module), preferably using a `debug.argscheck` - wrapper from the module export table, so that functions can call each - other inside the implementation without excessive rechecking of argument - types. + - 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 `debug.argscheck` wrapper around the + internal implementatin: this way, implementation functions can call + each other without excessive rechecking of argument types. - - Don't argument check metamethods, rely on the Lua runtime to diagnose any - errors there. + - 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. + + - Avoid using the `_functions` table in objects as much as possible; + it slows down cloning and complicates the API. ## 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 - `debug.argscheck` or equivalent. + - 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 `debug.argscheck` or equivalent. - `backtick_references` is disabled for stdlib, if you want an inline cross-reference, use `@{reference}`. @@ -85,14 +89,14 @@ - 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`. + - 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 consistently - from a UI perspective even though the implementation needs to be + - 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/lib/std/strbuf.lua b/lib/std/strbuf.lua index 95b84cd..937f616 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -15,29 +15,9 @@ local base = require "std.base" local Object = require "std.object" {} -local insert = base.insert - ---- Add a string to a buffer. --- @static --- @function concat --- @string s string to add --- @treturn StrBuf modified buffer --- @usage --- buf = concat (buf, "append this") -local function concat (self, s) - return insert (self, s) -end - - ---- Convert a buffer to a string. --- @static --- @function tostring --- @treturn string stringified `buf` --- @usage --- string = buf:tostring () -local function tostring (buf) - return table.concat (buf) +local function X (decl, fn) + return require "std.debug".argscheck ("std.strbuf." .. decl, fn) end @@ -47,14 +27,15 @@ end -- @{std.object.Object}. -- @object StrBuf -- @string[opt="StrBuf"] _type object name --- @see std.container -- @see std.object.__call -- @usage -- local std = require "std" --- std.prototype (std.strbuf) --> "StrBuf" +-- local StrBuf = std.strbuf {} +-- local buf = StrBuf {"initial buffer contents"} +-- buf = buf .. "append to buffer" +-- print (buf) -- implicit `tostring` concatenates everything -- os.exit (0) return Object { - -- Derived object type. _type = "StrBuf", --- Support concatenation to StrBuf objects. @@ -65,8 +46,7 @@ return Object { -- @see concat -- @usage -- buf = buf .. str - __concat = concat, - + __concat = X ("__concat (StrBuf, string)", base.insert), --- Support fast conversion to Lua string. -- @function __tostring @@ -75,12 +55,25 @@ return Object { -- @see tostring -- @usage -- str = tostring (buf) - __tostring = tostring, + __tostring = X ("__tostring (StrBuf)", table.concat), - --- @export __index = { - concat = concat, - tostring = tostring, + --- Add a string to a buffer. + -- @static + -- @function concat + -- @string s string to add + -- @treturn StrBuf modified buffer + -- @usage + -- buf = concat (buf, "append this") + concat = X ("concat (StrBuf, string)", base.insert), + + --- Convert a buffer to a string. + -- @static + -- @function tostring + -- @treturn string stringified `buf` + -- @usage + -- string = buf:tostring () + tostring = X ("tostring (StrBuf)", table.concat), }, } From 1187017557b3b930c7c0152c62fee5c6955de85b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 4 Sep 2014 11:08:45 +0100 Subject: [PATCH 442/703] set: check for strict Set type arguments. Since we have type checking to help maintain correctness, don't undermine ourselves and add complexity by adding type coercions and looser type safety. When we're using Set's, passing a table instead of a Set is probably an error, so treat it as one! * specs/set_spec.yaml: Remove specifications for type coercions. * lib/std/set.lua: Remove type coercions and require strict Set arguments everywhere. Add type checks to metamethods. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 10 +++ lib/std/set.lua | 175 ++++++++++++++++++-------------------------- specs/set_spec.yaml | 26 ------- 3 files changed, 83 insertions(+), 128 deletions(-) diff --git a/NEWS b/NEWS index c8c5942..b61c026 100644 --- a/NEWS +++ b/NEWS @@ -276,6 +276,16 @@ Stdlib NEWS - User visible changes - `io.catdir` now raises an error when called with no arguments, for consistency with `io.catfile`. + - `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: + + _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, diff --git a/lib/std/set.lua b/lib/std/set.lua index 56b4aeb..580f3bb 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -65,60 +65,42 @@ local difference, symmetric_difference, intersection, union, subset, function difference (set1, set2) - if prototype (set2) == "table" then - set2 = Set (set2) - end - local t = Set {} + local r = Set {} for e in elems (set1) do if not member (set2, e) then - insert (t, e) + insert (r, e) end end - return t + return r end function symmetric_difference (set1, set2) - if prototype (set2) == "table" then - set2 = Set (set2) - end return difference (union (set1, set2), intersection (set2, set1)) end function intersection (set1, set2) - if prototype (set2) == "table" then - set2 = Set (set2) - end - local t = Set {} + local r = Set {} for e in elems (set1) do if member (set2, e) then - insert (t, e) + insert (r, e) end end - return t + return r end function union (set1, set2) - if prototype (set2) == "table" then - set2 = Set (set2) - end - local t = Set {} - for e in elems (set1) do - insert (t, e) - end + local r = set1 {} for e in elems (set2) do - insert (t, e) + insert (r, e) end - return t + return r end function subset (set1, set2) - if prototype (set2) == "table" then - set2 = Set (set2) - end for e in elems (set1) do if not member (set2, e) then return false @@ -129,9 +111,6 @@ end function proper_subset (set1, set2) - if prototype (set2) == "table" then - t = Set (set2) - end return subset (set1, set2) and not subset (set2, set1) end @@ -152,25 +131,12 @@ local function X (decl, fn) end ---- Signature for cloning Set prototype object. --- @function Set_Clone --- @tparam table elements a list of additional elements --- @treturn Set clone of prototype, with *elements* merged in --- @usage --- local Set = require "std.set" {} --- local set_a = Set {1, 2, 3, 4} --- local set_b = set_a {2, 4, 6, 8} --- print (set_b) --> Set {1, 2, 3, 4, 6, 8} --- os.exit (0) - - --- Set prototype object. -- -- Set also inherits all the fields and methods from -- @{std.container.Container}. -- @object Set -- @string[opt="Set"] _type object name --- @tfield Set_Clone _init initialisation function -- @see std.container -- @see std.object.__call -- @usage @@ -187,90 +153,85 @@ Set = Container { return self end, - --- Union operator. -- @static -- @function __add - -- @tparam Set s a set - -- @tparam table|Set t another set, or table - -- @treturn Set union of *s* and *t* + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn Set everything from *set1* plus everything from *set2* -- @see union -- @usage - -- union = set + {"table"} - __add = union, - + -- union = set1 + set2 + __add = X ("__add (Set, Set)", union), --- Difference operator. -- @static -- @function __sub - -- @tparam Set s a set - -- @tparam table|Set t another set, or table - -- @treturn Set difference between *s* and *t* + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn Set everything from *set1* that is not also in *set2* -- @see difference -- @usage - -- difference = set - {"table"} - __sub = difference, - + -- difference = set1 - set2 + __sub = X ("__sub (Set, Set)", difference), --- Intersection operator. -- @static -- @function __mul - -- @tparam Set s a set - -- @tparam table|Set t another set, or table - -- @treturn Set intersection of *s* and *t* + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn Set anything this is in both *set1* and *set2* -- @see intersection -- @usage - -- intersection = set * {"table"} - __mul = intersection, - + -- intersection = set1 * set2 + __mul = X ("__mul (Set, Set)", intersection), --- Symmetric difference operator. -- @function __div -- @static - -- @tparam Set s a set - -- @tparam table|Set t another set, or table - -- @treturn Set symmetric difference between *s* and *t* + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn Set everything from *set1* or *set2* but not both -- @see symmetric_difference -- @usage - -- symmetric_difference = set / {"table"} - __div = symmetric_difference, - + -- symmetric_difference = set1 / set2 + __div = X ("__div (Set, Set)", symmetric_difference), --- Subset operator. -- @static -- @function __le - -- @tparam Set s a set - -- @tparam table|Set t another set, or table - -- @treturn boolean `true` if *s* is a subset of *t* + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn boolean `true` if everything in *set1* is also in *set2* -- @see subset -- @usage - -- set = set <= {"table"} - __le = subset, - + -- issubset = set1 <= set2 + __le = X ("__le (Set, Set)", subset), --- Proper subset operator. -- @static -- @function __lt - -- @tparam Set s set - -- @tparam table|Set t another set or table - -- @treturn boolean `true` if *s* is a proper subset of *t* + -- @tparam Set set1 set + -- @tparam Set set2 another set + -- @treturn boolean `true` if *set2* is not equal to *set1*, but does + -- contain everything from *set1* -- @see proper_subset -- @usage - -- proper_subset = set < {"table"} - __lt = proper_subset, - + -- ispropersubset = set1 < set2 + __lt = X ("__lt (Set, Set)", proper_subset), -- Return a string representation of this set. -- @treturn string string representation of a set. -- @see std.tostring - __tostring = function (self) - local keys = {} - for k in pairs (self) do - keys[#keys + 1] = tostring (k) - end - table.sort (keys) - return prototype (self) .. " {" .. table.concat (keys, ", ") .. "}" - end, + __tostring = X ("__tostring (Set)", + function (self) + local keys = {} + for k in pairs (self) do + keys[#keys + 1] = tostring (k) + end + table.sort (keys) + return prototype (self) .. " {" .. table.concat (keys, ", ") .. "}" + end), _functions = { @@ -289,16 +250,17 @@ Set = Container { -- @static -- @function difference -- @tparam Set set1 a set - -- @tparam table|Set set2 another set, or table + -- @tparam Set set2 another set -- @treturn Set a copy of *set1* with elements of *set2* removed -- @usage -- all = set.difference (all, {32, 49, 56}) - difference = X ("difference (Set, Set|table)", difference), + difference = X ("difference (Set, Set)", difference), --- Iterator for sets. -- @static -- @function elems -- @tparam Set set a set + -- @treturn *set* iterator -- @todo Make the iterator return only the key -- @usage -- for code in set.elems (isprintable) do print (code) end @@ -308,12 +270,12 @@ Set = Container { -- @static -- @function equal -- @tparam Set set1 a set - -- @tparam table|Set set2 another set, or table + -- @tparam Set set2 another set -- @treturn boolean `true` if *set1* and *set2* each contain identical -- elements, `false` otherwise -- @usage -- if set.equal (keys, {META, CTRL, "x"}) then process (keys) end - equal = X ( "equal (Set, Set|table)", equal), + equal = X ( "equal (Set, Set)", equal), --- Insert an element into a set. -- @static @@ -331,11 +293,11 @@ Set = Container { -- @static -- @function intersection -- @tparam Set set1 a set - -- @tparam table|Set set2 another set, or table + -- @tparam Set set2 another set -- @treturn Set a new set with elements in both *set1* and *set2* -- @usage -- common = set.intersection (a, b) - intersection = X ("intersection (Set, Set|table)", intersection), + intersection = X ("intersection (Set, Set)", intersection), --- Say whether an element is in a set. -- @static @@ -352,41 +314,50 @@ Set = Container { -- @static -- @function proper_subset -- @tparam Set set1 a set - -- @tparam table|Set set2 another set, or table + -- @tparam Set set2 another set -- @treturn boolean `true` if *set2* contains all elements in *set1* -- but not only those elements, `false` otherwise - proper_subset = X ("proper_subset (Set, Set|table)", proper_subset), + -- @usage + -- if set.proper_subset (a, b) then + -- for e in set.elems (set.difference (b, a)) do + -- set.delete (b, e) + -- end + -- end + -- assert (set.equal (a, b)) + proper_subset = X ("proper_subset (Set, Set)", proper_subset), --- Find whether one set is a subset of another. -- @static -- @function subset -- @tparam Set set1 a set - -- @tparam table|Set set2 another set, or table + -- @tparam Set set2 another set -- @treturn boolean `true` if all elements in *set1* are also in *set2*, -- `false` otherwise - subset = X ("subset (Set, Set|table)", subset), + -- @usage + -- if set.subset (a, b) then a = b end + subset = X ("subset (Set, Set)", subset), --- Find the symmetric difference of two sets. -- @static -- @function symmetric_difference -- @tparam Set set1 a set - -- @tparam table|Set set2 another set, or table + -- @tparam Set set2 another set -- @treturn Set a new set with elements that are in *set1* or *set2* -- but not both -- @usage -- unique = set.symmetric_difference (a, b) - symmetric_difference = X ("symmetric_difference (Set, Set|table)", + symmetric_difference = X ("symmetric_difference (Set, Set)", symmetric_difference), --- Find the union of two sets. -- @static -- @function union -- @tparam Set set1 a set - -- @tparam table|Set set2 another set, or table + -- @tparam Set set2 another set -- @treturn Set a copy of *set1* with elements in *set2* merged in -- @usage -- all = set.union (a, b) - union = X ("union (Set, Set|table)", union), + union = X ("union (Set, Set)", union), }, } diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index d4b8c46..ac87faf 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -61,8 +61,6 @@ specify std.set: expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members of the first that are not in the second: expect (Set.difference (r, s)).to_equal (Set {"foo"}) - - it coerces a table argument to a set: - expect (Set.difference (r, {"bar"})).to_equal (Set {"baz", "foo"}) - context when called as a set metamethod: - it returns a set object: expect (prototype (r - s)).to_be "Set" @@ -72,8 +70,6 @@ specify std.set: expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members of the first that are not in the second: expect (r - s).to_equal (Set {"foo"}) - - it coerces a table argument to a set: - expect (r - {"bar"}).to_equal (Set {"baz", "foo"}) - describe elems: @@ -148,9 +144,6 @@ specify std.set: - it returns a set containing members common to both arguments: expect (Set.intersection (r, s)). to_equal (Set {"bar", "baz"}) - - it coerces a table argument to a set: - expect (Set.intersection (r, {"bar", "quux"})). - to_equal (Set {"bar"}) - context when called as a set metamethod: - it returns a set object: q = r * s @@ -161,8 +154,6 @@ specify std.set: expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members common to both arguments: expect (r * s).to_equal (Set {"bar", "baz"}) - - it coerces a table argument to a set: - expect (r * {"bar", "quux"}).to_equal (Set {"bar"}) - describe member: @@ -200,9 +191,6 @@ specify std.set: - it fails when set does not contain all elements of another: s = s + Set {"quux"} expect (Set.proper_subset (r, s)).to_be (false) - - it coerces a table argument to a set: - expect (Set.proper_subset (s, {"foo", "bar", "baz"})).to_be (true) - expect (Set.proper_subset (s, {"foo"})).to_be (false) - context when called as a set metamethod: - it succeeds when set contains all elements of another: expect (s < r).to_be (true) @@ -228,9 +216,6 @@ specify std.set: - it fails when set does not contain all elements of another: s = s + Set {"quux"} expect (Set.subset (r, s)).to_be (false) - - it coerces a table argument to a set: - expect (Set.subset (s, {"foo", "bar", "baz"})).to_be (true) - expect (Set.subset (s, {"foo"})).to_be (false) - context when called as a set metamethod: - it succeeds when set contains all elements of another: expect (s <= r).to_be (true) @@ -258,9 +243,6 @@ specify std.set: - it returns a set containing members in only one argument set: expect (Set.symmetric_difference (r, s)). to_equal (Set {"foo", "quux"}) - - it coerces a table argument to a set: - expect (Set.symmetric_difference (r, {"bar"})). - to_equal (Set {"baz", "foo"}) - context when called as a set metamethod: - it returns a set object: expect (prototype (r / s)).to_be "Set" @@ -270,8 +252,6 @@ specify std.set: expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: expect (r / s).to_equal (Set {"foo", "quux"}) - - it coerces a table argument to a set: - expect (r / {"bar"}).to_equal (Set {"baz", "foo"}) - describe union: @@ -289,9 +269,6 @@ specify std.set: - it returns a set containing members in only one argument set: expect (Set.union (r, s)). to_equal (Set {"foo", "bar", "baz", "quux"}) - - it coerces a table argument to a set: - expect (Set.union (r, {"quux"})). - to_equal (Set {"foo", "bar", "baz", "quux"}) - context when called as a set metamethod: - it returns a set object: expect (prototype (r + s)).to_be "Set" @@ -301,9 +278,6 @@ specify std.set: expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: expect (r + s).to_equal (Set {"foo", "bar", "baz", "quux"}) - - it coerces a table argument to a set: - expect (r + {"quux"}). - to_equal (Set {"foo", "bar", "baz", "quux"}) - describe __tostring: From 7bec242fa9930b870d41b706655d910f6a1101f6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 4 Sep 2014 11:56:33 +0100 Subject: [PATCH 443/703] tree: use argscheck on exported apis. * lib/std/tree.lua: Move LDocs and add argschecks to returned Tree object function declarations. Signed-off-by: Gary V. Vaughan --- lib/std/tree.lua | 330 +++++++++++++++++++++++------------------------ 1 file changed, 160 insertions(+), 170 deletions(-) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index f60a333..2e3d15c 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -2,7 +2,7 @@ Tree container prototype. Note that Functions listed below are only available from the Tree - prototype return by requiring this module, because Container objects + prototype returned by requiring this module, because Container objects cannot have object methods. Prototype Chain @@ -22,7 +22,7 @@ local operator = require "std.operator" local Container = require "std.container" {} -local ielems, ipairs, base_leaves, pairs, prototype = +local ielems, ipairs, leaves, pairs, prototype = base.ielems, base.ipairs, base.leaves, base.pairs, base.prototype local last, len = base.last, base.len local reduce = base.reduce @@ -31,11 +31,6 @@ local Tree -- forward declaration ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - --- Tree iterator. -- @tparam function it iterator function -- @tparam tree|table tr tree or tree-like table @@ -61,73 +56,12 @@ local function _nodes (it, tr) end - ---[[ ================= ]]-- ---[[ Module Functions. ]]-- ---[[ ================= ]]-- - - ---- Tree iterator which returns just numbered leaves, in order. --- @static --- @function ileaves --- @tparam Tree|table tr tree or tree-like table --- @treturn function iterator function --- @treturn Tree|table the tree *tr* --- @see inodes --- @see leaves --- @usage --- --> t = {"one", "three", "five"} --- for leaf in ileaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} --- do --- t[#t + 1] = leaf --- end -local function ileaves (tr) - assert (type (tr) == "table", - "bad argument #1 to 'ileaves' (table expected, got " .. type (tr) .. ")") - return base_leaves (ipairs, tr) -end - - ---- Tree iterator which returns just leaves. --- @static --- @function leaves --- @tparam Tree|table tr tree or tree-like table --- @treturn function iterator function --- @treturn Tree|table the tree, *tr* --- @see ileaves --- @see nodes --- @usage --- for leaf in leaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} --- do --- t[#t + 1] = leaf --- end --- --> t = {2, 4, "five", "foo", "one", "three"} --- table.sort (t, lambda "=tostring(_1) < tostring(_2)") -local function leaves (tr) - assert (type (tr) == "table", - "bad argument #1 to 'leaves' (table expected, got " .. type (tr) .. ")") - return base_leaves (pairs, tr) -end - - ---- Make a deep copy of a tree, including any metatables. --- @tparam Tree|table tr tree or tree-like table --- @tparam boolean nometa if non-`nil` don't copy metatables --- @treturn Tree|table a deep copy of *tr* --- @see std.table.clone --- @usage --- tr = {"one", {two=2}, {{"three"}, four=4}} --- copy = clone (tr) --- copy[2].two=5 --- assert (tr[2].two == 2) -local function clone (tr, nometa) - assert (type (tr) == "table", - "bad argument #1 to 'clone' (table expected, got " .. type (tr) .. ")") +local function clone (t, nometa) local r = {} if not nometa then - setmetatable (r, getmetatable (tr)) + setmetatable (r, getmetatable (t)) end - local d = {[tr] = r} + local d = {[t] = r} local function copy (o, x) for i, v in pairs (x) do if type (v) == "table" then @@ -146,83 +80,17 @@ local function clone (tr, nometa) end return o end - return copy (r, tr) -end - - ---- Tree iterator over all nodes. --- --- The returned iterator function performs a depth-first traversal of --- `tr`, and at each node it returns `{node-type, tree-path, tree-node}` --- where `node-type` is `branch`, `join` or `leaf`; `tree-path` is a --- list of keys used to reach this node, and `tree-node` is the current --- node. --- --- Note that the `tree-path` reuses the same table on each iteration, so --- you must `table.clone` a copy if you want to take a snap-shot of the --- current state of the `tree-path` list before the next iteration --- changes it. --- @tparam Tree|table tr tree or tree-like table to iterate over --- @treturn function iterator function --- @treturn Tree|table the tree, *tr* --- @see inodes --- @usage --- -- tree = +-- node1 --- -- | +-- leaf1 --- -- | '-- leaf2 --- -- '-- leaf 3 --- tree = Tree { Tree { "leaf1", "leaf2"}, "leaf3" } --- for node_type, path, node in nodes (tree) do --- print (node_type, path, node) --- end --- --> "branch" {} {{"leaf1", "leaf2"}, "leaf3"} --- --> "branch" {1} {"leaf1", "leaf"2") --- --> "leaf" {1,1} "leaf1" --- --> "leaf" {1,2} "leaf2" --- --> "join" {1} {"leaf1", "leaf2"} --- --> "leaf" {2} "leaf3" --- --> "join" {} {{"leaf1", "leaf2"}, "leaf3"} --- os.exit (0) -local function nodes (tr) - assert (type (tr) == "table", - "bad argument #1 to 'nodes' (table expected, got " .. type (tr) .. ")") - return _nodes (pairs, tr) + return copy (r, t) end ---- Tree iterator over numbered nodes, in order. --- --- The iterator function behaves like @{nodes}, but only traverses the --- array part of the nodes of *tr*, ignoring any others. --- @tparam Tree|table tr tree or tree-like table to iterate over --- @treturn function iterator function --- @treturn tree|table the tree, *tr* --- @see nodes -local function inodes (tr) - assert (type (tr) == "table", - "bad argument #1 to 'inodes' (table expected, got " .. type (tr) .. ")") - return _nodes (ipairs, tr) -end - - ---- Destructively deep-merge one tree into another. --- @tparam Tree|table tr destination tree or table --- @tparam Tree|table ur tree or table with nodes to merge --- @treturn Tree|table *tr* with nodes from *ur* merged in --- @see std.table.merge --- @usage --- merge (dest, {{exists=1}, {{not = {present = { inside = "dest" }}}}}) -local function merge (tr, ur) - assert (type (tr) == "table", - "bad argument #1 to 'merge' (table expected, got " .. type (tr) .. ")") - assert (type (ur) == "table", - "bad argument #2 to 'merge' (table expected, got " .. type (ur) .. ")") - for ty, p, n in nodes (ur) do +local function merge (t, u) + for ty, p, n in _nodes (pairs, u) do if ty == "leaf" then - tr[p] = n + t[p] = n end end - return tr + return t end @@ -232,9 +100,30 @@ end --[[ ============ ]]-- +local function X (decl, fn) + return require "std.debug".argscheck ("std.tree." .. decl, fn) +end + + --- Tree prototype object. -- @object Tree -- @string[opt="Tree"] _type object name +-- @see std.container +-- @see std.object.__call +-- @usage +-- local std = require "std" +-- local Tree = std.tree {} +-- local tr = Tree {} +-- tr[{"branch1", 1}] = "leaf1" +-- tr[{"branch1", 2}] = "leaf2" +-- tr[{"branch2", 1}] = "leaf3" +-- print (tr[{"branch1"}]) --> Tree {leaf1, leaf2} +-- print (tr[{"branch1", 2}]) --> leaf2 +-- print (tr[{"branch1", 3}]) --> nil +-- --> leaf1 leaf2 leaf3 +-- for leaf in std.tree.leaves (tr) do +-- io.write (leaf .. "\t") +-- end Tree = Container { _type = "Tree", @@ -248,44 +137,145 @@ Tree = Container { -- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? -- @usage -- del_other_window = keymap[{"C-x", "4", KEY_DELETE}] - __index = function (tr, i) - if prototype (i) == "table" then - return reduce (operator.deref, tr, ielems, i) - else - return rawget (tr, i) - end - end, + __index = X ("__index (Tree, any)", + function (tr, i) + if prototype (i) == "table" then + return reduce (operator.deref, tr, ielems, i) + else + return rawget (tr, i) + end + end), --- Deep insertion. -- @static -- @function __newindex -- @tparam Tree tr a tree -- @param i non-table, or list of keys `{i1, ...i_n}` - -- @param v value + -- @param[opt] v value -- @usage -- function bindkey (keylist, fn) keymap[keylist] = fn end - __newindex = function (tr, i, v) - if prototype (i) == "table" then - for n = 1, len (i) - 1 do - if prototype (tr[i[n]]) ~= "Tree" then - rawset (tr, i[n], Tree {}) - end - tr = tr[i[n]] - end - rawset (tr, last (i), v) - else - rawset (tr, i, v) - end - end, + __newindex = X ("__newindex (Tree, any, any?)", + function (tr, i, v) + if prototype (i) == "table" then + for n = 1, len (i) - 1 do + if prototype (tr[i[n]]) ~= "Tree" then + rawset (tr, i[n], Tree {}) + end + tr = tr[i[n]] + end + rawset (tr, last (i), v) + else + rawset (tr, i, v) + end + end), - --- @export _functions = { - clone = clone, - ileaves = ileaves, - inodes = inodes, - leaves = leaves, - merge = merge, - nodes = nodes, + --- Make a deep copy of a tree, including any metatables. + -- @static + -- @function clone + -- @tparam table t tree or tree-like table + -- @tparam boolean nometa if non-`nil` don't copy metatables + -- @treturn Tree|table a deep copy of *tr* + -- @see std.table.clone + -- @usage + -- tr = {"one", {two=2}, {{"three"}, four=4}} + -- copy = clone (tr) + -- copy[2].two=5 + -- assert (tr[2].two == 2) + clone = X ("clone (table, boolean|:nometa?)", clone), + + --- Tree iterator which returns just numbered leaves, in order. + -- @static + -- @function ileaves + -- @tparam Tree|table tr tree or tree-like table + -- @treturn function iterator function + -- @treturn Tree|table the tree *tr* + -- @see inodes + -- @see leaves + -- @usage + -- --> t = {"one", "three", "five"} + -- for leaf in ileaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} + -- do + -- t[#t + 1] = leaf + -- end + ileaves = X ("ileaves (table)", function (t) return leaves (ipairs, t) end), + + --- Tree iterator over numbered nodes, in order. + -- + -- The iterator function behaves like @{nodes}, but only traverses the + -- array part of the nodes of *tr*, ignoring any others. + -- @static + -- @function inodes + -- @tparam Tree|table tr tree or tree-like table to iterate over + -- @treturn function iterator function + -- @treturn tree|table the tree, *tr* + -- @see nodes + inodes = X ("inodes (table)", function (t) return _nodes (ipairs, t) end), + + --- Tree iterator which returns just leaves. + -- @static + -- @function leaves + -- @tparam table t tree or tree-like table + -- @treturn function iterator function + -- @treturn table *t* + -- @see ileaves + -- @see nodes + -- @usage + -- for leaf in leaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} + -- do + -- t[#t + 1] = leaf + -- end + -- --> t = {2, 4, "five", "foo", "one", "three"} + -- table.sort (t, lambda "=tostring(_1) < tostring(_2)") + leaves = X ("leaves (table)", function (t) return leaves (pairs, t) end), + + --- Destructively deep-merge one tree into another. + -- @static + -- @function merge + -- @tparam table t destination tree + -- @tparam table u table with nodes to merge + -- @treturn table *t* with nodes from *u* merged in + -- @see std.table.merge + -- @usage + -- merge (dest, {{exists=1}, {{not = {present = { inside = "dest" }}}}}) + merge = X ("merge (table, table)", merge), + + --- Tree iterator over all nodes. + -- + -- The returned iterator function performs a depth-first traversal of + -- `tr`, and at each node it returns `{node-type, tree-path, tree-node}` + -- where `node-type` is `branch`, `join` or `leaf`; `tree-path` is a + -- list of keys used to reach this node, and `tree-node` is the current + -- node. + -- + -- Note that the `tree-path` reuses the same table on each iteration, so + -- you must `table.clone` a copy if you want to take a snap-shot of the + -- current state of the `tree-path` list before the next iteration + -- changes it. + -- @static + -- @function nodes + -- @tparam Tree|table tr tree or tree-like table to iterate over + -- @treturn function iterator function + -- @treturn Tree|table the tree, *tr* + -- @see inodes + -- @usage + -- -- tree = +-- node1 + -- -- | +-- leaf1 + -- -- | '-- leaf2 + -- -- '-- leaf 3 + -- tree = Tree { Tree { "leaf1", "leaf2"}, "leaf3" } + -- for node_type, path, node in nodes (tree) do + -- print (node_type, path, node) + -- end + -- --> "branch" {} {{"leaf1", "leaf2"}, "leaf3"} + -- --> "branch" {1} {"leaf1", "leaf"2") + -- --> "leaf" {1,1} "leaf1" + -- --> "leaf" {1,2} "leaf2" + -- --> "join" {1} {"leaf1", "leaf2"} + -- --> "leaf" {2} "leaf3" + -- --> "join" {} {{"leaf1", "leaf2"}, "leaf3"} + -- os.exit (0) + nodes = X ("nodes (table)", function (t) return _nodes (pairs, t) end), }, } From 37b1de80c0fcf8f75d1afed64ffe4a7c1479bc82 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 1 Oct 2014 06:07:42 +0100 Subject: [PATCH 444/703] refactor: rationalize deprecation interfaces. Tracking one deprecation warning per deprecated function is fiddly and confusing. Change the semantics to this: by default calling deprecated API fires a warning message; turn off the messages with `_DEBUG.deprecate = false`; elide deprecated APIs entirely with `_DEBUG.deprecate = true`. Easy and useful :) * lib/std/debug_init/init.lua (_ARGCHECK): Remove. Adjust all callers to use _DEBUG.argcheck instead. (_DEBUG): Always a table, with fields initialised according to global _DEBUG if necessary. Simplify all callers accordingly. * lib/std/debug.lua (setcompat, getcompat): Remove. (DEPRECATIONMSG): If _DEBUG.deprecate is nil, always return a deprecation message, otherwise the empty string. (DEPRECATED): If _DEBUG.deprecate is truthy, don't return a function at all. (_setdebug): New private function. A reliable way to jigger the _DEBUG table contents, without worrying about nested specl environments. * specs/spec_helper.lua (setdebug): Import std.debug._setdebug into the outermost execution environment. * specs/debug_spec.yaml (extend_base): Add _setdebug. Adjust other behaviour specs to match saner _DEBUG.deprecate semantics. * specs/functional_spec.yaml, specs/list_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Adjust deprecation warning behaviour examples to match new semantics. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 36 ++++++---- lib/std/container.lua | 4 +- lib/std/debug.lua | 82 +++++++++++----------- lib/std/debug_init/init.lua | 44 +++++++++--- lib/std/vector.lua | 4 +- specs/debug_spec.yaml | 33 +++++---- specs/functional_spec.yaml | 56 +++++++++++---- specs/list_spec.yaml | 132 +++++++++++++++++++++++++++--------- specs/spec_helper.lua | 4 ++ specs/string_spec.yaml | 12 +++- specs/table_spec.yaml | 16 +++-- 11 files changed, 281 insertions(+), 142 deletions(-) diff --git a/NEWS b/NEWS index b61c026..31bdf82 100644 --- a/NEWS +++ b/NEWS @@ -156,18 +156,30 @@ Stdlib NEWS - User visible changes - 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. - - - By default, deprecated APIs will issue a warning to stderr on first - use only. However, you can turn off these warnings entirely with: - - _DEBUG = { compat = true } - - Or, you can issue the warnings on every use with: - - _DEBUG = { compat = false } - - The `_DEBUG` global must be set before requiring any stdlib modules. + will most likely be removed entirely. You can prevent that by + raising an issue at + 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: + + _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: + + _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. diff --git a/lib/std/container.lua b/lib/std/container.lua index dec3503..eb21aa4 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -31,7 +31,7 @@ ]] -local _ARGCHECK = require "std.debug_init"._ARGCHECK +local _DEBUG = require "std.debug_init"._DEBUG local base = require "std.base" local debug = require "std.debug" @@ -201,7 +201,7 @@ local M = { } -if _ARGCHECK then +if _DEBUG.argcheck then local toomanyargmsg = debug.toomanyargmsg diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 86f814c..5f180a2 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -32,7 +32,6 @@ local debug_init = require "std.debug_init" local base = require "std.base" -local _ARGCHECK = debug_init._ARGCHECK local _DEBUG = debug_init._DEBUG local argerror = base.argerror local split, tostring = base.split, base.tostring @@ -42,31 +41,13 @@ local ipairs, pairs = base.ipairs, base.pairs local M - ---- Determine whether *key* will show a deprecation warning on next access. -local compat = {} - -local function setcompat (key) - compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat == nil) or _DEBUG == true -end - - -local function getcompat (key) - if compat[key] == nil then - -- Whether to warn on first access. - compat[key] = (type (_DEBUG) == "table" and _DEBUG.compat) or _DEBUG == false - end - return compat[key] -end - - +-- Return a deprecation message if _DEBUG.deprecate is `nil`, otherwise "". local function DEPRECATIONMSG (version, name, extramsg, level) if level == nil then level, extramsg = extramsg, nil end extramsg = extramsg or "and will be removed entirely in a future release" local _, where = pcall (function () error ("", level + 3) end) - if not getcompat (name) then - setcompat (name) + if _DEBUG.deprecate == nil then return (where .. string.format ("%s was deprecated in release %s, %s.\n", name, tostring (version), extramsg)) end @@ -75,12 +56,16 @@ local function DEPRECATIONMSG (version, name, extramsg, level) end +-- Define deprecated functions when _DEBUG.deprecate is not "truthy", +-- and write `DEPRECATIONMSG` output to stderr. local function DEPRECATED (version, name, extramsg, fn) if fn == nil then fn, extramsg = extramsg, nil end - return function (...) - io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) - return fn (...) + if not _DEBUG.deprecate then + return function (...) + io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) + return fn (...) + end end end @@ -148,7 +133,7 @@ end local argcheck, argscheck -- forward declarations -if _ARGCHECK then +if _DEBUG.argcheck then local copy, prototype = base.copy, base.prototype @@ -530,18 +515,15 @@ end local function say (n, ...) - local level = 1 - local arg = {n, ...} - if type (arg[1]) == "number" then - level = arg[1] - table.remove (arg, 1) + local level, argt = n, {...} + if type (n) ~= "number" then + level, argt = 1, {n, ...} end - if _DEBUG and - ((type (_DEBUG) == "table" and type (_DEBUG.level) == "number" and - _DEBUG.level >= level) - or level <= 1) then + if _DEBUG.level ~= math.huge and + ((type (_DEBUG.level) == "number" and _DEBUG.level >= level) or level <= 1) + then local t = {} - for k, v in pairs (arg) do t[k] = tostring (v) end + for k, v in pairs (argt) do t[k] = tostring (v) end io.stderr:write (table.concat (t, "\t") .. "\n") end end @@ -585,7 +567,10 @@ end M = { - --- Write a deprecation warning to stderr. + --- Provide a deprecated function definition according to _DEBUG.deprecate. + -- You can check whether your covered code uses deprecated functions by + -- setting `_DEBUG.deprecate` to `true` before loading any stdlib modules, + -- or silence deprecation warnings by setting `_DEBUG.deprecate = false`. -- @function DEPRECATED -- @string version first deprecation release version -- @string name function name for automatic warning message @@ -597,9 +582,6 @@ M = { DEPRECATED = DEPRECATED, --- Format a deprecation warning message. - -- If `_DEBUG.compat` is not set, warn only the first time *fn* is called; - -- if `_DEBUG.compat` is false, warn every time *fn* is called; - -- otherwise don't write any warnings, and run *fn* normally. -- @function DEPRECATIONMSG -- @string version first deprecation release version -- @string name function name for automatic warning message @@ -701,6 +683,7 @@ M = { say = say, --- Format a standard "too many arguments" error message. + -- @fixme remove this wart! -- @function toomanyargmsg -- @string name function name -- @number expect maximum number of arguments accepted @@ -722,6 +705,15 @@ M = { -- _DEBUG = { call = true } -- local debug = require "std.debug" trace = trace, + + + -- Private: + _setdebug = function (t) + for k, v in pairs (t) do + if v == "nil" then v = nil end + _DEBUG[k] = v + end + end, } @@ -746,14 +738,16 @@ return setmetatable (M, metatable) --- Control std.debug function behaviour. --- To activate debugging set _DEBUG either to any true value --- (equivalent to {level = 1}), or as documented below. +-- To declare debugging state, set _DEBUG either to `false` to disable all +-- runtime debugging; to any "truthy" value (equivalent to enabling everything +-- except *call*, or as documented below. -- @class table -- @name _DEBUG -- @tfield[opt=true] boolean argcheck honor argcheck and argscheck calls -- @tfield[opt=false] boolean call do call trace debugging --- @field[opt=nil] compat if `false`, always complain whenever a deprecated --- api is called; if `nil` complain on first use of each deprecated api; --- any other value disables deprecation warnings altogether +-- @field[opt=nil] deprecate if `false`, deprecated APIs are defined, +-- and do not issue deprecation warnings when used; if `nil` issue a +-- deprecation warning each time a deprecated api is used; any other +-- value causes deprecated APIs not to be defined at all -- @tfield[opt=1] int level debugging level -- @usage _DEBUG = { argcheck = false, level = 9 } diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index 186967f..17bd557 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -1,20 +1,44 @@ -- Debugging is on by default -local M = { - _DEBUG = true, - _ARGCHECK = true, -} +local M = {} -if _G._DEBUG ~= nil then +-- User specified fields. +if type (_G._DEBUG) == "table" then M._DEBUG = _G._DEBUG + +-- Turn everything off. +elseif _G._DEBUG == false then + M._DEBUG = { + argcheck = false, + call = false, + deprecate = false, + level = math.huge, + } + +-- Turn everything on (except _DEBUG.call must be set explicitly). +elseif _G._DEBUG == true then + M._DEBUG = { + argcheck = true, + call = false, + deprecate = true, + } + +else + M._DEBUG = {} end --- Argument checking is on by default -M._ARGCHECK = M._DEBUG -if type (M._DEBUG) == "table" then - M._ARGCHECK = M._DEBUG.argcheck - if M._ARGCHECK == nil then M._ARGCHECK= true end +local function setdefault (field, value) + if M._DEBUG[field] == nil then + M._DEBUG[field] = value + end end +-- Default settings if otherwise unspecified. +setdefault ("argcheck", true) +setdefault ("call", false) +setdefault ("deprecate", nil) +setdefault ("level", 1) + + return M diff --git a/lib/std/vector.lua b/lib/std/vector.lua index 6be4789..149d613 100644 --- a/lib/std/vector.lua +++ b/lib/std/vector.lua @@ -29,7 +29,7 @@ ]] -local _ARGCHECK = require "std.debug_init"._ARGCHECK +local _DEBUG = require "std.debug_init"._DEBUG local have_alien, alien = pcall (require, "alien") local base = require "std.base" @@ -207,7 +207,7 @@ core_metatable = { -- --> 57005 48879 65261 57005 nil -- print (a[1], a[2], a[3], a[-3], a[-4]) __call = function (self, type, init) - if _ARGCHECK then + if _DEBUG.argcheck then if init ~= nil then -- When called with 2 arguments: argcheck ("Vector", 1, "string", type) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 75c2fc6..427f839 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,7 +4,8 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "argscheck", "say", "toomanyargmsg", "trace" } + "argscheck", "say", "toomanyargmsg", "trace", + "_setdebug" } M = require (this_module) @@ -77,27 +78,25 @@ specify std.debug: fn () -- line 3 fn () -- line 4 ]] - - it warns only on first call by default: + - it warns every call by default: expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" - - it warns only on first call with _DEBUG set to true: - script = "_DEBUG = true " .. script - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" - - it warns only on first call with _DEBUG.compat unset: - script = "_DEBUG = {} " .. script - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).not_to_match_error "\n%S+:4:.*deprecated" - - it does not warn at all when set to false: | + expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" + - it does not warn at all with _DEBUG set to false: script = "_DEBUG = false " .. script expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - - it does not warn at all when compat field is set to true: | - script = "_DEBUG = { compat = true } " .. script - expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - - it warns on every call with compat field set to false: - script = "_DEBUG = { compat = false } " .. script + - it does not define the function with _DEBUG set to true: | + script = "_DEBUG = true " .. script + expect (luaproc (script)).to_contain_error ":3: attempt to call global 'fn'" + - it warns on every call with _DEBUG.deprecate unset: + script = "_DEBUG = {} " .. script expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" + - it does not warn at all with _DEBUG.deprecate set to false: + script = "_DEBUG = { deprecate = false } " .. script + expect (luaproc (script)).not_to_match_error "%d:.*deprecated" + - it warns on every call with _DEBUG.deprecate set to true: | + script = "_DEBUG = { deprecate = true } " .. script + expect (luaproc (script)).to_contain_error ":3: attempt to call global 'fn'" - describe DEPRECATIONMSG: diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 8ebec3e..59834ad 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -38,10 +38,12 @@ specify std.functional: - it diagnoses wrong argument types: expect (f (false)).to_raise (badarg (1, "function", "boolean")) - - it writes an argument passing deprecation warning on first call: - expect (capture (f, {init, M, fname})). + - it writes an argument passing deprecation warning: + setdebug { deprecate = "nil" } + expect (capture (f, {init, M, fname})). to_contain_error "was deprecated" - expect (capture (f, {init, M, fname})). + setdebug { deprecate = false } + expect (capture (f, {init, M, fname})). not_to_contain_error "was deprecated" - it does not affect normal operation if no arguments are bound: @@ -216,8 +218,10 @@ specify std.functional: - before: f = M.eval - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {"42"})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {"42"})).not_to_contain_error "was deprecated" - it diagnoses invalid lua: @@ -269,9 +273,11 @@ specify std.functional: op = require "std.operator" f = M.fold - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {M.id, 1, ipairs, {}})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {M.id, 1, ipairs, {}})). not_to_contain_error "was deprecated" @@ -521,9 +527,11 @@ specify std.functional: - before: f = M.op["[]"] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {{2}, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {{2}, 1})). not_to_contain_error "was deprecated" @@ -536,9 +544,11 @@ specify std.functional: - before: f = M.op["+"] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {2, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {2, 1})). not_to_contain_error "was deprecated" @@ -549,9 +559,11 @@ specify std.functional: - before: f = M.op["-"] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {2, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {2, 1})). not_to_contain_error "was deprecated" @@ -562,9 +574,11 @@ specify std.functional: - before: f = M.op["*"] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {2, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {2, 1})). not_to_contain_error "was deprecated" @@ -575,9 +589,11 @@ specify std.functional: - before: f = M.op["/"] - - it writes a deprecation warning on first call: + - it writes a deprecation warning on: + setdebug { deprecate = "nil" } expect (capture (f, {2, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {2, 1})). not_to_contain_error "was deprecated" @@ -588,9 +604,11 @@ specify std.functional: - before: f = M.op["and"] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {true, false})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {true, false})). not_to_contain_error "was deprecated" @@ -609,9 +627,11 @@ specify std.functional: - before: f = M.op["or"] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {true, false})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {true, false})). not_to_contain_error "was deprecated" @@ -630,9 +650,11 @@ specify std.functional: - before: f = M.op["not"] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {true})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {true})). not_to_contain_error "was deprecated" @@ -647,9 +669,11 @@ specify std.functional: - before: f = M.op["=="] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {2, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {2, 1})). not_to_contain_error "was deprecated" @@ -664,9 +688,11 @@ specify std.functional: - before: f = M.op["~="] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {2, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {2, 1})). not_to_contain_error "was deprecated" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 3c1037a..a58ca49 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -325,8 +325,10 @@ specify std.list: - before: f = M.depair - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a primitive table: @@ -341,8 +343,10 @@ specify std.list: - before: f = l.depair - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a primitive table: @@ -358,8 +362,10 @@ specify std.list: - before: f = M.elems - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {{}})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {{}})).not_to_contain_error "was deprecated" - it is an iterator over List members: @@ -375,8 +381,10 @@ specify std.list: - before: f = l.elems - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it is an iterator over List members: @@ -394,8 +402,10 @@ specify std.list: t = {"first", "second", third = 4} f = M.enpair - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {t})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {t})).not_to_contain_error "was deprecated" - context as a module function: @@ -417,8 +427,10 @@ specify std.list: - before: f = M.filter - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {p, l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {p, l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -432,8 +444,10 @@ specify std.list: - before: f = l.filter - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, p})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, p})).not_to_contain_error "was deprecated" - it returns a List object: @@ -452,8 +466,10 @@ specify std.list: - before: f = M.flatten - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -469,8 +485,10 @@ specify std.list: - before: f = l.flatten - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -492,9 +510,11 @@ specify std.list: - before: f = M.foldl - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {op.sum, 1, l})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {op.sum, 1, l})). not_to_contain_error "was deprecated" @@ -519,9 +539,11 @@ specify std.list: - before: f = l.foldl - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, op.sum, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, op.sum, 1})). not_to_contain_error "was deprecated" @@ -543,9 +565,11 @@ specify std.list: - before: f = M.foldr - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {op.sum, 1, {10}})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {op.sum, 1, {10}})). not_to_contain_error "was deprecated" @@ -571,9 +595,11 @@ specify std.list: - before: f = l.foldr - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, op.sum, 1})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, op.sum, 1})). not_to_contain_error "was deprecated" @@ -591,9 +617,11 @@ specify std.list: - before: f = M.index_key - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {1, List {{1}}})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {1, List {{1}}})). not_to_contain_error "was deprecated" @@ -620,8 +648,10 @@ specify std.list: - before: f = l.index_key - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, 1})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, 1})).not_to_contain_error "was deprecated" - it makes a map of matched table field values to table List offsets: @@ -649,9 +679,11 @@ specify std.list: - before: f = M.index_value - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {1, List {{1}}})). to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {1, List {{1}}})). not_to_contain_error "was deprecated" @@ -680,8 +712,10 @@ specify std.list: f = l.index_value - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, 1})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, 1})).not_to_contain_error "was deprecated" - it makes a table of matched table field values to table List references: @@ -713,8 +747,10 @@ specify std.list: - before: f, badarg = init (M, this_module, "map") - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {sq, l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {sq, l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -734,8 +770,10 @@ specify std.list: - before: f = l.map - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, sq})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, sq})).not_to_contain_error "was deprecated" - it returns a List object: @@ -762,8 +800,10 @@ specify std.list: - before: f = M.map_with - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {fn, l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {fn, l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -785,8 +825,10 @@ specify std.list: - before: f = l.map_with - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, fn})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - it returns a List object: @@ -817,8 +859,10 @@ specify std.list: - before: f = M.project - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {"third", l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {"third", l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -834,8 +878,10 @@ specify std.list: - before: f = l.project - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, "third"})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, "third"})).not_to_contain_error "was deprecated" - it returns a List object: @@ -853,8 +899,10 @@ specify std.list: - before: f = M.relems - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it is a reverse iterator over List members: @@ -870,8 +918,10 @@ specify std.list: - before: f = l.relems - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it is a reverse iterator over List members: @@ -929,8 +979,10 @@ specify std.list: - before: f = M.reverse - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {{}})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {{}})).not_to_contain_error "was deprecated" - it returns a List object: @@ -949,8 +1001,10 @@ specify std.list: - before: f = l.reverse - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -973,8 +1027,10 @@ specify std.list: - before: f = M.shape - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {{0}, l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {{0}, l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -1001,8 +1057,10 @@ specify std.list: - before: f = l.shape - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, {0}})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, {0}})).not_to_contain_error "was deprecated" - it returns a List object: @@ -1126,8 +1184,10 @@ specify std.list: - before: f = M.transpose - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -1146,8 +1206,10 @@ specify std.list: - before: f = l.transpose - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: @@ -1173,8 +1235,10 @@ specify std.list: - before: f = M.zip_with - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, fn})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - it returns a List object: @@ -1193,8 +1257,10 @@ specify std.list: - before: f = l.zip_with - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {l, fn})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - it returns a List object: diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 2acea40..5caa2b4 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -10,6 +10,10 @@ package.path = std.package.normalize ("lib/?.lua", "lib/?/init.lua", package.pat local LUA = os.getenv "LUA" or "lua" +-- Tweak _DEBUG without tripping over Specl nested environments. +setdebug = require "std.debug"._setdebug + + -- A copy of base.lua:prototype, so that an unloadable base.lua doesn't -- prevent everything else from working. function prototype (o) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 20b3e2d..9a52260 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -65,8 +65,10 @@ specify std.string: - before: f = M.assert - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {"std.string"})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" - context when it does not trigger: @@ -557,8 +559,10 @@ specify std.string: - before: f = M.require_version - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {"std.string"})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" - it diagnoses non-existent module: @@ -730,8 +734,10 @@ specify std.string: - before: f = M.tostring - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {"std.string"})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" - it renders primitives exactly like system tostring: diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index f84dd27..2a73815 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -91,8 +91,10 @@ specify std.table: fname = "clone_rename" f = M[fname] - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {{}, subject})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" - it copies the subject: @@ -519,8 +521,10 @@ specify std.table: f = M.metamethod - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {{}, subject})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" - it returns nil for missing metamethods: @@ -699,8 +703,10 @@ specify std.table: - before: f = M.ripairs - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {{}, subject})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" - it returns a function, the table and a number: @@ -803,8 +809,10 @@ specify std.table: f, badarg = init (M, this_module, "totable") - - it writes a deprecation warning on first call: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } expect (capture (f, {{}})).to_contain_error "was deprecated" + setdebug { deprecate = false } expect (capture (f, {{}})).not_to_contain_error "was deprecated" - it calls object's __totable metamethod: From d478f431e0b13ef7c17c3230f7628b35413ccd63 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 1 Oct 2014 13:03:07 +0100 Subject: [PATCH 445/703] travis: use specl-git-1.rockspec from specl git master branch. * .travis.yml (script): Adjust specl-git-1.rockspec URL. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ea8d87..2772a4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ script: # Install extra rocks into $LUAROCKS_CONFIG rocks tree. - $LUAROCKS install lyaml; $LUAROCKS install ldoc # Temporarily install git specl for required unreleased fixes - - $LUAROCKS install https://raw.githubusercontent.com/gvvaughan/specl/release/specl-git-1.rockspec + - $LUAROCKS install https://raw.githubusercontent.com/gvvaughan/specl/master/specl-git-1.rockspec # Make git rockspec for this stdlib - make rockspecs LUAROCKS="$LUAROCKS" V=1 From 1b8305801956c577f01461d76d6fa108b6fdbf5a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 1 Oct 2014 13:51:23 +0100 Subject: [PATCH 446/703] travis: reformat .travis.yml. * .travis.yml: Reformat. Signed-off-by: Gary V. Vaughan --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2772a4e..bbee215 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,8 @@ script: # Install extra rocks into $LUAROCKS_CONFIG rocks tree. - $LUAROCKS install lyaml; $LUAROCKS install ldoc # Temporarily install git specl for required unreleased fixes - - $LUAROCKS install https://raw.githubusercontent.com/gvvaughan/specl/master/specl-git-1.rockspec + - $LUAROCKS install + https://raw.githubusercontent.com/gvvaughan/specl/master/specl-git-1.rockspec # Make git rockspec for this stdlib - make rockspecs LUAROCKS="$LUAROCKS" V=1 From 3567ed36f302b6b76ba9363595c4a1e2a76b1f94 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 29 Sep 2014 21:19:55 +0100 Subject: [PATCH 447/703] debug: improve argscheck parsing patterns. * lib/std/debug.lua (argscheck): Strip leading and trailing whitespace from argument type list. Allow commas without trailing whitespace. Allow periods in `fname` pattern. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 5f180a2..59c7717 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -423,13 +423,13 @@ if _DEBUG.argcheck then function argscheck (decl, inner) -- Parse "fname (argtype, argtype, argtype...)". - local fname, types = decl:match "([%w_][%.%d%w_]*)%s+%((.*)%)" + local fname, types = decl:match "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" if types == "" then types = {} elseif types then - types = split (types, ",%s+") + types = split (types, ",%s*") else - fname = decl:match "([%w_][%d%d%w_]*)" + fname = decl:match "([%w_][%.%d%w_]*)" end -- If the final element of types ends with "*", then set max to a From 16f60a8dc516b635d2f67dedf9e0d56edf91af1f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 1 Oct 2014 22:26:09 +0100 Subject: [PATCH 448/703] functional: fill unbound arguments in order with bind. * lib/std/functional (bind): When filling unbound arguments, be sure to traverse the remaining arguments *in* order! Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 7c6269f..ed3c690 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -32,7 +32,7 @@ local function bind (fn, ...) arg[i] = v end local i = 1 - for _, v in pairs {...} do + for _, v in ipairs {...} do while arg[i] ~= nil do i = i + 1 end arg[i] = v end From 92de15543181d6538a20e8bed4ef437700c0c139 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 26 Sep 2014 11:28:32 +0100 Subject: [PATCH 449/703] slingshot: sync with upstream for SPECL_ENV improvements. * slingshot: Sync with upstream. * specs/specs.mk: Simplify accordingly. * specs/spec_helper.lua (package.path): Set according to slingshot recommendations. * bootstrap.conf: Require latest specl, and tidy accordingly. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 6 +----- slingshot | 2 +- specs/spec_helper.lua | 12 ++++++++++-- specs/specs.mk | 10 ---------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/bootstrap.conf b/bootstrap.conf index deeb49d..7546cf3 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -30,15 +30,11 @@ # List of programs, minimum versions, and software urls required to # bootstrap, maintain and release GNU Zile. -## !!WARNING!! Tidy up specs/specs.mk as instructed when buildreq bumps -#@ specl requirement to 12 or higher. - -# Build prerequisites buildreq=' git - http://git-scm.com ldoc 1.4.0 http://luarocks.org/repositories/rocks/ldoc-1.4.2-1.rockspec + specl 13 http://luarocks.org/repositories/rocks/specl-13-1.rockspec ' -### specl 13 http://luarocks.org/repositories/rocks/specl-13-1.rockspec # List of slingshot files to link into stdlib tree before autotooling. slingshot_files=' diff --git a/slingshot b/slingshot index 8779cd8..97ab1b5 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 8779cd8abe3f49ec65e76590f3ac4e8c339a0f04 +Subproject commit 97ab1b5a6fcc53f0e6c5aac156645349fd759bb9 diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 5caa2b4..1bc7703 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -2,8 +2,16 @@ local hell = require "specl.shell" local inprocess = require "specl.inprocess" local std = require "specl.std" -package.path = std.package.normalize ("lib/?.lua", "lib/?/init.lua", package.path) - +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. diff --git a/specs/specs.mk b/specs/specs.mk index 70061a3..f4cfc5f 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -1,16 +1,6 @@ # Specl specs make rules. -## ------------ ## -## Environment. ## -## ------------ ## - -## !!WARNING!! When bootstrap.conf:buildreq specl setting requires specl -## 12 or higher, remove this entire Environment section! - -SPECL_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" LUA_INIT= LUA_INIT_5_2= - - ## ------ ## ## Specs. ## ## ------ ## From ba2692266e2e667b092d6f8dc3e9129e19133b4c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 5 Oct 2014 10:49:31 +0100 Subject: [PATCH 450/703] travis: remove temporary specl-git-1.rockspec dependency. * .travis.yml (script): Regenerate from travis.yml.n. Signed-off-by: Gary V. Vaughan --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index bbee215..f90af29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,10 +54,7 @@ script: - export PATH=`pwd`/luarocks/bin:$PATH # Install extra rocks into $LUAROCKS_CONFIG rocks tree. - - $LUAROCKS install lyaml; $LUAROCKS install ldoc - # Temporarily install git specl for required unreleased fixes - - $LUAROCKS install - https://raw.githubusercontent.com/gvvaughan/specl/master/specl-git-1.rockspec + - $LUAROCKS install lyaml; $LUAROCKS install ldoc; $LUAROCKS install specl; # Make git rockspec for this stdlib - make rockspecs LUAROCKS="$LUAROCKS" V=1 From 9d9386a8f44ed7d3659afc101412f867321a29e6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 1 Oct 2014 21:45:54 +0100 Subject: [PATCH 451/703] specs: simplify bad argument checking with specl.badargs. * specs/spec_helper.lua (badargs): Expose specl.badargs to the example execution environment. (badarg, init): Remove. Superseded by badargs.init. * specs/debug_spec.yaml, specs/string_spec.yaml: Use badargs.init instead of init. * specs/functional_spec.yaml, specs/io_spec.yaml, specs/list_spec.yaml, specs/math_spec.yaml, specs/package_spec.yaml, specs/std_spec.yaml, specs/table_spec.yaml: Use badargs.diagnose to write bad argument diagnostics examples automatically. * bootstrap.conf (buildreq): Use moonscript rocks server URLs. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 14 +- lib/std/debug.lua | 6 +- specs/container_spec.yaml | 4 +- specs/debug_spec.yaml | 4 +- specs/functional_spec.yaml | 200 +++++++++--------------- specs/io_spec.yaml | 148 +++++++++--------- specs/list_spec.yaml | 81 +++------- specs/math_spec.yaml | 37 ++--- specs/package_spec.yaml | 64 +++----- specs/spec_helper.lua | 46 ++---- specs/std_spec.yaml | 129 +++++----------- specs/string_spec.yaml | 225 +++++++-------------------- specs/table_spec.yaml | 304 +++++++++++++------------------------ 13 files changed, 417 insertions(+), 845 deletions(-) diff --git a/bootstrap.conf b/bootstrap.conf index 7546cf3..0a3c8e9 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -1,4 +1,4 @@ -# bootstrap.conf (Stdlib) version 2014-08-18 +# bootstrap.conf (Stdlib) version 2014-10-04 # # Copyright (C) 2013-2014 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 @@ -17,10 +17,8 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with GNU Libtool; see the file COPYING. If not, a copy -# can be downloaded from http://www.gnu.org/licenses/gpl.html, -# or obtained by writing to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# along with this program; see the file COPYING. If not, a copy +# can be downloaded from http://www.gnu.org/licenses/gpl.html. ## -------------- ## @@ -28,12 +26,12 @@ ## -------------- ## # List of programs, minimum versions, and software urls required to -# bootstrap, maintain and release GNU Zile. +# bootstrap, maintain and release this project. buildreq=' git - http://git-scm.com - ldoc 1.4.0 http://luarocks.org/repositories/rocks/ldoc-1.4.2-1.rockspec - specl 13 http://luarocks.org/repositories/rocks/specl-13-1.rockspec + ldoc 1.4.2 http://rocks.moonscript.org/manifests/steved/ldoc-1.4.2-1.rockspec + specl 13 http://rocks.moonscript.org/manifests/gvvaughan/specl-13-1.rockspec ' # List of slingshot files to link into stdlib tree before autotooling. diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 59c7717..9a8f2af 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -126,8 +126,8 @@ end local function toomanyargmsg (name, expect, actual) - local fmt = "too many arguments to '%s' (no more than %d expected, got %d)" - return string.format (fmt, name, expect, actual) + local fmt = "bad argument #%d to '%s' (no more than %d argument%s expected, got %d)" + return string.format (fmt, expect + 1, name, expect, expect > 1 and "s" or "", actual) end @@ -314,6 +314,8 @@ if _DEBUG.argcheck then t[i] = "function" elseif v == "any" then t[i] = "any value" + elseif v == "file" then + t[i] = "FILE*" elseif not index then t[i] = v:match "(%S+) of %S+" or v else diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 7d57fe1..cb6cdf2 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -14,9 +14,9 @@ specify std.container: - it diagnoses missing arguments: | expect (Container ()). to_raise "bad argument #1 to 'Container' (table expected, got no value)" - - it diagnoses too many arguments: + - it diagnoses too many arguments: | expect (Container ({}, false)). - to_raise "too many arguments to 'Container' (no more than 1 expected, got 2)" + to_raise "bad argument #2 to 'Container' (no more than 1 argument expected, got 2)" - context with function _init: - before: Thing = Container { _type = "Thing", _init = function (obj) return obj end } diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 427f839..f0240af 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -236,13 +236,13 @@ specify std.debug: - context with primitives: - it diagnoses missing types: expect (fn ("boolean", nil)).to_raise "boolean expected, got no value" - expect (fn ("file", nil)).to_raise "file expected, got no value" + expect (fn ("file", nil)).to_raise "FILE* expected, got no value" expect (fn ("number", nil)).to_raise "number expected, got no value" expect (fn ("string", nil)).to_raise "string expected, got no value" expect (fn ("table", nil)).to_raise "table expected, got no value" - it diagnoses mismatched types: expect (fn ("boolean", {0})).to_raise "boolean expected, got table" - expect (fn ("file", {0})).to_raise "file expected, got table" + expect (fn ("file", {0})).to_raise "FILE* expected, got table" expect (fn ("number", {0})).to_raise "number expected, got table" expect (fn ("string", {0})).to_raise "string expected, got table" expect (fn ("table", false)).to_raise "table expected, got boolean" diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 59834ad..9573888 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -11,6 +11,7 @@ before: M = require (this_module) + specify std.functional: - context when required: - context by name: @@ -30,22 +31,19 @@ specify std.functional: - describe bind: - before: - fname = "bind" - f, badarg = init (M, this_module, fname) - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) + f = M.bind - it writes an argument passing deprecation warning: setdebug { deprecate = "nil" } - expect (capture (f, {init, M, fname})). + expect (capture (f, {nop, M, "bind"})). to_contain_error "was deprecated" setdebug { deprecate = false } - expect (capture (f, {init, M, fname})). + expect (capture (f, {nop, M, "bind"})). not_to_contain_error "was deprecated" + - context with bad arguments: + badargs.diagnose (f, "std.functional.bind (function, ?any*)") + - it does not affect normal operation if no arguments are bound: expect (f (math.min, {}) (2, 3, 4)).to_be (2) - it takes the extra arguments into account: @@ -66,6 +64,10 @@ specify std.functional: - describe callable: - before: f = M.callable + + - context with bad arguments: + badargs.diagnose (f, "std.functional.callable (?any)") + - it returns the function associated with a callable: Container = require "std.container" { __call = M.nop } for _, v in ipairs { @@ -88,15 +90,10 @@ specify std.functional: default = function (s) return s end branches = { yes = yes, no = no, default } - f, badarg = init (M, this_module, "case") + f = M.case - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (2, "non-empty table")) - - it diagnoses wrong argument types: - expect (f ("no", false)). - to_raise (badarg (2, "non-empty table", "boolean")) - - it diagnoses too many arguments: - expect (f (1, {2}, false)).to_raise (badarg (3)) + - context with bad arguments: | + badargs.diagnose (f, "std.functional.case (?any, #table)") - it matches against branch keys: expect (f ("yes", branches)).to_be (true) @@ -115,7 +112,7 @@ specify std.functional: - it evaluates returned functables: functable = setmetatable ({}, {__call = function (t, with) return with end}) expect (f ("functable", {functable})).to_be "functable" - - it evaluates 'with` exactly once: + - it evaluates 'with' exactly once: s = "prince" function acc () s = s .. "s"; return s end expect (f (acc (), { @@ -128,10 +125,10 @@ specify std.functional: - describe collect: - before: - f, badarg = init (M, this_module, "collect") + f = M.collect - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function or any value")) + - context with bad arguments: + badargs.diagnose (f, "std.functional.collect ([func], any*)") - it collects a list of single return value iterator results: expect (f (base.ielems, {"a", "b", "c"})).to_equal {"a", "b", "c"} @@ -144,13 +141,10 @@ specify std.functional: - describe compose: - before: - f, badarg = init (M, this_module, "compose") + f = M.compose - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (f, false)).to_raise (badarg (2, "function", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.functional.compose (func*)") - it composes a single function correctly: expect (f (M.id) (1)).to_be (1) @@ -189,16 +183,10 @@ specify std.functional: - describe curry: - before: - f, badarg = init (M, this_module, "curry") + f = M.curry - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (f)).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (f, 1.234)).to_raise (badarg (2, "int", "number")) - - it diagnoses too many arguments: - expect (f (f, 2, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.functional.curry (func, int)") - it returns a zero argument function uncurried: expect (f (f, 0)).to_be (f) @@ -236,13 +224,10 @@ specify std.functional: elements = {"a", "b", "c", "d", "e"} inverse = require "std.table".invert (elements) - f, badarg = init (M, this_module, "filter") + f = M.filter - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (f)).to_raise (badarg (2, "function or any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.functional.filter (func, [func], any*)") - it works with an empty table: expect (f (M.id, pairs, {})).to_equal {} @@ -298,17 +283,10 @@ specify std.functional: - describe foldl: - before: op = require "std.operator" - f, badarg = init (M, this_module, "foldl") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (f, nil)).to_raise (badarg (2, "any value or table")) - expect (f (f, 42)).to_raise (badarg (3, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (f, 42, false)).to_raise (badarg (3, "table", "boolean")) - - it diagnoses too many arguments: - expect (f (f, 42, {}, false)).to_raise (badarg (4)) + f = M.foldl + + - context with bad arguments: + badargs.diagnose (f, "std.functional.foldl (func, [any], table)") - it works with an empty table: expect (f (op.sum, 10000, {})).to_be (10000) @@ -323,17 +301,10 @@ specify std.functional: - describe foldr: - before: op = require "std.operator" - f, badarg = init (M, this_module, "foldr") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (f, nil)).to_raise (badarg (2, "any value or table")) - expect (f (f, 42)).to_raise (badarg (3, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (f, 42, false)).to_raise (badarg (3, "table", "boolean")) - - it diagnoses too many arguments: - expect (f (f, 42, {}, false)).to_raise (badarg (4)) + f = M.foldr + + - context with bad arguments: + badargs.diagnose (f, "std.functional.foldr (func, [any], table)") - it works with an empty table: expect (f (op.sum, 1, {})).to_be (1) @@ -357,19 +328,18 @@ specify std.functional: - describe lambda: - before: - f, badarg = init (M, this_module, "lambda") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong arguments types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("foo", false)).to_raise (badarg (2)) - - it diagnoses bad lambda string: - expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" - - it diagnoses an uncompilable expression: - expect (select (2, f "||+")).to_be "invalid lambda string '||+'" - expect (select (2, f "=")).to_be "invalid lambda string '='" + f = M.lambda + + - context with bad arguments: + badargs.diagnose (f, "std.functional.lambda (string)") + + examples {["it diagnoses bad lambda string"] = function () + expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" + end} + examples {["it diagnoses an uncompilable expression"] = function () + expect (select (2, f "||+")).to_be "invalid lambda string '||+'" + expect (select (2, f "=")).to_be "invalid lambda string '='" + end} - context with argument format: - it returns a function: @@ -393,16 +363,13 @@ specify std.functional: - describe map: - before: - elements = {"a", "b", "c", "d", "e"} - inverse = require "std.table".invert (elements) + elements = {"a", "b", "c", "d", "e"} + inverse = require "std.table".invert (elements) - f, badarg = init (M, this_module, "map") + f = M.map - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (f)).to_raise (badarg (2, "function or any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.functional.map (func, [func], any*)") - it works with an empty table: expect (f (M.id, ipairs, {})).to_equal {} @@ -438,18 +405,10 @@ specify std.functional: t = {{1, 2, 3}, {4, 5}} fn = function (...) return select ("#", ...) end - f, badarg = init (M, this_module, "map_with") + f = M.map_with - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (fn)).to_raise (badarg (2, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (fn, false)).to_raise (badarg (2, "table", "boolean")) - expect (f (fn, {{}, false})). - to_raise (badarg (2, "table of tables", "boolean at index 2")) - - it diagnoses too many arguments: - expect (f (fn, t, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.functional.map_with (func, table of tables)") - it works for an empty table: expect (f (fn, {})).to_equal ({}) @@ -472,19 +431,14 @@ specify std.functional: - describe memoize: - before: - f, badarg = init (M, this_module, "memoize") + f = M.memoize memfn = f (function (x) if x then return {x} else return nil, "bzzt" end end) - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (f, false)).to_raise (badarg (2, "function or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (f, f, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.functional.memoize (func, ?func)") - it propagates multiple return values: expect (select (2, memfn (false))).to_be "bzzt" @@ -708,15 +662,11 @@ specify std.functional: - describe reduce: - before: op = require "std.operator" - f, badarg = init (M, this_module, "reduce") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (f)).to_raise (badarg (2, "any value")) - expect (f (f, 1)).to_raise (badarg (3, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (f, 1, false)).to_raise (badarg (3, "function", "boolean")) + f = M.reduce + + - context with bad arguments: + badargs.diagnose (f, "std.functional.reduce (func, any, func, any*)") - it works with an empty table: expect (f (op.sum, 2, ipairs, {})).to_be (2) @@ -736,14 +686,10 @@ specify std.functional: - before: tt = {{1, 2}, {3, 4}, {5, 6}} - f, badarg = init (M, this_module, "zip") + f = M.zip - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f (tt, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.functional.zip (table)") - it works for an empty table: expect (f {}).to_equal {} @@ -760,18 +706,10 @@ specify std.functional: tt = {{1, 2}, {3, 4}, {5}} fn = function (...) return tonumber (table.concat {...}) end - f, badarg = init (M, this_module, "zip_with") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - expect (f (fn)).to_raise (badarg (2, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - expect (f (fn, false)).to_raise (badarg (2, "table", "boolean")) - expect (f (fn, {{}, false})). - to_raise (badarg (2, "table of tables", "boolean at index 2")) - - it diagnoses too many arguments: - expect (f (fn, tt, false)).to_raise (badarg (3)) + f = M.zip_with + + - context with bad arguments: + badargs.diagnose (f, "std.functional.zip_with (function, table of tables)") - it works for an empty table: expect (f (fn, {})).to_equal {} diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 228a025..7d0439c 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -11,6 +11,7 @@ before: | M = require (this_module) + specify std.io: - context when required: - context by name: @@ -35,14 +36,10 @@ specify std.io: - describe catdir: - before: | - f, badarg = init (M, this_module, "catdir") + f = M.catdir - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("", false)).to_raise (badarg (2, "string", "boolean")) - expect (f ("", "false", false)).to_raise (badarg (3, "string", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.io.catdir (string*)") - it treats initial empty string as root directory: expect (f ("")).to_be (dirsep) @@ -58,14 +55,10 @@ specify std.io: - describe catfile: - before: - f, badarg = init (M, this_module, "catfile") + f = M.catfile - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("", false)).to_raise (badarg (2, "string", "boolean")) - expect (f ("", "false", false)).to_raise (badarg (3, "string", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.io.catfile (string*)") - it treats initial empty string as root directory: expect (f ("", "")).to_be (dirsep) @@ -83,12 +76,10 @@ specify std.io: - before: | script = [[require "std.io".die "By 'eck!"]] - f, badarg = init (M, this_module, "die") + f = M.die - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.io.die (string, ?any*)") - it outputs a message to stderr: expect (luaproc (script)).to_fail_with "By 'eck!\n" @@ -145,12 +136,10 @@ specify std.io: - before: namespace = {} - f, badarg = init (M, this_module, "monkey_patch") + f = M.monkey_patch - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (t, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.io.monkey_patch (?table)") - it returns the monkey_patched io entry from namespace: namespace = {} @@ -191,18 +180,19 @@ specify std.io: require "std.io".process_files (function () io.write (io.input ():read "*a") end) ]] - f, badarg = init (M, this_module, "process_files") + f = M.process_files + + - context with bad arguments: | + badargs.diagnose (f, "std.io.process_files (func)") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "function")) - - it diagnoses wrong argument types: | - expect (f (false)).to_raise (badarg (1, "function", "boolean")) - 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 + 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 diagnoses too many arguments: - expect (f (f, false)).to_raise (badarg (2)) - it defaults to `-` if no arguments were passed: expect (luaproc (ascript)).to_output "-\n" @@ -237,16 +227,21 @@ specify std.io: - after: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end - - it diagnoses wrong argument types: | - expect (f (false)).to_raise (badarg (1, "file, string or nil", "boolean")) - expect (f "not-an-existing-file"). - to_raise "bad argument #1 to 'std.io.readlines' (" -- system dependent error message - - it diagnoses closed file argument: + - context with bad arguments: | + badargs.diagnose (f, "std.io.readlines (?file|string)") + + 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 () - expect (f (closed)). - to_raise (badarg (1, "file, string or nil", "closed file")) - - it diagnoses too many arguments: - expect (f ("string", false)).to_raise (badarg (2)) + examples { + ["it diagnoses closed file argument"] = function () + expect (f (closed)).to_raise (badarg (1, "?file|string", "closed file")) + end + } - it closes file handle upon completion: h = io.open (name) @@ -264,14 +259,10 @@ specify std.io: - describe shell: - before: - f, badarg = init (M, this_module, "shell") + f = M.shell - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", false)).to_raise (badarg (2)) + - 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" @@ -289,16 +280,21 @@ specify std.io: - after: if io.type (defaultin) ~= "closed file" then io.input (defaultin) end - - it diagnoses wrong argument types: | - expect (f (false)).to_raise (badarg (1, "file, string or nil", "boolean")) - expect (f "not-an-existing-file"). - to_raise "bad argument #1 to 'std.io.slurp' (" -- system dependent error message - - it diagnoses closed file argument: + - context with bad arguments: | + badargs.diagnose (f, "std.io.slurp (?file|string)") + + 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 () - expect (f (closed)). - to_raise (badarg (1, "file, string or nil", "closed file")) - - it diagnoses too many arguments: - expect (f ("string", false)).to_raise (badarg (2)) + examples { + ["it diagnoses closed file argument"] = function () + expect (f (closed)).to_raise (badarg (1, "?file|string", "closed file")) + end + } - it reads content from an existing named file: expect (f (name)).to_be (content) @@ -316,14 +312,10 @@ specify std.io: - describe splitdir: - before: - f, badarg = init (M, this_module, "splitdir") + f = M.splitdir - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", false)).to_raise (badarg (2)) + - 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"} @@ -340,12 +332,10 @@ specify std.io: - describe warn: - before: script = [[require "std.io".warn "Ayup!"]] - f, badarg = init (M, this_module, "warn") + f = M.warn - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) + - 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" @@ -408,13 +398,15 @@ specify std.io: h:close () os.remove (name) - - it diagnoses wrong argument types: | - expect (f (false)). - to_raise (badarg (1, "file, string, number or nil", "boolean")) - - it diagnoses closed file argument: | - closed = io.open (name) closed:close () - expect (f (closed)). - to_raise (badarg (1, "file, string, number or nil", "closed file")) + - context with bad arguments: | + badargs.diagnose (f, "std.io.writelines (?file|string|number, ?string|number*)") + + closed = io.open (name, "r") closed:close () + examples { + ["it diagnoses closed file argument"] = function () + 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" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index a58ca49..7a90aa0 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -14,6 +14,7 @@ before: l = List {"foo", "bar", "baz"} + specify std.list: - context when required: - context by name: @@ -75,15 +76,10 @@ specify std.list: - describe append: - before: - f, badarg = init (M, this_module, "append") + f = M.append - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 42, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.list.append (List, any)") - context as a module function: - it returns a List object: @@ -120,16 +116,10 @@ specify std.list: - before: a, b = List {"foo", "bar"}, List {"foo", "baz"} - f, badarg = init (M, this_module, "compare") + f = M.compare - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (a, false)).to_raise (badarg (2, "List or table", "boolean")) - - it diagnoses too many arguments: - expect (f (a, b, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.list.compare (List, List|table)") - context as a module function: - it returns -1 when the first list is less than the second: @@ -220,16 +210,10 @@ specify std.list: - before: l = List {"foo", "bar"} - f, badarg = init (M, this_module, "concat") + f = M.concat - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "List or table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "List or table", "boolean")) - expect (f (l, l, false)). - to_raise (badarg (3, "List or table", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.list.concat (List, List|table*)") - context as a module function: - it returns a List object: @@ -286,15 +270,10 @@ specify std.list: - describe cons: - before: - f, badarg = init (M, this_module, "cons") + f = M.cons - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (List {})).to_raise (badarg (2, "any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (List {}, "x", false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.list.cons (List, any)") - context as a module function: - it returns a List object: @@ -938,16 +917,10 @@ specify std.list: - before: l = List {"foo", "bar"} - f, badarg = init (M, this_module, "rep") + f = M.rep - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - expect (f (l)).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "int", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 2, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.list.rep (List, int)") - context as a module function: - it returns a List object: @@ -1088,16 +1061,10 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6, 7} - f, badarg = init (M, this_module, "sub") + f = M.sub - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - expect (f (l, false)).to_raise (badarg (2, "int or nil", "boolean")) - expect (f (l, 1, false)).to_raise (badarg (3, "int or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (l, 1, 2, false)).to_raise (badarg (4)) + - context with bad arguments: + badargs.diagnose (f, "std.list.sub (List, ?int, ?int)") - context as a module function: - it returns a List object: @@ -1143,14 +1110,10 @@ specify std.list: - before: l = List {1, 2, 3, 4, 5, 6, 7} - f, badarg = init (M, this_module, "tail") + f = M.tail - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "List")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "List", "boolean")) - - it diagnoses too many arguments: - expect (f (l, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.list.tail (List)") - context as a module function: - it returns a List object: diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 441d392..86e58e5 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -1,4 +1,4 @@ -before: | +before: base_module = "math" this_module = "std.math" global_table = "_G" @@ -7,6 +7,7 @@ before: | M = require (this_module) + specify std.math: - context when required: - context by name: @@ -31,15 +32,10 @@ specify std.math: - describe floor: - before: - f, badarg = init (M, this_module, "floor") + f = M.floor - - it diagnoses missing arguments: | - expect (f ()).to_raise (badarg (1, "number")) - - it diagnoses wrong argument types: | - expect (f (1.2, false)).to_raise (badarg (2, "int or nil", "boolean")) - expect (f (1.2, 3.4)).to_raise (badarg (2, "int or nil", "number")) - - it diagnoses too many arguments: - expect (f (1, 2, 3)).to_raise (badarg (3)) + - 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) @@ -58,12 +54,10 @@ specify std.math: - describe monkey_patch: - before: - f, badarg = init (M, this_module, "monkey_patch") + f = M.monkey_patch - - it diagnoses wrong argument types: | - expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (t, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.math.monkey_patch (?table)") - it returns the monkey_patched math entry namespace: namespace = {} @@ -78,17 +72,10 @@ specify std.math: - describe round: - before: - f, badarg = init (M, this_module, "round") - - - it diagnoses missing arguments: | - expect (f ()).to_raise (badarg (1, "number")) - - it diagnoses wrong argument types: | - expect (f (1.2, false)). - to_raise (badarg (2, "int or nil", "boolean")) - expect (f (1.2, 3.4)). - to_raise (badarg (2, "int or nil", "number")) - - it diagnoses too many arguments: - expect (f (1, 2, 3)).to_raise (badarg (3)) + 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) diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index ad537d3..2a4832e 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -41,19 +41,10 @@ specify std.package: - before: | path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) - f, badarg = init (M, this_module, "find") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - expect (f (path)).to_raise (badarg (2, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f (path, false)).to_raise (badarg (2, "string", "boolean")) - expect (f (path, "foo", false)).to_raise (badarg (3, "int or nil", "boolean")) - expect (f (path, "foo", 1, 2)). - to_raise (badarg (4, "boolean, :plain or nil", "number")) - - it diagnoses too many arguments: - expect (f (path, "foo", 1, ":plain", false)).to_raise (badarg (5)) + 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) @@ -73,17 +64,10 @@ specify std.package: - describe insert: - before: - f, badarg = init (M, this_module, "insert") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - expect (f (path)).to_raise (badarg (2, "int or string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f (path, false)).to_raise (badarg (2, "int or string", "boolean")) - expect (f (path, 1, false)).to_raise (badarg (3, "string", "boolean")) - - it diagnoses too many arguments: - expect (f (path, 1, "string", false)).to_raise (badarg (4)) + f = M.insert + + - context with bad arguments: + badargs.diagnose (f, "std.package.insert (string, [int], string)") - it appends by default: expect (f (path, "new")). @@ -108,14 +92,10 @@ specify std.package: - before: | expected = require "std.string".split (path, M.pathsep) - f, badarg = init (M, this_module, "mappath") + f = M.mappath - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - expect (f ("")).to_raise (badarg (2, "function")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("", false)).to_raise (badarg (2, "function", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.package.mappath (string, function, ?any*)") - it calls a function with each path element: t = {} @@ -133,13 +113,10 @@ specify std.package: - describe normalize: - before: - f, badarg = init (M, this_module, "normalize") + f = M.normalize - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("", false)).to_raise (badarg (2, "string", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.package.normalize (string*)") - context with a single element: - it strips redundant . directories: @@ -185,15 +162,10 @@ specify std.package: - describe remove: - before: - f, badarg = init (M, this_module, "remove") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("", false)).to_raise (badarg (2, "int or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", 2, false)).to_raise (badarg (3)) + 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")) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 1bc7703..e33ce38 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -1,7 +1,9 @@ -local hell = require "specl.shell" 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 "." @@ -22,6 +24,13 @@ local LUA = os.getenv "LUA" or "lua" setdebug = require "std.debug"._setdebug +-- 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 +end + + -- A copy of base.lua:prototype, so that an unloadable base.lua doesn't -- prevent everything else from working. function prototype (o) @@ -106,40 +115,6 @@ local function tabulate_output (code) end ---- Return a formatted bad argument string. --- @tparam table M module table --- @string fname base-name of the erroring function --- @int i argument number --- @string want expected argument type --- @string[opt="no value"] got actual argument type --- @usage --- expect (f ()).to_error (badarg (fname, mname, 1, "function")) -local function badarg (mname, fname, i, want, got) - if want == nil then i, want = i - 1, i end - - local fqfname = (mname .. "." .. fname):gsub ("^%.", "") - - if got == nil and type (want) == "number" then - local s = "too many arguments to '%s' (no more than %d expected, got %d)" - return string.format (s, fqfname, i, want) - end - return string.format ("bad argument #%d to '%s' (%s expected, got %s)", - i, fqfname, want, got or "no value") -end - - ---- Initialise custom function and argument error handlers. --- @tparam table M module table --- @string fname function name to bind --- @treturn string *fname* --- @treturn function `M[fname]` if any, otherwise `nil` --- @treturn function badarg with *M* and *fname* prebound --- @treturn function toomanyarg with *M* and *fname* prebound -function init (M, mname, fname) - return M[fname], bind (badarg, {mname, fname}) -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"`: @@ -230,7 +205,6 @@ capture = inprocess.capture or function (f, arg) return nil, nil, f (unpack (arg or {})) end - do -- Custom matcher for set size and set membership. diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index aedb5f5..4c7916d 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -27,6 +27,7 @@ before: | M = require (this_module) + specify std: - context when required: - it does not touch the global table: @@ -51,10 +52,10 @@ specify std: - describe assert: - before: - f, badarg = init (M, this_module, "assert") + f = M.assert - - it diagnoses wrong argument types: - expect (f (false, false)).to_raise (badarg (2, "string or nil", "boolean")) + - context with bad arguments: + badargs.diagnose (f, "std.assert (?any, ?string, ?any*)") - context when it does not trigger: - it has a truthy initial argument: @@ -90,14 +91,12 @@ specify std: }, } - f, badarg = init (M, this_module, "barrel") + f = M.barrel f (namespace) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.barrel (?table)") - it installs std monkey patches: for _, api in ipairs (exported_apis) do @@ -171,14 +170,10 @@ specify std: - describe elems: - before: - f, badarg = init (M, this_module, "elems") + f = M.elems - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.elems (table)") - it is an iterator over table values: t = {} @@ -199,14 +194,10 @@ specify std: - describe eval: - before: - f, badarg = init (M, this_module, "eval") + f = M.eval - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("1", false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.eval (string)") - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable "=" code. @@ -217,16 +208,10 @@ specify std: - describe getmetamethod: - before: - f, badarg = init (M, this_module, "getmetamethod") + f = M.getmetamethod - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "object or table")) - expect (f ({})).to_raise (badarg (2, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "object or table", "boolean")) - expect (f ({}, false)).to_raise (badarg (2, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, "foo", false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.getmetamethod (object|table, string)") - context with a table: - before: @@ -257,14 +242,10 @@ specify std: - describe ielems: - before: - f, badarg = init (M, this_module, "ielems") + f = M.ielems - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.ielems (table)") - it is an iterator over integer-keyed table values: t = {} @@ -290,14 +271,10 @@ specify std: - describe ipairs: - before: - f, badarg = init (M, this_module, "ipairs") + f = M.ipairs - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.ipairs (table)") - it is an iterator over integer-keyed table values: t = {} @@ -323,14 +300,10 @@ specify std: - describe ireverse: - before: - f, badarg = init (M, this_module, "ireverse") + f = M.ireverse - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.ireverse (table)") - it returns a new list: t = {1, 2, 5} @@ -358,14 +331,12 @@ specify std: table = {}, } - f, badarg = init (M, this_module, "monkey_patch") + f = M.monkey_patch f (t) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.monkey_patch (?table)") - it installs std module functions: for _, v in ipairs (exported_apis) do @@ -377,14 +348,10 @@ specify std: - describe pairs: - before: - f, badarg = init (M, this_module, "pairs") + f = M.pairs - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.pairs (table)") - it is an iterator over all table values: t = {} @@ -405,20 +372,10 @@ specify std: - describe require: - before: - f, badarg = init (M, this_module, "require") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("module", false)).to_raise (badarg (2, "string or nil", "boolean")) - expect (f ("module", "min", false)). - to_raise (badarg (3, "string or nil", "boolean")) - expect (f ("module", "min", "too_big", false)). - to_raise (badarg (4, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("module", "min", "too_big", "pattern", false)). - to_raise (badarg (5)) + 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" @@ -496,14 +453,10 @@ specify std: - describe ripairs: - before: - f, badarg = init (M, this_module, "ripairs") + f = M.ripairs - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - 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} @@ -531,10 +484,10 @@ specify std: - describe tostring: - before: - f, badarg = init (M, this_module, "tostring") + f = M.tostring - - it diagnoses too many arguments: - expect (f (true, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.tostring (?any)") - it renders primitives exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 9a52260..2c4825a 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -96,14 +96,10 @@ specify std.string: - describe caps: - before: - f, badarg = init (M, this_module, "caps") + f = M.caps - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.string.caps (string)") - it capitalises words of a string: target = "A String \n\n" @@ -121,15 +117,10 @@ specify std.string: - describe chomp: - before: target = "a string \n" + f = M.chomp - f, badarg = init (M, this_module, "chomp") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", false)).to_raise (badarg (2)) + - 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) @@ -151,15 +142,10 @@ specify std.string: for i = 1, string.len (meta) do magic[meta:sub (i, i)] = true end + f = M.escape_pattern - f, badarg = init (M, this_module, "escape_pattern") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.string.escape_pattern (string)") - context with each printable ASCII char: - before: @@ -182,14 +168,10 @@ specify std.string: - describe escape_shell: - before: - f, badarg = init (M, this_module, "escape_shell") + f = M.escape_shell - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.string.escape_shell (string)") - context with each printable ASCII char: - before: @@ -216,22 +198,10 @@ specify std.string: - describe finds: - before: subject = "abcd" + f = M.finds - f, badarg = init (M, this_module, "finds") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - expect (f ("string")).to_raise (badarg (2, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_raise (badarg (2, "string", "boolean")) - expect (f ("string", "pattern", false)). - to_raise (badarg (3, "int or nil", "boolean")) - expect (f ("string", "pattern", nil, "plain")). - to_raise (badarg (4, "boolean, :plain or nil", "string")) - - it diagnoses too many arguments: - expect (f ("string", "pattern", nil, false, function () end)). - to_raise (badarg (5)) + - context with bad arguments: + badargs.diagnose (f, "std.string.finds (string, string, ?int, ?boolean|:plain)") - context given a complex nested list: - before: @@ -259,12 +229,10 @@ specify std.string: - before: subject = "string=%s, number=%d" - f, badarg = init (M, this_module, "format") + f = M.format - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) + - 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) @@ -280,16 +248,10 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - f, badarg = init (M, this_module, "ltrim") + f = M.ltrim - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("string", false)). - to_raise (badarg (2, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", "pattern", false)).to_raise (badarg (3)) + - 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 " @@ -308,12 +270,10 @@ specify std.string: - describe monkey_patch: - before: - f, badarg = init (M, this_module, "monkey_patch") + f = M.monkey_patch - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (t, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.string.monkey_patch (?table)") - it returns the monkey_patched namespace: namespace = {} @@ -333,14 +293,10 @@ specify std.string: - describe numbertosi: - before: - f, badarg = init (M, this_module, "numbertosi") + f = M.numbertosi - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "number or string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "number or string", "boolean")) - - it diagnoses too many arguments: - expect (f (1, false)).to_raise (badarg (2)) + - 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", @@ -357,14 +313,10 @@ specify std.string: - describe ordinal_suffix: - before: - f, badarg = init (M, this_module, "ordinal_suffix") + f = M.ordinal_suffix - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "int or string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "int or string", "boolean")) - - it diagnoses too many arguments: - expect (f (1, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.string.ordinal_suffix (int|string)") - it returns the English suffix for a number: subject, target = {}, {} @@ -387,18 +339,10 @@ specify std.string: - before: width = 20 - f, badarg = init (M, this_module, "pad") + f = M.pad - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - expect (f ("string")).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_raise (badarg (2, "int", "boolean")) - expect (f ("string", 42, false)). - to_raise (badarg (3, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", 42, "\t", false)).to_raise (badarg (4)) + - context with bad arguments: + badargs.diagnose (f, "std.string.pad (string, int, ?string)") - context when string is shorter than given width: - before: @@ -463,14 +407,10 @@ specify std.string: - describe prettytostring: - before: - f, badarg = init (M, this_module, "prettytostring") + f = M.prettytostring - - it diagnoses wrong argument types: - expect (f (true, false)).to_raise (badarg (2, "string or nil", "boolean")) - expect (f (true, "indent", false)). - to_raise (badarg (3, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (true, "indent", "spacing", false)).to_raise (badarg (4)) + - 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)) @@ -516,25 +456,10 @@ specify std.string: t = {1, {{2, 3}, 4, {5}}} a = require "std.vector" {"a", "b", {{"c"},"d"},"e"} - f, badarg = init (M, this_module, "render") - - - it diagnoses missing arguments: - expect (f (true)).to_raise (badarg (2, "function")) - expect (f (true, f)).to_raise (badarg (3, "function")) - expect (f (true, f, f)).to_raise (badarg (4, "function")) - expect (f (true, f, f, f)).to_raise (badarg (5, "function")) - expect (f (true, f, f, f, f)).to_raise (badarg (6, "function")) - - it diagnoses wrong argument types: - expect (f (true, false)).to_raise (badarg (2, "function", "boolean")) - expect (f (true, f, false)).to_raise (badarg (3, "function", "boolean")) - expect (f (true, f, f, false)).to_raise (badarg (4, "function", "boolean")) - expect (f (true, f, f, f, false)).to_raise (badarg (5, "function", "boolean")) - expect (f (true, f, f, f, f, false)). - to_raise (badarg (6, "function", "boolean")) - expect (f (true, f, f, f, f, f, false)). - to_raise (badarg (7, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f (true, f, f, f, f, f, {}, false)).to_raise (badarg (8)) + f = M.render + + - context with bad arguments: + badargs.diagnose (f, "std.string.render (?any, func, func, func, func, func, ?table)") - it converts a primitive to a representative string: expect (r (nil)).to_be "nil" @@ -615,15 +540,10 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - f, badarg = init (M, this_module, "rtrim") + f = M.rtrim - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_raise (badarg (2, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", "pattern", false)).to_raise (badarg (3)) + - 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" @@ -645,16 +565,10 @@ specify std.string: target = { "first", "the second one", "final entry" } subject = table.concat (target, ", ") - f, badarg = init (M, this_module, "split") + f = M.split - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("string", false)). - to_raise (badarg (2, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", "pattern", false)).to_raise (badarg (3)) + - 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)). @@ -694,20 +608,10 @@ specify std.string: - before: subject = "abc" - f, badarg = init (M, this_module, "tfind") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - expect (f ("string")).to_raise (badarg (2, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_raise (badarg (2, "string", "boolean")) - expect (f ("string", "pattern", false)). - to_raise (badarg (3, "int or nil", "boolean")) - expect (f ("string", "pattern", 1, "plain")). - to_raise (badarg (4, "boolean, :plain or nil", "string")) - - it diagnoses too many arguments: - expect (f ("string", "pattern", 1, true, false)).to_raise (badarg (5)) + 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" } } @@ -766,16 +670,10 @@ specify std.string: - before: subject = " \t\r\n a short string \t\r\n " - f, badarg = init (M, this_module, "trim") + f = M.trim - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("string", false)). - to_raise (badarg (2, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", "pattern", false)).to_raise (badarg (3)) + - 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" @@ -800,19 +698,10 @@ specify std.string: "er the MIT license (the same license as Lua itself). There" .. " is no warranty." - f, badarg = init (M, this_module, "wrap") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("string", false)).to_raise (badarg (2, "int or nil", "boolean")) - expect (f ("string", 72, false)). - to_raise (badarg (3, "int or nil", "boolean")) - expect (f ("string", 72, 4, false)). - to_raise (badarg (4, "int or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ("string", 72, 4, 8, false)).to_raise (badarg (5)) + 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" .. diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 2a73815..4ed1003 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -12,6 +12,7 @@ before: | M = require "std.table" + specify std.table: - context when required: - context by name: @@ -41,18 +42,10 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) - f, badarg = init (M, this_module, "clone") + f = M.clone - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, "nometa")). - to_raise (badarg (2, "table, boolean, :nometa or nil", "string")) - expect (f ({}, {}, "nometa")). - to_raise (badarg (3, "boolean, :nometa or nil", "string")) - - it diagnoses too many arguments: - expect (f ({}, {}, nil, false)).to_raise (badarg (4)) + - 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) @@ -120,18 +113,10 @@ specify std.table: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } withmt = setmetatable (M.clone (subject), {"meta!"}) - f, badarg = init (M, this_module, "clone_select") + f = M.clone_select - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, "nometa")). - to_raise (badarg (2, "table, boolean, :nometa or nil", "string")) - expect (f ({}, {}, "nometa")). - to_raise (badarg (3, "boolean, :nometa or nil", "string")) - - it diagnoses too many arguments: - expect (f ({}, {}, nil, false)).to_raise (badarg (4)) + - 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) @@ -163,18 +148,10 @@ specify std.table: t = {"first", "second", third = 4} l = M.enpair (t) - f, badarg = init (M, this_module, "depair") + f = M.depair - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "list")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "list", "boolean")) - expect (f {0}). - to_raise (badarg (1, "list of lists", "number at index 1")) - expect (f {{"a", "b"}, ""}). - to_raise (badarg (1, "list of lists", "string at index 2")) - - it diagnoses too many arguments: - expect (f ({{"a", "b"}}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.depair (list of lists)") - it returns a primitive table: expect (prototype (f (l))).to_be "table" @@ -186,14 +163,10 @@ specify std.table: - describe empty: - before: - f, badarg = init (M, this_module, "empty") + f = M.empty - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.empty (table)") - it returns true for an empty table: expect (f {}).to_be (true) @@ -209,14 +182,10 @@ specify std.table: t = {"first", "second", third = 4} l = M.enpair (t) - f, badarg = init (M, this_module, "enpair") + f = M.enpair - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.enpair (table)") - it returns a table: expect (prototype (f (t))).to_be "table" @@ -232,14 +201,10 @@ specify std.table: - before: t = {{{"one"}}, "two", {{"three"}, "four"}} - f, badarg = init (M, this_module, "flatten") + f = M.flatten - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f (t, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.flatten (table)") - it returns a table: expect (type (f (t))).to_be "table" @@ -251,21 +216,24 @@ specify std.table: - describe insert: - before: - f, badarg = init (M, this_module, "insert") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - expect (f {}).to_raise (badarg (2, "int or any value")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses out of bounds pos arguments: - 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" - - it diagnoses too many arguments: - expect (f ({}, 1, 2, false)).to_raise (badarg (4)) - pending "#issue 76" - expect (f ({}, false, false)).to_raise (badarg (3)) + 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 = {} @@ -289,14 +257,10 @@ specify std.table: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } - f, badarg = init (M, this_module, "invert") + f = M.invert - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.invert (table)") - it returns a new table: expect (f (subject)).not_to_be (subject) @@ -313,14 +277,10 @@ specify std.table: - before: subject = { k1 = 1, k2 = 2, k3 = 3 } - f, badarg = init (M, this_module, "keys") + f = M.keys - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.keys (table)") - it returns an empty list when subject is empty: expect (f {}).to_equal {} @@ -337,14 +297,10 @@ specify std.table: - describe len: - before: - f, badarg = init (M, this_module, "len") + f = M.len - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.len (table)") - it returns the length of a table: expect (f {"a", "b", "c"}).to_be (3) @@ -360,14 +316,10 @@ specify std.table: - describe maxn: - before: - f, badarg = init (M, this_module, "maxn") + f = M.maxn - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - 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) @@ -393,21 +345,15 @@ specify std.table: f, badarg = init (M, this_module, "merge") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - expect (f ({})).to_raise (badarg (2, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_raise (badarg (2, "table", "boolean")) - expect (f ({}, {}, "nometa")). - to_raise (badarg (3, "table, boolean, :nometa or nil", "string")) - expect (f ({}, {}, {}, "nometa")). - to_raise (badarg (4, "boolean, :nometa or nil", "string")) - - it diagnoses too many arguments: | - expect (f ({}, {}, {}, ":nometa", false)). - to_raise (badarg (5)) - pending "issue #76" - expect (f ({}, {}, ":nometa", false)).to_raise (badarg (4)) + - 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) @@ -460,21 +406,15 @@ specify std.table: f, badarg = init (M, this_module, "merge_select") - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - expect (f ({})).to_raise (badarg (2, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_raise (badarg (2, "table", "boolean")) - expect (f ({}, {}, "nometa")). - to_raise (badarg (3, "table, boolean, :nometa or nil", "string")) - expect (f ({}, {}, {}, "nometa")). - to_raise (badarg (4, "boolean, :nometa or nil", "string")) - - it diagnoses too many arguments: | - expect (f ({}, {}, {}, ":nometa", false)). - to_raise (badarg (5)) - pending "issue #76" - expect (f ({}, {}, ":nometa", false)).to_raise (badarg (4)) + - 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) @@ -537,12 +477,10 @@ specify std.table: - describe monkey_patch: - before: - f, badarg = init (M, this_module, "monkey_patch") + f = M.monkey_patch - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.monkey_patch (?table)") - it returns the monkey_patched table entry from namespace: namespace = {} @@ -557,12 +495,10 @@ specify std.table: - describe new: - before: - f, badarg = init (M, this_module, "new") + f = M.new - - it diagnoses wrong argument types: - expect (f (nil, false)).to_raise (badarg (2, "table or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, {}, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.table.new (?any, ?table)") - context when not setting a default: - before: default = nil @@ -599,14 +535,10 @@ specify std.table: - before: subject = { "v1", k1 = 1, "v2", k2 = 2, "v3", k3 = 3, [10]="v10" } - f, badarg = init (M, this_module, "okeys") + f = M.okeys - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.okeys (table)") - it returns an empty list when subject is empty: expect (f {}).to_equal {} @@ -636,19 +568,10 @@ specify std.table: {first = "1st", second = "2nd", third = "3rd"}, } - f, badarg = init (M, this_module, "project") + f = M.project - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "any value")) - expect (f ("x")).to_raise (badarg (2, "list")) - - it diagnoses wrong argument types: - expect (f ("x", false)).to_raise (badarg (2, "list", "boolean")) - expect (f ("x", {false})). - to_raise (badarg (2, "list of tables", "boolean at index 1")) - expect (f ("x", {{}, false})). - to_raise (badarg (2, "list of tables", "boolean at index 2")) - - it diagnoses too many arguments: - expect (f ("x", l, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.table.project (any, list of tables)") - it returns a table: expect (prototype (f ("third", l))).to_be "table" @@ -662,18 +585,18 @@ specify std.table: - describe remove: - before: - f, badarg = init (M, this_module, "remove") - - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses out of bounds pos arguments: - 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" - - it diagnoses too many arguments: - expect (f ({}, 1, false)).to_raise (badarg (3)) + 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"} @@ -726,16 +649,10 @@ specify std.table: - before: l = {1, 2, 3, 4, 5, 6} - f, badarg = init (M, this_module, "shape") + f = M.shape - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - expect (f ({})).to_raise (badarg (2, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_raise (badarg (2, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, l, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.table.shape (table, table)") - it returns a table: expect (prototype (f ({2, 3}, l))).to_be "table" @@ -763,14 +680,10 @@ specify std.table: -- - 1 - --------- 2 ---------- -- 3 -- subject = { "one", { { "two" }, "three" }, four = 5 } - f, badarg = init (M, this_module, "size") + f = M.size - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - 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) @@ -784,15 +697,10 @@ specify std.table: target = { 0, 1, 2, 3, 4, 5 } cmp = function (a, b) return a < b end - f, badarg = init (M, this_module, "sort") + f = M.sort - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - expect (f ({}, false)).to_raise (badarg (2, "function or nil", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, f, false)).to_raise (badarg (3)) + - context with bad arguments: + badargs.diagnose (f, "std.table.sort (table, ?function)") - it sorts elements in place: f (subject, cmp) @@ -807,7 +715,7 @@ specify std.table: mt = { _type = "MockObject", __totable = function (self) return self.content end } - f, badarg = init (M, this_module, "totable") + f = M.totable - it writes a deprecation warning: setdebug { deprecate = "nil" } @@ -828,14 +736,10 @@ specify std.table: - before: subject = { k1 = {1}, k2 = {2}, k3 = {3} } - f, badarg = init (M, this_module, "values") + f = M.values - - it diagnoses missing arguments: - expect (f ()).to_raise (badarg (1, "table")) - - it diagnoses wrong argument types: - expect (f (false)).to_raise (badarg (1, "table", "boolean")) - - it diagnoses too many arguments: - expect (f ({}, false)).to_raise (badarg (2)) + - context with bad arguments: + badargs.diagnose (f, "std.table.values (table)") - it returns an empty list when subject is empty: expect (f {}).to_equal {} From ddb947c2ab2bc5ea1f381ee7706b3fb4949601cb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 5 Nov 2014 22:01:45 +0000 Subject: [PATCH 452/703] maint: fix bitrot in README. * README.md: Update. Signed-off-by: Gary V. Vaughan --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da259ce..4080ff1 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Installation The simplest way to install stdlib is with [LuaRocks][]. To install the latest release (recommended): - luarocks --server=http://rocks.moonscript.org install stdlib + luarocks install stdlib To install current git master (for testing): @@ -60,7 +60,7 @@ Documentation ------------- The libraries are [documented in LDoc][github.io]. Pre-built HTML -files are included. +files are included in the release. [github.io]: http://lua-stdlib.github.io/lua-stdlib From 5724893cf25d6795c1ef3d1ff90d841c20197901 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 16 Dec 2014 17:46:50 +0000 Subject: [PATCH 453/703] configury: adopt semantic versioning. * configure.ac (AC_INIT): Set version to 41.0.0. Signed-off-by: Gary V. Vaughan --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6732509..a6a471f 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [41], [http://github.com/lua-stdlib/lua-stdlib/issues]) +AC_INIT([stdlib], [41.0.0], [http://github.com/lua-stdlib/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) From a3e2fc6577cd57ea2e3c506add7d76ce008b0e3a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 19 Dec 2014 16:24:28 +0000 Subject: [PATCH 454/703] vector: remove unneeded module. On second thoughts, this is not necessary. Adding methods to core table would give us the API benefits, and posix.curses.chstr is a much easier way of handling arrays of attributed strings. * lib/std/vector.lua: Remove. * build-aux/config.ld.in (file): Remove lib/std/vector.lua. * local.mk (dist_luastd_DATA): Likewise. (dist_classes_DATA): Remove doc/classes/std.vector.html. * specs/vector_spec.yaml: Remove. * specs/string_spec.yaml (render): Remove vector examples. * specs/specs.mk (specl_SPECS): Remove specs/vector_spec.yaml. * NEWS: Remove vector references. Signed-off-by: Gary V. Vaughan --- NEWS | 4 - build-aux/config.ld.in | 1 - lib/std/vector.lua | 591 ---------------------------------- local.mk | 2 - specs/specs.mk | 1 - specs/string_spec.yaml | 4 - specs/vector_spec.yaml | 705 ----------------------------------------- 7 files changed, 1308 deletions(-) delete mode 100644 lib/std/vector.lua delete mode 100644 specs/vector_spec.yaml diff --git a/NEWS b/NEWS index 31bdf82..56a54e1 100644 --- a/NEWS +++ b/NEWS @@ -27,10 +27,6 @@ Stdlib NEWS - User visible changes operators `mod`, and `pow`; and relational operators `lt`, `lte`, `gt` and `gte`. - - New `std.vector` object, for clean and fast queue-like or stack-like - container management. When alien is installed, and element types - are compatible, uses alien.buffers for efficient element management. - - `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 diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index c88f3c5..969faaa 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -25,7 +25,6 @@ file = { "../lib/std/optparse.lua", "../lib/std/set.lua", "../lib/std/strbuf.lua", - "../lib/std/vector.lua", } new_type ("object", "Objects", false, "Fields") diff --git a/lib/std/vector.lua b/lib/std/vector.lua deleted file mode 100644 index 149d613..0000000 --- a/lib/std/vector.lua +++ /dev/null @@ -1,591 +0,0 @@ ---[[-- - Vector container prototype. - - A vector is usually a block of contiguous memory, divided into equal - sized elements that can be indexed quickly. - - If the Lua alien module is installed, and the *type* argument passed - when cloning a new vector object is suitable (i.e. the name of a numeric - C type that `alien.sizeof` understands), then the vector contents are - managed in an `alien.buffer`. - - If alien is not installed, or does not understand the *type* argument - given when cloning, then a much slower (but API compatible) Lua table - is transparently used to manage elements instead. - - In either case, @{Vector} provides a means for managing collections - of homogenous Lua objects with an array-like, stack-like or queue-like - API. - - Prototype Chain - --------------- - - table - `-> Object - `-> Container - `-> Vector - - @classmod std.vector -]] - - -local _DEBUG = require "std.debug_init"._DEBUG - -local have_alien, alien = pcall (require, "alien") -local base = require "std.base" -local debug = require "std.debug" - -local Container = require "std.container" {} - -local typeof = type - -local argcheck = debug.argcheck -local pairs, prototype = base.pairs, base.prototype -local insert, len = base.insert, base.len - - ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - -local buffer, memmove, memset -if have_alien then - buffer, memmove, memset = alien.buffer, alien.memmove, alien.memset -else - buffer = function () return {} end -end - - ---- Number of bytes needed in an alien.buffer for each `type` element. --- @string type name of an element type --- @treturn int bytes per `type`, or 0 if alien.buffer cannot store `type`s -local function sizeof (type) - local ok, size = pcall ((alien or {}).sizeof, type) - return ok and size or 0 -end - - ---- Convert a vector element index into a pointer. --- @tparam std.vector self a vector --- @int i[opt=1] an index into vector --- @treturn alien.buffer.pointer suitable for memmove or memset -local function topointer (self, i) - i = i or 1 - return self.buffer:topointer ((i - 1) * self.size + 1) -end - - ---- Fast zeroing of a contiguous block of vector elements for `alien.buffer`s. --- @tparam std.vector self a vector --- @int from index of first element to zero out --- @int n number of elements to zero out -local function setzero (self, from, n) - if n > 0 then memset (topointer (self, from), 0, n * self.size) end -end - - - ---[[ ================== ]]-- ---[[ Lua Table Manager. ]]-- ---[[ ================== ]]-- - - --- Initial vector prototype object, plus any derived object containing --- elements that don't fit in alien buffers use `core_functions` to --- find object methods and `core_metatable` for metamethods. - -local core_metatable, alien_metatable -- forward declarations - - -local core_functions = { - --- Remove the right-most element. - -- @function pop - -- @return the right-most element - -- @usage removed = avector:pop () - pop = function (self) - self.length = math.max (self.length - 1, 0) - return table.remove (self.buffer) - end, - - - --- Add elem as the new right-most element. - -- @function push - -- @param elem new element to be pushed - -- @return elem - -- @usage added = avector:push (anelement) - push = function (self, elem) - local length = self.length + 1 - self.buffer[length] = elem - self.length = length - return elem - end, - - - --- Change the number of elements allocated to be at least *n*. - -- @function realloc - -- @int n the number of elements required - -- @treturn std.vector the vector - -- @usage avector = avector:realloc (avector.length) - realloc = function (self, n) - argcheck ("realloc", 2, "int", n) - - -- Zero padding for uninitialised elements. - for i = self.length + 1, n do - self.buffer[i] = 0 - end - self.length = n - - return self - end, - - - --- Set *n* elements starting at *from* to *v*. - -- @function set - -- @int from index of first element to set - -- @param v value to store - -- @int n number of elements to set - -- @treturn std.vector the vector - -- @usage avector:realloc (avector.length):set (1, -1, avector.length) - set = function (self, from, v, n) - argcheck ("set", 2, "int", from) - argcheck ("set", 3, "any", v) - argcheck ("set", 4, "int", n) - - local length = self.length - if from < 0 then from = from + length + 1 end - assert (from > 0 and from <= length) - - for i = from, from + n - 1 do - self[i] = v - end - - return self - end, - - - --- Shift the whole vector to the left by removing the left-most element. - -- This makes the vector 1 element shorter than it was before the shift. - -- @function shift - -- @return the removed element. - -- @usage removed = avector:shift () - shift = function (self) - self.length = math.max (self.length - 1, 0) - return table.remove (self.buffer, 1) - end, - - - --- Shift the whole vector to the right by inserting a new left-most element. - -- @function unshift - -- @param elem new element to be pushed - -- @return *elem* - -- @usage added = avector:unshift (anelement) - unshift = function (self, elem) - self.length = self.length + 1 - insert (self.buffer, 1, elem) - return elem - end, -} - - -core_metatable = { - _type = "Vector", - - - --- Instantiate a newly cloned vector. - -- If not specified, `type` will be the same as the prototype vector being - -- cloned; otherwise, it can be any string. Only a type name accepted by - -- `alien.sizeof` will use the fast `alien.buffer` managed memory buffer - -- for vector contents; otherwise, a much slower Lua emulation is used. - -- @function __call - -- @string[opt="any"] type element type name - -- @tparam[opt={}] int|table init initial size or list of initial elements - -- @treturn std.vector a new vector object - -- @usage - -- local Vector = require "std.vector" {} -- not a typo! - -- local new = Vector ("int", {0xdead, 0xbeef, 0xfeed}) - -- --> 57005 48879 65261 57005 nil - -- print (a[1], a[2], a[3], a[-3], a[-4]) - __call = function (self, type, init) - if _DEBUG.argcheck then - if init ~= nil then - -- When called with 2 arguments: - argcheck ("Vector", 1, "string", type) - argcheck ("Vector", 2, "int|table", init) - elseif type ~= nil then - -- When called with 1 argument: - argcheck ("Vector", 1, "int|string|table", type) - end - end - - -- Non-string argument 1 is really an init argument. - if typeof (type) ~= "string" then type, init = nil, type end - - type = type or self.type - init = init or self.length - - -- This will become the cloned vector object. - local obj = {} - - for k, v in pairs (self) do - if typeof (v) ~= "table" or v._type ~= "modulefunction" then - obj[k] = v - end - end - - local size = sizeof (type) - obj.size = size -- setzero uses self.size for byte calculations - - if size == 0 then - - -- Either alien is not installed, or it cannot handle elements - -- of `type`, so we'll use Lua tables and core_metatable: - local b = {} - if typeof (init) == "table" then - for i = 1, len (init) do - b[i] = init[i] - end - else - local plength = self.length - local length = init or plength - for i = 1, math.min (plength, length) do - b[i] = self.buffer[i] - end - for i = plength + 1, length do - b[i] = 0 - end - end - obj.allocated = 0 - obj.buffer = b - obj.length = #b - - setmetatable (obj, core_metatable) - - else - - -- We have alien, and it knows how to manage elements of `type`, - -- so we'll use an alien.buffer and alien_metatable: - if typeof (init) == "table" then - local initlen = len (init) - obj.allocated = initlen - obj.buffer = buffer (size * initlen) - obj.length = initlen - - for i = 1, initlen do - obj.buffer:set ((i - 1) * size + 1, init[i], type) - end - else - obj.allocated = math.max (init or 0, 1) - obj.buffer = buffer (size * obj.allocated) - obj.length = init - - if size == self.size then - local bytes = math.min (init, self.length) * size - memmove (obj.buffer:topointer (), self.buffer:topointer (), bytes) - else - local a, b = obj.buffer, self.buffer - for i = 1, math.min (init, self.length) do a[i] = b[i] end - end - setzero (obj, self.length + 1, init - self.length) - end - - setmetatable (obj, alien_metatable) - - end - obj.type = type - - return obj - end, - - - --- Return the *n*th element in this vector. - -- - -- Unlike normal @{std.container.Container}, access is not limited to - -- only metamethods, integer keys are used to fetch element, and - -- string keys for method names. - -- @function __index - -- @int n 1-based index, or negative to index starting from the right - -- @treturn string the element at index *n* - -- @usage rightmost = avector[avector.length] - __index = function (self, n) - argcheck ("__index", 2, "int|string", n) - - if typeof (n) == "number" then - if n < 0 then n = n + self.length + 1 end - if n > 0 and n <= self.length then - return self.buffer[n] - end - else - return core_functions[n] - end - end, - - - --- Set the *n*th element of this vector to *elem*. - -- @function __newindex - -- @int n 1-based index - -- @param elem value to store at index n - -- @treturn std.vector the vector - -- @usage avector[1] = newvalue - __newindex = function (self, n, elem) - argcheck ("__newindex", 2, "int", n) - - if typeof (n) == "number" then - local used = self.length - if n == 0 or math.abs (n) > used then - error ("vector access " .. n .. " out of bounds: 0 < abs (n) <= " .. - tostring (self.length), 2) - end - if n < 0 then n = n + used + 1 end - self.buffer[n] = elem - else - rawset (self, n, elem) - end - return self - end, - - - --- Return the number of elements in this vector. - -- - -- Beware that Lua 5.1 `#` operator does not respect this metamethod; - -- use `vector.length` or @{std.table.len} if you care about portability. - -- @function __len - -- @treturn int number of elements - -- @usage length = table.len (avector) - __len = function (self) - argcheck ("__len", 1, "Vector", self) - - return self.length - end, - - - --- Return a string representation of the contents of this vector. - -- @function __tostring - -- @treturn string string representation - -- @usage print (avector) - __tostring = function (self) - argcheck ("__tostring", 1, "Vector", self) - - local t = {} - for i = 1, self.length do - t[#t + 1] = tostring (self[i]) - end - return prototype (self) .. ' ("' .. self.type .. - '", {' .. table.concat (t, ", ") .. "})" - end, -} - - - ---[[ ===================== ]]-- ---[[ Alien Buffer Manager. ]]-- ---[[ ===================== ]]-- - - --- Cloned vector objects with elements managed by an alien buffer use --- `alien_functions` to find object methods and `alien_metatable` --- for metamethods. - - -local element_chunk_size = 16 - - -local alien_functions = { - pop = function (self) - local used = self.length - if used > 0 then - local elem = self[used] - self:realloc (used - 1) - return elem - end - return nil - end, - - - push = function (self, elem) - argcheck ("push", 2, "number", elem) - - local used = self.length + 1 - self:realloc (used) - self[used] = elem - return elem - end, - - - realloc = function (self, n) - argcheck ("realloc", 2, "int", n) - - if n > self.allocated or n < self.allocated / 2 then - self.allocated = n + element_chunk_size - self.buffer:realloc (self.allocated * self.size) - end - - -- Zero padding for uninitialised elements. - local used = self.length - self.length = n - setzero (self, used + 1, n - used) - - return self - end, - - - set = function (self, from, v, n) - argcheck ("set", 2, "int", from) - argcheck ("set", 3, "number", v) - argcheck ("set", 4, "int", n) - - local used = self.length - if from < 0 then from = from + used + 1 end - assert (from > 0 and from <= used) - - local i = from + n - 1 - while i >= from do - self[i] = v - i = i - 1 - end - return self - end, - - - shift = function (self) - local n = self.length - 1 - if n >= 0 then - local elem = self[1] - memmove (topointer (self), topointer (self, 2), n * self.size) - self:realloc (n) - return elem - end - return nil - end, - - - unshift = function (self, elem) - argcheck ("unshift", 2, "number", elem) - - local n = self.length - self:realloc (n + 1) - memmove (topointer (self, 2), topointer (self), n * self.size) - self[1] = elem - return elem - end, -} - - -alien_metatable = { - _type = "Vector", - - __index = function (self, n) - argcheck ("__index", 2, "int|string", n) - - if typeof (n) == "number" then - if n < 0 then n = n + self.length + 1 end - if n > 0 and n <= self.length then - return self.buffer:get ((n - 1) * self.size + 1, self.type) - end - else - return alien_functions[n] - end - end, - - __newindex = function (self, n, elem) - argcheck ("__newindex", 2, "int", n) - argcheck ("__newindex", 3, "number", elem) - - if typeof (n) == "number" then - local used = self.length - if n == 0 or math.abs (n) > used then - error ("vector access " .. n .. " out of bounds: 0 < n <= " .. tostring (self.length), 2) - end - if n < 0 then n = n + used + 1 end - self.buffer:set ((n - 1) * self.size + 1, elem, self.type) - else - rawset (self, n, elem) - end - return self - end, - - __call = core_metatable.__call, - __len = core_metatable.__len, - __tostring = core_metatable.__tostring, -} - - - ---[[ ========================= ]]-- ---[[ Public Dispatcher Object. ]]-- ---[[ ========================= ]]-- - - ---- Return a function that dispatches to a virtual function table. --- The __call metamethod ensures that cloned vector objects are assigned --- a metatable and method table optimised for the element storage method --- (either alien buffer, or Lua table element containers), but the vector --- prototype returned by this module needs to dispatch to the correct --- function according to the element type at run-time, because we want --- to support passing either object as an argument to a module function. --- @string name method name to dispatch --- @treturn function call `alien_function[name]` or -- `core_function[name]` --- as appropriate to the element manager of vector -local function dispatch (name) - return function (vector, ...) - argcheck (name, 1, "Vector", vector) - local vfns = vector.size > 0 and alien_functions or core_functions - return vfns[name] (vector, ...) - end -end - - ---- Vector prototype object. --- --- Vector also inherits all the fields and methods from --- @{std.container.Container}, however the api is not limited to only --- metamethods like other Containers, because element access uses only --- integer keys, and so method name strings work too! --- @object Vector --- @string[opt="Vector"] _type object name --- @tfield __call __call instantiation function --- @int allocated number of allocated element slots, for `alien.buffer` --- managed elements --- @tfield alien.buffer|table buffer a block of indexable memory --- @int length number of elements currently stored --- @int size length of each stored element, or 0 when `alien.buffer` is --- not managing this vector --- @string type type name for elements --- @see std.container --- @see __index --- @usage --- local std = require "std" --- std.prototype (std.vector) --> "Vector" --- os.exit (0) -local Vector = Container { - _type = "Vector", - - - -- Prototype initial values. - allocated = 0, - buffer = {}, - length = 0, - size = 0, - type = "any", - - - _functions = { - pop = dispatch "pop", - push = dispatch "push", - realloc = dispatch "realloc", - set = dispatch "set", - shift = dispatch "shift", - unshift = dispatch "unshift", - }, - - - __index = dispatch "__index", - __newindex = dispatch "__newindex", - - __call = core_metatable.__call, - __len = core_metatable.__len, - __tostring = core_metatable.__tostring, -} - - -return Vector diff --git a/local.mk b/local.mk index 8186560..e1548cf 100644 --- a/local.mk +++ b/local.mk @@ -79,7 +79,6 @@ dist_luastd_DATA = \ lib/std/string.lua \ lib/std/table.lua \ lib/std/tree.lua \ - lib/std/vector.lua \ $(NOTHING_ELSE) # For bugwards compatibility with LuaRocks 2.1, while ensuring that @@ -134,7 +133,6 @@ dist_classes_DATA += \ $(srcdir)/doc/classes/std.set.html \ $(srcdir)/doc/classes/std.strbuf.html \ $(srcdir)/doc/classes/std.tree.html \ - $(srcdir)/doc/classes/std.vector.html \ $(NOTHING_ELSE) dist_modules_DATA += \ diff --git a/specs/specs.mk b/specs/specs.mk index f4cfc5f..591b61c 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -28,7 +28,6 @@ specl_SPECS = \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ - $(srcdir)/specs/vector_spec.yaml \ $(srcdir)/specs/std_spec.yaml \ $(NOTHING_ELSE) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 2c4825a..6d00cb8 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -454,7 +454,6 @@ specify std.string: return M.render (x, term "{", term "}", tostring, pair, sep) end t = {1, {{2, 3}, 4, {5}}} - a = require "std.vector" {"a", "b", {{"c"},"d"},"e"} f = M.render @@ -475,9 +474,6 @@ specify std.string: t[1] = t expect (r (t)). to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") - - it converts a Vector to a representative string: - expect (r (a)). - to_be ('Vector ("any", {a, b, ' .. tostring (a[3]) .. ', e})') - describe require_version: diff --git a/specs/vector_spec.yaml b/specs/vector_spec.yaml deleted file mode 100644 index 89cc45a..0000000 --- a/specs/vector_spec.yaml +++ /dev/null @@ -1,705 +0,0 @@ -before: - Object = require "std.object" - Vector = require "std.vector" - - prototype = Object.prototype - -specify Vector: -- before: | - vector = Vector ("long", {1, 1}) - -- append fibonacci numbers until long word overflows - repeat - vector:push (vector[-1] + vector[-2]) - until vector.length > 50 or vector[-1] < vector[-2] - -- discard overflowed element - vector:pop () - - local aliens = Vector ("int", {42}) - have_alien = aliens.allocated > 0 - -- describe __call: - - it diagnoses wrong argument types: | - expect (Vector (1, 2)). - to_raise "bad argument #1 to 'Vector' (string expected, got number)" - expect (Vector (function () end)). - to_raise "bad argument #1 to 'Vector' (int, string or table expected, got function)" - expect (Vector ("int", function () end)). - to_raise "bad argument #2 to 'Vector' (int or table expected, got function)" - - context with inherited element type: - - it constructs an empty vector: - vector = Vector () - expect (vector.length).to_be (0) - expect (vector.type).to_be (Vector.type) - - it constructs a sized vector: - vector = Vector (100) - expect (vector.length).to_be (100) - expect (vector.type).to_be (Vector.type) - - it sets uninitialised elements to zero: - vector = Vector (100) - for i = 1, 100 do - expect (vector[i]).to_be (0) - end - expect (vector.type).to_be (Vector.type) - - it initialises values from a table: - vector = Vector {1, 4, 9, 16, 25, 36, 49, 64, 81} - expect (vector.length).to_be (9) - for i = 1, vector.length do - expect (vector[i]).to_be (i * i) - end - expect (vector.type).to_be (Vector.type) - - it contains values from prototype vector: - a = vector () - for i = 3, vector.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - expect (a.type).to_be (vector.type) - - it truncates copied prototype values: - c = math.floor (vector.length / 2) - a = vector (c) - expect (a.length).to_be (c) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - expect (a.type).to_be (vector.type) - - it zero pads copied prototype values: - a = vector (vector.length * 2) - expect (a.length).to_be (vector.length * 2) - for i = 3, vector.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - for i = vector.length + 1, a.length do - expect (a[i]).to_be (0) - end - expect (a.type).to_be (vector.type) - - context with specified element type: - - it constructs an alien managed vector when possible: - if have_alien then - a = vector ("int", {1, 1, 2, 3, 5}) - expect (a.allocated).not_to_be (0) - b = a ("int", {1, 4, 9, 16, 25}) - expect (b.allocated).not_to_be (0) - end - - it constructs a tabular vector when necessary: - aliens = Vector ("int", {1, 1, 2, 3, 5}) - a = aliens ("table", {1, 2, 5}) - expect (a.allocated).to_be (0) - b = a ("table", {1, 4, 9, 16, 25}) - expect (a.allocated).to_be (0) - - it constructs an empty vector: - vector = Vector "double" - expect (vector.length).to_be (0) - expect (vector.type).to_be "double" - - it constructs a sized vector: - vector = Vector ("double", 100) - expect (vector.length).to_be (100) - expect (vector.type).to_be "double" - - it sets uninitialised elements to zero: - vector = Vector ("double", 100) - for i = 1, 100 do - expect (vector[i]).to_be (0) - end - expect (vector.type).to_be "double" - - it initialises values from a table: - vector = Vector ("double", {1, 4, 9, 16, 25, 36, 49, 64, 81}) - expect (vector.length).to_be (9) - for i = 1, vector.length do - expect (vector[i]).to_be (i * i) - end - expect (vector.type).to_be "double" - - it contains values from prototype vector: - a = vector "double" - for i = 3, vector.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - expect (a.type).to_be "double" - - it truncates copied prototype values: - c = math.floor (vector.length / 2) - a = vector ("double", c) - expect (a.length).to_be (c) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - expect (a.type).to_be "double" - - it zero pads copied prototype values: - a = vector ("double", vector.length * 2) - expect (a.length).to_be (vector.length * 2) - for i = 3, vector.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - for i = vector.length + 1, a.length do - expect (a[i]).to_be (0) - end - expect (a.type).to_be "double" - - context with non-alien element type: - - before: - vector = Vector ("table", { - {v=1}, {v=1}, {v=2}, {v=3}, {v=5}, {v=8}, {v=13}, {v=21}, {v=34} - }) - - it constructs an alien managed vector when possible: - if have_alien then - a = vector ("int", {1, 1, 2, 3, 5}) - expect (a.allocated).not_to_be (0) - b = vector ("int", {1, 4, 9, 16, 25}) - expect (a.allocated).not_to_be (0) - end - - it constructs a tabular vector when necessary: - aliens = Vector ("int", {1, 1, 2, 3, 5}) - a = aliens ("table", {1, 2, 5}) - expect (a.allocated).to_be (0) - b = a ("table", {1, 4, 9, 16, 25}) - expect (a.allocated).to_be (0) - - it constructs an empty vector: - vector = Vector "table" - expect (vector.length).to_be (0) - expect (vector.allocated).to_be (0) - - it constructs a sized vector: - vector = Vector ("table", 100) - expect (vector.length).to_be (100) - expect (vector.allocated).to_be (0) - - it sets uninitialised elements to zero: - vector = Vector ("table", 100) - for i = 1, 100 do - expect (vector[i]).to_be (0) - end - expect (vector.allocated).to_be (0) - - it initialises values from a table: - vector = Vector ("table", {{v=1}, {v=4}, {v=9}, {v=16}, {v=25}}) - expect (vector.length).to_be (5) - for i = 1, vector.length do - expect (vector[i].v).to_be (i * i) - end - expect (vector.allocated).to_be (0) - - it contains values from prototype vector: - a = vector () - for i = 3, vector.length do - expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) - end - expect (a.allocated).to_be (0) - - it truncates copied prototype values: - c = math.floor (vector.length / 2) - a = vector ("table", c) - expect (a.length).to_be (c) - for i = 3, a.length do - expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) - end - expect (a.allocated).to_be (0) - - it zero pads copied prototype values: - a = vector ("table", vector.length * 2) - expect (a.length).to_be (vector.length * 2) - for i = 3, vector.length do - expect (a[i].v).to_be (a[i - 1].v + a[i - 2].v) - end - for i = vector.length + 1, a.length do - expect (a[i]).to_be (0) - end - expect (a.allocated).to_be (0) - - -- describe __len: - - before: | - -- Some luajit releases, and PUC RIO 5.1 don't respect __len - -- metamethod for # operator. - local t = setmetatable ({}, {__len = function () return 42 end }) - meta__len = #t == 42 - - it returns the number of elements stored: | - empty = Vector "char" - trio = Vector ("short", {1, 2, 3}) - if meta__len then - -- __len metamethod support available - expect (#empty).to_be (0) - expect (#trio).to_be (3) - else - -- have to get the length explicitly - expect (empty.length).to_be (0) - expect (trio.length).to_be (3) - end - -- describe __index: - - it returns nil for an empty vector: - vector = Vector "int" - expect (vector[1]).to_be (nil) - expect (vector[-1]).to_be (nil) - - it retrieves a value stored at that index: - expect (vector[1]).to_be (1) - expect (vector[2]).to_be (1) - expect (vector[3]).to_be (2) - expect (vector[4]).to_be (3) - expect (vector[5]).to_be (5) - - it retrieves negative indices counting from the right: - expect (vector[-1]).to_be (vector[vector.length]) - expect (vector[-2]).to_be (vector[vector.length - 1]) - expect (vector[-(vector.length - 1)]).to_be (vector[2]) - expect (vector[-(vector.length)]).to_be (vector[1]) - - it returns nil for out of bounds indices: - expect (vector[-(vector.length * 2)]).to_be (nil) - expect (vector[-(vector.length + 1)]).to_be (nil) - expect (vector[0]).to_be (nil) - expect (vector[vector.length + 1]).to_be (nil) - expect (vector[vector.length * 2]).to_be (nil) - - it retrieves method names: - expect (type (vector.push)).to_be "function" - expect (type (vector.pop)).to_be "function" - - it diagnoses undefined methods: - expect (vector.notamethod ()).to_raise "attempt to call field 'notamethod'" - -- describe __newindex: - - it sets a new value at that index: - vector[2] = 2 - expect (vector[2]).to_be (2) - - it sets negative indexed elements counting from the right: - for i = 1, vector.length do vector[-i] = vector.length - i + 1 end - for i = 1, vector.length do - expect (vector[i]).to_be (i) - end - - it diagnoses out of bounds indices: - for _, i in ipairs {vector.length * -2, -1 - vector.length, 0, - vector.length + 1, vector.length * 2} do - expect ((function () vector[i] = i end) ()). - to_raise "out of bounds" - end - -- describe __tostring: - - it renders all elements of the vector: - vector = Vector ("char", {1, 4, 9, 16, 25}) - expect (tostring (vector)).to_be 'Vector ("char", {1, 4, 9, 16, 25})' - expect (tostring (Vector "char")).to_be 'Vector ("char", {})' - -- describe pop: - - context when called as a module function: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short vector for this example. - vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - expect (Vector.pop ()). - to_raise "bad argument #1 to 'pop' (Vector expected, got no value)" - - it returns nil for an empty vector: - vector = Vector "char" - expect (Vector.pop (vector)).to_be (nil) - - it removes an element from the vector: - count = vector.length - repeat - expect (vector.length).to_be (count) - count = count - 1 - until Vector.pop (vector) == nil - expect (vector.length).to_be (0) - - it returns the removed element: - while vector.length > 2 do - expect (Vector.pop (vector)).to_be (vector[-1] + vector[-2]) - end - - it does not perturb existing elements: - Vector.pop (vector) - for i = 3, vector.length do - expect (vector[i]).to_be (vector[i -1] + vector[i - 2]) - end - - context when called as an object method: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short vector for this example. - vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it returns nil for an empty vector: - vector = Vector "char" - expect (vector:pop ()).to_be (nil) - - it removes an element from the vector: - count = vector.length - repeat - expect (vector.length).to_be (count) - count = count - 1 - until vector:pop () == nil - expect (vector.length).to_be (0) - - it returns the removed element: - while vector.length > 2 do - expect (vector:pop ()).to_be (vector[-1] + vector[-2]) - end - - it does not perturb existing elements: - vector:pop () - for i = 3, vector.length do - expect (vector[i]).to_be (vector[i -1] + vector[i - 2]) - end - -- describe push: - - context when called as a module function: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short vector for this example. - vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - expect (Vector.push ()). - to_raise "bad argument #1 to 'push' (Vector expected, got no value)" - if vector.allocated > 0 then - -- non-alien managed vectors don't require number valued argument #2 - expect (Vector.push (vector)). - to_raise "bad argument #2 to 'push' (number expected, got no value)" - end - - it diagnoses wrong argument types: | - expect (Vector.push (1234)). - to_raise "bad argument #1 to 'push' (Vector expected, got number)" - if vector.allocated > 0 then - expect (Vector.push (vector, "short")). - to_raise "bad argument #2 to 'push' (number expected, got string)" - end - - it adds a single element to an empty vector: - vector = Vector "int" - Vector.push (vector, 42) - expect (vector[1]).to_be (vector[-1]) - - it adds an element to an vector: - count = vector.length - Vector.push (vector, 42) - expect (vector[-1]).to_be (42) - expect (vector.length).to_be (count + 1) - Vector.push (vector, -273) - expect (vector[-1]).to_be (-273) - expect (vector.length).to_be (count + 2) - - it does not perturb existing elements: - Vector.push (vector, 42) - for i = 3, vector.length - 1 do - expect (vector[i]).to_be (vector[i - 1] + vector[i - 2]) - end - - context when called as an object method: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short vector for this example. - vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - if vector.allocated > 0 then - expect (vector:push ()). - to_raise "bad argument #2 to 'push' (number expected, got no value)" - end - - it diagnoses wrong argument type: | - if vector.allocated > 0 then - expect (vector:push ("short")). - to_raise "bad argument #2 to 'push' (number expected, got string)" - end - - it adds a single element to an empty vector: - vector = Vector "int" - vector:push (42) - expect (vector[1]).to_be (vector[-1]) - - it adds an element to an vector: - count = vector.length - vector:push (42) - expect (vector[-1]).to_be (42) - expect (vector.length).to_be (count + 1) - vector:push (-273) - expect (vector[-1]).to_be (-273) - expect (vector.length).to_be (count + 2) - - it returns pushed value: - expect (vector:push (42)).to_be (42) - expect (vector:push (-273)).to_be (-273) - - it does not perturb existing elements: - vector:push (42) - for i = 3, vector.length - 1 do - expect (vector[i]).to_be (vector[i - 1] + vector[i -2]) - end - -- describe realloc: - - context when called as a module function: - - it diagnoses missing arguments: | - expect (Vector.realloc ()). - to_raise "bad argument #1 to 'realloc' (Vector expected, got no value)" - expect (Vector.realloc (vector)). - to_raise "bad argument #2 to 'realloc' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (Vector.realloc (1234)). - to_raise "bad argument #1 to 'realloc' (Vector expected, got number)" - expect (Vector.realloc (vector, "string")). - to_raise "bad argument #2 to 'realloc' (int expected, got string)" - - it reduces the number of usable elements: - vector = Vector (100) - Vector.realloc (vector, 50) - expect (vector.length).to_be (50) - - it truncates existing elements when reducing size: - a = vector (100) - Vector.realloc (a, 50) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it increases the number of usable elements: - vector = Vector (50) - Vector.realloc (vector, 100) - expect (vector.length).to_be (100) - - it does not perturb existing element values: - a = vector (50) - Vector.realloc (a, 100) - for i = 3, 50 do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it sets new elements to zero: - a = vector (50) - Vector.realloc (a, 100) - for i = 51, a.length do - expect (a[i]).to_be (0) - end - - context when called as an object method: - - it diagnoses missing arguments: | - expect (vector:realloc ()). - to_raise "bad argument #2 to 'realloc' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (vector:realloc "string"). - to_raise "bad argument #2 to 'realloc' (int expected, got string)" - - it reduces the number of usable elements: - vector = Vector (100):realloc (50) - expect (vector.length).to_be (50) - - it truncates existing elements when reducing size: - a = vector (100):realloc (50) - for i = 3, a.length do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it increases the number of usable elements: - vector = Vector (50):realloc (100) - expect (vector.length).to_be (100) - - it does not perturb existing element values: - a = vector (50):realloc (100) - for i = 3, 50 do - expect (a[i]).to_be (a[i - 1] + a[i - 2]) - end - - it sets new elements to zero: - a = vector (50):realloc (100) - for i = 51, a.length do - expect (a[i]).to_be (0) - end - -- describe set: - - context when called as a module function: - - it diagnoses missing arguments: | - expect (Vector.set ()). - to_raise "bad argument #1 to 'set' (Vector expected, got no value)" - expect (Vector.set (vector)). - to_raise "bad argument #2 to 'set' (int expected, got no value)" - if vector.allocated > 0 then - expect (Vector.set (vector, 1)). - to_raise "bad argument #3 to 'set' (number expected, got no value)" - end - expect (Vector.set (vector, 1, 0)). - to_raise "bad argument #4 to 'set' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (Vector.set (100)). - to_raise "bad argument #1 to 'set' (Vector expected, got number)" - expect (Vector.set (vector, "bogus")). - to_raise "bad argument #2 to 'set' (int expected, got string)" - if vector.allocated > 0 then - expect (Vector.set (vector, 1, {0})). - to_raise "bad argument #3 to 'set' (number expected, got table)" - end - expect (Vector.set (vector, 1, 0, function () end)). - to_raise "bad argument #4 to 'set' (int expected, got function)" - - it changes the value of a subsequence of elements: - vector = Vector (100) - Vector.set (vector, 25, 1, 50) - for i = 1, vector.length do - if i >= 25 and i < 75 then - expect (vector[i]).to_be (1) - else - expect (vector[i]).to_be (0) - end - end - - it understands negative from index: - vector = Vector (100) - Vector.set (vector, -50, 1, 50) - for i = 1, vector.length do - if i <= 50 then - expect (vector[i]).to_be (0) - else - expect (vector[i]).to_be (1) - end - end - - it does not affect the prototype vector elements: - a = vector (100) - Vector.set (a, 25, 1, 50) - for i = 3, vector.length do - expect (vector[i]).to_be (vector[i - 1] + vector[i - 2]) - end - - it does not affect elements outside range being set: - a = vector (100) - Vector.set (a, 25, 1, 50) - for i = 1, a.length do - if i >= 25 and i < 75 then - expect (a[i]).to_be (1) - elseif i <= vector.length then - expect (a[i]).to_be (vector[i]) - else - expect (a[i]).to_be (0) - end - end - - context when called as an object method: - - it diagnoses missing arguments: | - expect (vector:set ()). - to_raise "bad argument #2 to 'set' (int expected, got no value)" - if vector.allocated > 0 then - expect (vector:set (1)). - to_raise "bad argument #3 to 'set' (number expected, got no value)" - end - expect (vector:set (1, 0)). - to_raise "bad argument #4 to 'set' (int expected, got no value)" - - it diagnoses wrong argument types: | - expect (vector:set "bogus"). - to_raise "bad argument #2 to 'set' (int expected, got string)" - if vector.allocated > 0 then - expect (vector:set (1, {0})). - to_raise "bad argument #3 to 'set' (number expected, got table)" - end - expect (vector:set (1, 0, function () end)). - to_raise "bad argument #4 to 'set' (int expected, got function)" - - it changes the value of a subsequence of elements: - vector = Vector (100):set (25, 1, 50) - for i = 1, vector.length do - if i >= 25 and i < 75 then - expect (vector[i]).to_be (1) - else - expect (vector[i]).to_be (0) - end - end - - it understands negative from index: - vector = Vector (100):set (-50, 1, 50) - for i = 1, vector.length do - if i <= 50 then - expect (vector[i]).to_be (0) - else - expect (vector[i]).to_be (1) - end - end - - it does not affect the prototype vector elements: - a = vector (100):set (25, 1, 50) - for i = 3, vector.length do - expect (vector[i]).to_be (vector[i - 1] + vector[i - 2]) - end - - it does not affect elements outside range being set: - a = vector (100):set (25, 1, 50) - for i = 1, a.length do - if i >= 25 and i < 75 then - expect (a[i]).to_be (1) - elseif i <= vector.length then - expect (a[i]).to_be (vector[i]) - else - expect (a[i]).to_be (0) - end - end - -- describe shift: - - context when called as a module function: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short vector for this example. - vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - expect (Vector.shift ()). - to_raise "bad argument #1 to 'shift' (Vector expected, got no value)" - - it returns nil for an empty vector: - vector = Vector "char" - expect (Vector.shift (vector)).to_be (nil) - - it removes an element from the vector: - count = vector.length - repeat - expect (vector.length).to_be (count) - count = count - 1 - until Vector.shift (vector) == nil - expect (vector.length).to_be (0) - - it returns the removed element: - while vector.length > 2 do - expect (Vector.shift (vector)).to_be (vector[2] - vector[1]) - end - - it shifts existing elements one position left: - shiftme = vector () - Vector.shift (shiftme) - for i = 1, shiftme.length do - expect (shiftme[i]).to_be (vector[i + 1]) - end - - context when called as an object method: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short vector for this example. - vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it returns nil for an empty vector: - vector = Vector "char" - expect (vector:shift ()).to_be (nil) - - it removes an element from the vector: - count = vector.length - repeat - expect (vector.length).to_be (count) - count = count - 1 - until vector:shift () == nil - expect (vector.length).to_be (0) - - it returns the removed element: - while vector.length > 2 do - expect (vector:shift ()).to_be (vector[2] - vector[1]) - end - - it shifts existing elements one position left: - shiftme = vector () - shiftme:shift () - for i = 1, shiftme.length do - expect (shiftme[i]).to_be (vector[i + 1]) - end - -- describe unshift: - - context when called as a module function: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short vector for this example. - vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - expect (Vector.unshift ()). - to_raise "bad argument #1 to 'unshift' (Vector expected, got no value)" - if vector.allocated > 0 then - expect (Vector.unshift (vector)). - to_raise "bad argument #2 to 'unshift' (number expected, got no value)" - end - - it diagnoses wrong argument types: | - expect (Vector.unshift (1234)). - to_raise "bad argument #1 to 'unshift' (Vector expected, got number)" - if vector.allocated > 0 then - expect (Vector.unshift (vector, "short")). - to_raise "bad argument #2 to 'unshift' (number expected, got string)" - end - - it adds a single element to an empty vector: - vector = Vector "int" - Vector.unshift (vector, 42) - expect (vector[1]).to_be (vector[-1]) - - it inserts an element into an vector: - count = vector.length - Vector.unshift (vector, 42) - expect (vector[1]).to_be (42) - expect (vector.length).to_be (count + 1) - Vector.unshift (vector, -273) - expect (vector[1]).to_be (-273) - expect (vector.length).to_be (count + 2) - - it shifts existing elements one position right: - unshiftme = vector () - Vector.unshift (unshiftme, 42) - for i = 1, vector.length do - expect (unshiftme[i + 1]).to_be (vector[i]) - end - - context when called as an object method: - - before: - # Rounding impedance mismatch between Lua double and alien long, so we - # use an intentionally short vector for this example. - vector = Vector ("short", {1, 1, 2 ,3, 5, 8, 13, 21, 34, 55, 89}) - - it diagnoses missing arguments: | - if vector.allocated > 0 then - expect (vector:unshift ()). - to_raise "bad argument #2 to 'unshift' (number expected, got no value)" - end - - it diagnoses wrong argument type: | - if vector.allocated > 0 then - expect (vector:unshift ("short")). - to_raise "bad argument #2 to 'unshift' (number expected, got string)" - end - - it adds a single element to an empty vector: - vector = Vector "int" - vector:unshift (42) - expect (vector[1]).to_be (vector[-1]) - - it adds an element to an vector: - count = vector.length - vector:unshift (42) - expect (vector[1]).to_be (42) - expect (vector.length).to_be (count + 1) - vector:unshift (-273) - expect (vector[1]).to_be (-273) - expect (vector.length).to_be (count + 2) - - it returns unshifted value: - expect (vector:unshift (42)).to_be (42) - expect (vector:unshift (-273)).to_be (-273) - - it shifts existing elements one position right: - unshiftme = vector () - unshiftme:unshift (42) - for i = 1, vector.length do - expect (unshiftme[i + 1]).to_be (vector[i]) - end From b4e3bdfdfdbe5c3759db7d5db608f18b88561c16 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 19 Dec 2014 20:26:25 +0000 Subject: [PATCH 455/703] io: add dirname function again. * specs/io_spec.yaml (dirname): Specify behaviour of a new function to discard the final path separator in a string and all that follows. * lib/std/io.lua (M.dirname): Implement it. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 2 ++ lib/std/io.lua | 16 ++++++++++++---- specs/io_spec.yaml | 14 +++++++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 56a54e1..0ad0a8e 100644 --- a/NEWS +++ b/NEWS @@ -84,6 +84,8 @@ Stdlib NEWS - User visible changes `list.zip_with`, but also handles arbitrary tables of tables correctly, and is orthogonal to `functional.map_with`. + - `io.dirname` is available again. + - `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. diff --git a/lib/std/io.lua b/lib/std/io.lua index 778a3d4..f8e247e 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -15,11 +15,9 @@ local base = require "std.base" local debug = require "std.debug" local argerror = debug.argerror -local dirsep = base.dirsep +local catfile, dirsep, insert, len, leaves, split = + base.catfile, base.dirsep, base.insert, base.len, base.leaves, base.split local ipairs, pairs = base.ipairs, base.pairs -local insert, len = base.insert, base.len -local leaves = base.leaves -local split = base.split @@ -167,6 +165,16 @@ M = { -- @usage die ("oh noes! (%s)", tostring (obj)) die = X ("die (string, any?*)", function (...) warn (...); error () end), + --- 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 path:gsub (catfile ("", "[^", "]*$"), "") + end), + --- Overwrite core `io` methods with `std` enhanced versions. -- -- Also adds @{readlines} and @{writelines} metamethods to core file objects. diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 7d0439c..de387fb 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -3,7 +3,7 @@ before: | this_module = "std.io" global_table = "_G" - extend_base = { "catdir", "catfile", "die", "monkey_patch", + extend_base = { "catdir", "catfile", "die", "dirname", "monkey_patch", "process_files", "readlines", "shell", "slurp", "splitdir", "warn", "writelines" } @@ -132,6 +132,18 @@ specify std.io: expect (luaproc (script)).to_fail_with "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 monkey_patch: - before: namespace = {} From ee0d0c0a89a07dd66c36a3a734662f237d42e066 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 20 Dec 2014 11:20:18 +0000 Subject: [PATCH 456/703] string: don't return spurious additional values. * lib/std/base.lua (escape_pattern): Wrap gsub return value in parens to strip all but the first string return value. * lib/std/string.lua (caps, chomp, escape_shell, ltrim, rtrim) (trim): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 2 +- lib/std/string.lua | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index d0d99ca..40eb42d 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -163,7 +163,7 @@ end local function escape_pattern (s) - return s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") + return (s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) end diff --git a/lib/std/string.lua b/lib/std/string.lua index 41ebc33..b192614 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -86,12 +86,12 @@ end local function caps (s) - return s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end) + return (s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end)) end local function escape_shell (s) - return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1")) + return (s:gsub ("([ %(%)%\\%[%]\"'])", "\\%1")) end @@ -168,7 +168,7 @@ end local function trim (s, r) r = r or "%s+" - return s:gsub ("^" .. r, ""):gsub (r .. "$", "") + return (s:gsub ("^" .. r, ""):gsub (r .. "$", "")) end @@ -291,7 +291,7 @@ M = { -- @string s any string -- @treturn string *s* with any single trailing newline removed -- @usage line = chomp (line) - chomp = X ("chomp (string)", function (s) return s:gsub ("\n$", "") end), + chomp = X ("chomp (string)", function (s) return (s:gsub ("\n$", "")) end), --- Escape a string to be used as a pattern. -- @function escape_pattern @@ -339,7 +339,7 @@ M = { -- @treturn string *s* with leading *r* stripped -- @usage print ("got: " .. ltrim (userinput)) ltrim = X ("ltrim (string, string?)", - function (s, r) return s:gsub ("^" .. (r or "%s+"), "") end), + function (s, r) return (s:gsub ("^" .. (r or "%s+"), "")) end), --- Overwrite core `string` methods with `std` enhanced versions. -- @@ -426,7 +426,7 @@ M = { -- @treturn string *s* with trailing *r* stripped -- @usage print ("got: " .. rtrim (userinput)) rtrim = X ("rtrim (string, string?)", - function (s, r) return s:gsub ((r or "%s+") .. "$", "") end), + function (s, r) return (s:gsub ((r or "%s+") .. "$", "")) end), --- Split a string at a given separator. -- Separator is a Lua pattern, so you have to escape active characters, From e8f8c69e667d1495c849c04129155a875b34964f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 24 Dec 2014 11:16:20 +0000 Subject: [PATCH 457/703] doc: show release version in LDocs column headings. * build-aux/config.ld.in (project): Add release version. Signed-off-by: Gary V. Vaughan --- build-aux/config.ld.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 969faaa..7eff64d 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -1,6 +1,6 @@ -- -*- lua -*- -title = "@PACKAGE@ @VERSION@ Reference" -project = "stdlib" +title = "@PACKAGE_STRING@ Reference" +project = "@PACKAGE_STRING@" description = "Standard Lua Libraries" dir = "." From baa6fb658e8aae7ec53e928559f6a3eb6bd06752 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 21 Dec 2014 23:29:14 +0000 Subject: [PATCH 458/703] functional: improve lambda expressiveness. * specs/functional_spec.yaml: Specify new behaviours with omitted optional '=', and '_' alias argument. * lib/std/functional.lua (lambda): Strip more useless whitespace in parsing to improve memoize cache hits. Support omitting leading '=' when first non-whitespace of lambda string is '_'. Add '_' alias to '_1' for lambda string compiled expressions. Update LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 23 +++++++++++++---------- specs/functional_spec.yaml | 7 +++++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index ed3c690..20df8b3 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -165,18 +165,19 @@ local lambda = memoize (function (s) local expr -- Support "|args|expression" format. - local args, body = s:match "^|([^|]*)|%s*(.+)$" + local args, body = s:match "^%s*|%s*([^|]*)|%s*(.+)%s*$" if args and body then expr = "return function (" .. args .. ") return " .. body .. " end" end - -- Support "=expression" format. + -- Support "expression" format. if not expr then - body = s:match "^=%s*(.+)$" + body = s:match "^%s*(_.*)%s*$" or s:match "^=%s*(.+)%s*$" if body then expr = [[ return function (...) local _1,_2,_3,_4,_5,_6,_7,_8,_9 = unpack {...} + local _ = _1 return ]] .. body .. [[ end ]] @@ -404,18 +405,20 @@ local M = { -- -- A valid lambda string takes one of the following forms: -- - -- 1. `'=expression'`: equivalent to `function (...) return (expression) end` - -- 1. `'|args|expression'`: equivalent to `function (args) return (expression) end` + -- 1. `'=expression'`: equivalent to `function (...) return expression end` + -- 1. `'|args|expression'`: equivalent to `function (args) return expression end` -- - -- The first form (starting with `=`) automatically assigns the first - -- nine arguments to parameters `_1` through `_9` for use within the - -- expression body. + -- The first form (starting with `'='`) automatically assigns the first + -- nine arguments to parameters `'_1'` through `'_9'` for use within the + -- expression body. The parameter `'_1'` is aliased to `'_'`, and if the + -- first non-whitespace of the whole expression is `'_'`, then the + -- leading `'='` can be omitted. -- - -- The results are memoized, so recompiling an previously compiled + -- The results are memoized, so recompiling a previously compiled -- lambda string is extremely fast. -- @function lambda -- @string s a lambda string - -- @treturn table compiled lambda string, can be called like a function + -- @treturn functable compiled lambda string, can be called like a function -- @usage -- -- The following are equivalent: -- lambda '= _1 < _2' diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 9573888..768dbe5 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -352,13 +352,16 @@ specify std.functional: expect (fn (1,2,3)).to_equal {1,2,3} - context with expression format: - it returns a function: - expect (prototype (f "=1")).to_be "function" + expect (prototype (f "_")).to_be "function" - it compiles to a working Lua function: fn = f "=42" expect (fn ()).to_be (42) - it sets auto-argument values: - fn = f "=_1*_1" + fn = f "_*_" expect (fn (42)).to_be (1764) + - it sets numeric auto-argument values: + fn = f "_1+_2+_3" + expect (fn (1, 2, 5)).to_be (8) - describe map: From 520be773934e7531b7ed93387333b0d6e635a25f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 21 Dec 2014 23:33:26 +0000 Subject: [PATCH 459/703] functional: generalize reduce to any table. * specs/functional_spec.yaml (reduce): Specify optional iterator argument defaulting to std.pairs. Specify requirement for std.ielems when ignoring reduced table keys. * specs/operator_spec.yaml (set): Specify behaviours for a new table element setting operator. * specs/container_spec.yaml: Use appropriate iterator functions for new reduce API. * lib/std/functional.lua (reduce): Diagnose optional iterator argument to std.pairs. (fold): Copy old reduce implementation into deprecated call. * lib/std.lua.in (barrel): Adjust accordingly. * lib/std/base.lua (reduce): Default optional iterator argument to std.pairs. Pass all iterator results to accumulator function. Adjust all clients. * lib/std/operator.lua (set): Implement according to new specs. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 27 +++++++++++++++++++++------ lib/std.lua.in | 2 +- lib/std/base.lua | 18 ++++++++++++------ lib/std/functional.lua | 28 ++++++++++++++++++++-------- lib/std/list.lua | 4 ++-- lib/std/operator.lua | 19 +++++++++++++++---- specs/container_spec.yaml | 3 ++- specs/functional_spec.yaml | 10 ++++++---- specs/operator_spec.yaml | 12 ++++++++++++ specs/std_spec.yaml | 2 +- 10 files changed, 92 insertions(+), 33 deletions(-) diff --git a/NEWS b/NEWS index 0ad0a8e..c3c714c 100644 --- a/NEWS +++ b/NEWS @@ -59,7 +59,6 @@ Stdlib NEWS - User visible changes 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 @@ -269,11 +268,27 @@ Stdlib NEWS - User visible changes 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. + - `functional.collect`, `functional.filter`, `functional.map` and + `functional.reduce` 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. + + - `functional.reduce` now passes all iterator results to the reduce + function along with the accumlator. This means if you were + previously relying on all but the last result being discarded, you + either need to rewrite your reduce function or else select a + different iterator to compensate. Instead of: + + reduce (function (d, v) return d + v end, 2, ipairs, {4, 6, 8}) + + either, rewrite the reduce function: + + reduce (function (d, i, v) return d + v end, 2, ipairs, {4, 6, 8}) + + or, use the index discarding iterator: + + reduce (function (d, v) return d + v end, 2, ielems, {4, 6, 8}) - The `functional.op` table has been factored out into its own new module `std.operator`. It will also continue to be available from the diff --git a/lib/std.lua.in b/lib/std.lua.in index 1150d05..c12f727 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -55,7 +55,7 @@ local function barrel (namespace) end -- Support old api names, for backwards compatibility. - namespace.fold = M.functional.reduce + namespace.fold = M.functional.fold namespace.metamethod = M.getmetamethod namespace.op = M.operator namespace.require_version = M.require diff --git a/lib/std/base.lua b/lib/std/base.lua index 40eb42d..d802e43 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -275,13 +275,19 @@ end local function reduce (fn, d, ifn, ...) - local nextfn, state, k = ifn (...) - local t = {nextfn (state, k)} + local argt = {...} + if not callable (ifn) then + ifn, argt = pairs, {ifn, ...} + end + + local nextfn, state, k = ifn (unpack (argt)) + local t = {nextfn (state, k)} -- table of iteration 1 - local r = d - while t[1] ~= nil do - r = fn (r, t[#t]) - t = {nextfn (state, t[1])} + local r = d -- initialise accumulator + while t[1] ~= nil do -- until iterator returns nil + k = t[1] + r = fn (r, unpack (t)) -- pass all iterator results to fn + t = {nextfn (state, k)} -- maintain loop invariant end return r end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 20df8b3..22306b1 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -11,8 +11,8 @@ local base = require "std.base" local debug = require "std.debug" -local ipairs, ireverse, len, pairs = - base.ipairs, base.ireverse, base.len, base.pairs +local ielems, ipairs, ireverse, len, pairs = + base.ielems, base.ipairs, base.ireverse, base.len, base.pairs local callable, reduce = base.callable, base.reduce @@ -123,7 +123,7 @@ local function foldl (fn, d, t) for i = 2, len (d) do tail[#tail + 1] = d[i] end d, t = d[1], tail end - return reduce (fn, d, ipairs, t) + return reduce (fn, d, ielems, t) end @@ -133,7 +133,7 @@ local function foldr (fn, d, t) for i = 1, last - 1 do u[#u + 1] = d[i] end d, t = d[last], u end - return reduce (function (x, y) return fn (y, x) end, d, ipairs, ireverse (t)) + return reduce (function (x, y) return fn (y, x) end, d, ielems, ireverse (t)) end @@ -478,15 +478,15 @@ local M = { -- @function reduce -- @func fn reduce function -- @param d initial first argument - -- @func ifn iterator function + -- @func[opt=std.pairs] ifn iterator function -- @param ... iterator arguments -- @return result -- @see foldl -- @see foldr -- @usage -- --> 2 ^ 3 ^ 4 ==> 4096 - -- reduce (std.operator.pow, 2, std.ipairs, {3, 4}) - reduce = X ("reduce (func, any, func, any*)", reduce), + -- reduce (std.operator.pow, 2, std.ielems, {3, 4}) + reduce = X ("reduce (func, any, [func], any*)", reduce), --- Zip a table of tables. -- Make a new table, with lists of elements at the same index in the @@ -533,8 +533,20 @@ M.eval = DEPRECATED ("41", "'std.functional.eval'", "use 'std.eval' instead", base.eval) +local function fold (fn, d, ifn, ...) + local nextfn, state, k = ifn (...) + local t = {nextfn (state, k)} + + local r = d + while t[1] ~= nil do + r = fn (r, t[#t]) + t = {nextfn (state, t[1])} + end + return r +end + M.fold = DEPRECATED ("41", "'std.functional.fold'", - "use 'std.functional.reduce' instead", reduce) + "use 'std.functional.reduce' instead", fold) local operator = require "std.operator" diff --git a/lib/std/list.lua b/lib/std/list.lua index d8bd9ec..6cf1915 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -220,7 +220,7 @@ local function foldl (fn, d, t) for i = 2, len (d) do tail[#tail + 1] = d[i] end d, t = d[1], tail end - return base.reduce (fn, d, ipairs, t) + return base.reduce (fn, d, base.ielems, t) end @@ -231,7 +231,7 @@ local function foldr (fn, d, t) d, t = d[last], u end return base.reduce ( - function (x, y) return fn (y, x) end, d, ipairs, base.ireverse (t)) + function (x, y) return fn (y, x) end, d, base.ielems, base.ireverse (t)) end diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 5db2044..6d441ea 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -16,7 +16,7 @@ local M = { -- @return concatenation of stringified arguments. -- @usage -- --> "=> 1000010010" - -- functional.reduce (concat, "=> ", ipairs, {10000, 100, 10}) + -- functional.foldl (concat, "=> ", {10000, 100, 10}) concat = function (a, b) return tostring (a) .. tostring (b) end, --- Dereference a table. @@ -25,9 +25,20 @@ local M = { -- @return value stored at *t[k]* if any, otherwise `nil` -- @usage -- --> 4 - -- functional.reduce (deref, {1, {{2, 3, 4}, 5}}, std.ielems, {2, 1, 3}) + -- functional.foldl (deref, {1, {{2, 3, 4}, 5}}, {2, 1, 3}) deref = function (t, k) return t and t[k] or nil end, + --- Set a table element, honoring metamethods. + -- @tparam table t a table + -- @param k a key to lookup in *t* + -- @param v a value to set for *k* + -- @treturn table *t* + -- @usage + -- -- destructive table merge: + -- --> {"one", bar="baz", two=5} + -- functional.reduce (set, {"foo", bar="baz"}, {"one", two=5}) + set = function (t, k, v) t[k]=v; return t end, + --- Return the sum of the arguments. -- @param a an argument -- @param b another argument @@ -43,7 +54,7 @@ local M = { -- @return the difference between *a* and *b* -- @usage -- --> 890 - -- functional.foldl (sum, {10000, 100, 10}) + -- functional.foldl (diff, {10000, 100, 10}) diff = function (a, b) return a - b end, --- Return the product of the arguments. @@ -52,7 +63,7 @@ local M = { -- @return the product of *a* and *b* -- @usage -- --> 10000000 - -- functional.foldl (sum, {10000, 100, 10}) + -- functional.foldl (prod, {10000, 100, 10}) prod = function (a, b) return a * b end, --- Return the quotient of the arguments. diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index cb6cdf2..857537f 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -53,9 +53,10 @@ specify std.container: - context with module functions: - before: reduce = require "std.functional".reduce + elems = require "std".elems functions = { count = function (bag) - return reduce (function (r, k) return r + k end, 0, pairs, bag) + return reduce (function (r, k) return r + k end, 0, elems, bag) end, } Bag = Container { diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 768dbe5..e54624d 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -669,7 +669,7 @@ specify std.functional: f = M.reduce - context with bad arguments: - badargs.diagnose (f, "std.functional.reduce (func, any, func, any*)") + badargs.diagnose (f, "std.functional.reduce (func, any, [func], any*)") - it works with an empty table: expect (f (op.sum, 2, ipairs, {})).to_be (2) @@ -679,10 +679,12 @@ specify std.functional: expect (f (op.prod, 2, base.ielems, {3, 4})). to_be (2 * 3 * 4) - it calls a binary function over key:value iterator results: - expect (f (op.sum, 2, ipairs, {3})).to_be (2 + 3) - expect (f (op.prod, 2, ipairs, {3, 4})).to_be (2 * 3 * 4) + expect (f (op.sum, 2, base.ielems, {3})).to_be (2 + 3) + expect (f (op.prod, 2, base.ielems, {3, 4})).to_be (2 * 3 * 4) - it reduces elements from left to right: - expect (f (op.pow, 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) + expect (f (op.pow, 2, base.ielems, {3, 4})).to_be ((2 ^ 3) ^ 4) + - it passes all iterator results to accumulator function: + expect (f (rawset, {}, {"one", two=5})).to_equal {"one", two=5} - describe zip: diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index 10f487e..73cb866 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -36,6 +36,18 @@ specify std.operator: expect (f ({"foo", "bar"}, 1)).to_be "foo" expect (f ({foo = "bar"}, "foo")).to_be "bar" +- describe set: + - before: + f = M.set + + - it sets a table entry: + expect (f ({}, 1, 42)).to_equal {42} + expect (f ({}, "foo", 42)).to_equal {foo=42} + - it overwrites an existing entry: + expect (f ({1, 2}, 1, 42)).to_equal {42, 2} + expect (f ({foo="bar", baz="quux"}, "foo", 42)). + to_equal {foo=42, baz="quux"} + - describe sum: - before: f = M.sum diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 4c7916d..67d90d2 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -149,7 +149,7 @@ specify std: curry = M.functional.curry, die = M.io.die, filter = M.functional.filter, - fold = M.functional.reduce, + fold = M.functional.fold, id = M.functional.id, ileaves = M.tree.ileaves, inodes = M.tree.inodes, From 1e3229a5d3afa60b72d97a505771975b425c3cfc Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 22 Dec 2014 00:04:33 +0000 Subject: [PATCH 460/703] refactor: rename operator.deref to operator.get. * specs/operator_spec.yaml (deref): Rename from this... (get): ...to this. * lib/std/operator.lua (deref): Rename from this... (get): ...to this. Adjust all callers. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 8 ++++---- lib/std/functional.lua | 2 +- lib/std/operator.lua | 4 ++-- lib/std/tree.lua | 2 +- specs/operator_spec.yaml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index c3c714c..1934040 100644 --- a/NEWS +++ b/NEWS @@ -22,10 +22,10 @@ Stdlib NEWS - User visible changes 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`. + `get`, `diff`, `disj`, `eq`, `neg`, `neq`, `prod`, `quot`, and `sum`), + a new `set` operator, 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 diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 22306b1..22c80ce 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -557,7 +557,7 @@ local function DEPRECATEOP (old, new) end M.op = { - ["[]"] = DEPRECATEOP ("[]", "deref"), + ["[]"] = DEPRECATEOP ("[]", "get"), ["+"] = DEPRECATEOP ("+", "sum"), ["-"] = DEPRECATEOP ("-", "diff"), ["*"] = DEPRECATEOP ("*", "prod"), diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 6d441ea..1f77e78 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -25,8 +25,8 @@ local M = { -- @return value stored at *t[k]* if any, otherwise `nil` -- @usage -- --> 4 - -- functional.foldl (deref, {1, {{2, 3, 4}, 5}}, {2, 1, 3}) - deref = function (t, k) return t and t[k] or nil end, + -- functional.foldl (get, {1, {{2, 3, 4}, 5}}, {2, 1, 3}) + get = function (t, k) return t and t[k] or nil end, --- Set a table element, honoring metamethods. -- @tparam table t a table diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 2e3d15c..91b7665 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -140,7 +140,7 @@ Tree = Container { __index = X ("__index (Tree, any)", function (tr, i) if prototype (i) == "table" then - return reduce (operator.deref, tr, ielems, i) + return reduce (operator.get, tr, ielems, i) else return rawget (tr, i) end diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index 73cb866..505d041 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -27,9 +27,9 @@ specify std.operator: - it concatenates its arguments: expect (f (1, 2)).to_be "12" -- describe deref: +- describe get: - before: - f = M.deref + f = M.get - it dereferences a table: expect (f ({}, 1)).to_be (nil) From 9cb26e68b64eb88bdfae1c65de4b6b140a85c7d7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 21 Dec 2014 20:22:39 +0000 Subject: [PATCH 461/703] specs: use correct Specl > 13 arity for bad argument errors. Now that Specl's badargs module is generating examples with the correct messages, we can now say 'no more than 0 arguments'. * lib/std/debug.lua (toomanyargmsg): Return singular 'argument' in error message only for exactly 1 expected argument, otherwise plural. * bootstrap.conf (buildreq): Bump specl requirement. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 2 +- lib/std/debug.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap.conf b/bootstrap.conf index 0a3c8e9..a69e0cd 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -31,7 +31,7 @@ buildreq=' git - http://git-scm.com ldoc 1.4.2 http://rocks.moonscript.org/manifests/steved/ldoc-1.4.2-1.rockspec - specl 13 http://rocks.moonscript.org/manifests/gvvaughan/specl-13-1.rockspec + specl 14 http://rocks.moonscript.org/manifests/gvvaughan/specl-14-1.rockspec ' # List of slingshot files to link into stdlib tree before autotooling. diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 9a8f2af..f0e45bf 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -127,7 +127,7 @@ end local function toomanyargmsg (name, expect, actual) local fmt = "bad argument #%d to '%s' (no more than %d argument%s expected, got %d)" - return string.format (fmt, expect + 1, name, expect, expect > 1 and "s" or "", actual) + return string.format (fmt, expect + 1, name, expect, expect == 1 and "" or "s", actual) end From 2c2b048ca10f7b41662083f9ed56f32900fd0550 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 16 Dec 2014 18:11:58 +0000 Subject: [PATCH 462/703] maint: preliminary Lua 5.3.0 compatibility. * configure.ac (AX_PROG_LUA): Accept Lua 5.3 interpreters. * lib/std/io.lua (setmetatable): Define locally to debug.setmetatable for resetting FILE* metatable in given namespace. * lib/std/base.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/list.lua, lib/std/package.lua (unpack): Set to either table.unpack or _G.unpack to satisfy Lua 5.1, 5.2 and 5.3. * specs/spec_helper.lua (unpack): Set appropriately for all supported Lua releases. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS | 2 ++ configure.ac | 2 +- lib/std/base.lua | 1 + lib/std/debug.lua | 1 + lib/std/functional.lua | 2 ++ lib/std/io.lua | 1 + lib/std/list.lua | 1 + lib/std/package.lua | 2 +- specs/spec_helper.lua | 1 + 9 files changed, 11 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 1934040..d564b9c 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ Stdlib NEWS - User visible changes ** New features: + - Preliminary Lua 5.3.0 compatibility. + - `object.prototype` now reports "file" for open file handles, and "closed file" for closed file handles. diff --git a/configure.ac b/configure.ac index a6a471f..16157da 100644 --- a/configure.ac +++ b/configure.ac @@ -29,7 +29,7 @@ AM_INIT_AUTOMAKE([-Wall]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) dnl Check for programs -AX_PROG_LUA([5.1], [5.3]) +AX_PROG_LUA([5.1], [5.4]) AC_PATH_PROG([LDOC], [ldoc], [:]) AC_PATH_PROG([SPECL], [specl], [:]) AC_PROG_EGREP diff --git a/lib/std/base.lua b/lib/std/base.lua index d802e43..365483a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,6 +24,7 @@ local dirsep = string.match (package.config, "^(%S+)\n") +local unpack = table.unpack or unpack local function argerror (name, i, extramsg, level) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index f0e45bf..7995839 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -37,6 +37,7 @@ local argerror = base.argerror local split, tostring = base.split, base.tostring local insert, last, len, maxn = base.insert, base.last, base.len, base.maxn local ipairs, pairs = base.ipairs, base.pairs +local unpack = table.unpack or unpack local M diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 22c80ce..1e97bca 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -14,6 +14,7 @@ local debug = require "std.debug" local ielems, ipairs, ireverse, len, pairs = base.ielems, base.ipairs, base.ireverse, base.len, base.pairs local callable, reduce = base.callable, base.reduce +local unpack = table.unpack or unpack local function bind (fn, ...) @@ -176,6 +177,7 @@ local lambda = memoize (function (s) if body then expr = [[ return function (...) + local unpack = table.unpack or unpack local _1,_2,_3,_4,_5,_6,_7,_8,_9 = unpack {...} local _ = _1 return ]] .. body .. [[ diff --git a/lib/std/io.lua b/lib/std/io.lua index f8e247e..87230b3 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -18,6 +18,7 @@ local argerror = debug.argerror local catfile, dirsep, insert, len, leaves, split = base.catfile, base.dirsep, base.insert, base.len, base.leaves, base.split local ipairs, pairs = base.ipairs, base.pairs +local setmetatable = debug.setmetatable diff --git a/lib/std/list.lua b/lib/std/list.lua index 6cf1915..8932028 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -21,6 +21,7 @@ local ipairs, pairs = base.ipairs, base.pairs local len = base.len local compare = base.compare local prototype = base.prototype +local unpack = table.unpack or unpack local M, List diff --git a/lib/std/package.lua b/lib/std/package.lua index d8f36ab..0c13a64 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -80,7 +80,7 @@ local function normalize (...) end -local unpack = unpack or table.unpack +local unpack = table.unpack or unpack local function insert (pathstrings, ...) local paths = split (pathstrings, pathsep) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index e33ce38..b5dc3b9 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -3,6 +3,7 @@ local hell = require "specl.shell" local std = require "specl.std" badargs = require "specl.badargs" +unpack = table.unpack or unpack local top_srcdir = os.getenv "top_srcdir" or "." local top_builddir = os.getenv "top_builddir" or "." From 9efdf306479cce58a2f4de894220360526405c9c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 19 Dec 2014 21:46:58 +0000 Subject: [PATCH 463/703] maint: 5.3 uses load instead of loadstring. * lib/std/base.lua (loadstring): Set to load if loadstring is not available. * lib/std/functional.lua (loadstring): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 5 +++-- lib/std/functional.lua | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 365483a..9c068cb 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,8 +23,9 @@ ]] -local dirsep = string.match (package.config, "^(%S+)\n") -local unpack = table.unpack or unpack +local dirsep = string.match (package.config, "^(%S+)\n") +local loadstring = loadstring or load +local unpack = table.unpack or unpack local function argerror (name, i, extramsg, level) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 1e97bca..026d7b7 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -14,7 +14,8 @@ local debug = require "std.debug" local ielems, ipairs, ireverse, len, pairs = base.ielems, base.ipairs, base.ireverse, base.len, base.pairs local callable, reduce = base.callable, base.reduce -local unpack = table.unpack or unpack +local loadstring = loadstring or load +local unpack = table.unpack or unpack local function bind (fn, ...) From 3c32757afd6095147fd4f1285a6ed183eabd7d4a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 20 Dec 2014 11:18:52 +0000 Subject: [PATCH 464/703] string: consistent numbertosi output in Lua 5.3. * lib/std/string.lua (numbertosi): Be careful to output an integer even when handling a number ('double') in Lua 5.3. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index b192614..ec5aab3 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -162,7 +162,7 @@ local function numbertosi (n) local shift = exp - siexp * 3 local s = SIprefix[siexp] or "e" .. tostring (siexp) man = man * (10 ^ shift) - return tostring (man) .. s + return format ("%0.f", man) .. s end From e5bc4639e0dd8423dd22dcf7d6820f11676b5d9e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 19 Dec 2014 16:30:47 +0000 Subject: [PATCH 465/703] slingshot: sync with upstream, for Lua 5.3.0 compatibility. * slingshot: Sync with upstream. * bootstrap: Update from slingshot. * NEWS: Move from here... * NEWS.md: ...to here. Reformat as Markdown and update. * local.mk (old_NEWS_hash): Update. * .gitignore: Add NEWS. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .gitignore | 1 + .travis.yml | 185 +++++--- NEWS | 1135 ---------------------------------------------- NEWS.md | 1174 ++++++++++++++++++++++++++++++++++++++++++++++++ bootstrap | 327 +++++++++++--- bootstrap.conf | 11 +- configure.ac | 1 - local.mk | 2 +- slingshot | 2 +- 9 files changed, 1569 insertions(+), 1269 deletions(-) delete mode 100644 NEWS create mode 100644 NEWS.md diff --git a/.gitignore b/.gitignore index 9168ea4..697e22f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /Makefile /Makefile.am /Makefile.in +/NEWS /README /aclocal.m4 /autom4te.cache diff --git a/.travis.yml b/.travis.yml index f90af29..c82fdfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,74 +1,137 @@ -# Lua is not officially supported, but an erlang environment will do. -language: erlang +language: c env: global: - - PACKAGE=stdlib - - ROCKSPEC=$PACKAGE-git-1.rockspec - - LUAROCKS_CONFIG=build-aux/luarocks-config.lua - - LUAROCKS_BASE=luarocks-2.2.0 - - LUAROCKS="$LUA $HOME/bin/luarocks" + - _COMPILE="libtool --mode=compile --tag=CC gcc" + - _CFLAGS="-O2 -Wall -DLUA_COMPAT_ALL -DLUA_USE_LINUX" + - _INSTALL="libtool --mode=install install -p" + - _LINK="libtool --mode=link --tag=CC gcc" + - _LIBS="-lm -Wl,-E -ldl -lreadline" + + - prefix=/usr/local + - bindir=$prefix/bin + - incdir=$prefix/include + - libdir=$prefix/lib matrix: - - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1 LUA_SUFFIX=5.1 - - LUA=lua5.2 LUA_INCDIR=/usr/include/lua5.2 LUA_SUFFIX=5.2 - - LUA=luajit-2.0.0-beta9 LUA_INCDIR=/usr/include/luajit-2.0 LUA_SUFFIX=5.1 + - LUA=lua5.3 + - LUA=lua5.2 + - LUA=lua5.1 + - LUA=luajit -# Tool setup. -install: + +before_install: # Put back the links for libyaml, which are missing on recent Travis VMs - test -f /usr/lib/libyaml.so || sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; - - sudo apt-get install help2man - - sudo apt-get install luajit - - sudo apt-get install libluajit-5.1-dev - - sudo apt-get install lua5.1 - - sudo apt-get install liblua5.1-dev - - sudo apt-get install lua5.2 - - sudo apt-get install liblua5.2-dev - - # Install a recent luarocks release locally for everything else. - - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz - - tar zxvpf $LUAROCKS_BASE.tar.gz - # LuaRocks configure --with-lua argument is just a prefix! - - ( cd $LUAROCKS_BASE; - ./configure - --prefix=$HOME --with-lua=/usr --lua-version=$LUA_SUFFIX - --lua-suffix=$LUA_SUFFIX --with-lua-include=$LUA_INCDIR; - make build; - make install; ) - -# Configure and build. + + # Fetch Lua sources. + - cd $TRAVIS_BUILD_DIR + - 'if test lua5.3 = "$LUA"; then + curl http://www.lua.org/work/lua-5.3.0-rc3.tar.gz | tar xz; + cd lua-5.3.0; + fi' + - 'if test lua5.2 = "$LUA"; then + curl http://www.lua.org/ftp/lua-5.2.3.tar.gz | tar xz; + cd lua-5.2.3; + fi' + - 'if test lua5.1 = "$LUA"; then + curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz; + cd lua-5.1.5; + fi' + + # Unpack, compile and install Lua. + - 'if test luajit = "$LUA"; then + curl http://luajit.org/download/LuaJIT-2.0.3.tar.gz | tar xz; + cd LuaJIT-2.0.3; + make && sudo make install; + for header in lua.h luaconf.h lualib.h lauxlib.h luajit.h lua.hpp; do + if test -f /usr/local/include/luajit-2.0/$header; then + sudo ln -s /usr/local/include/luajit-2.0/$header /usr/local/include/$header; + fi; + done; + else + for src in src/*.c; do + test src/lua.c = "$src" || test src/luac.c = "$src" || eval $_COMPILE $_CFLAGS -c $src; + done; + eval $_LINK -o lib$LUA.la -version-info 0:0:0 -rpath $libdir *.lo; + sudo mkdir -p $libdir; + eval sudo $_INSTALL lib$LUA.la $libdir/lib$LUA.la; + + eval $_COMPILE $_CFLAGS -c src/lua.c; + eval $_LINK -static -o $LUA lua.lo lib$LUA.la $_LIBS; + sudo mkdir -p $bindir; + eval sudo $_INSTALL $LUA $bindir/$LUA; + + sudo mkdir -p $incdir; + for header in lua.h luaconf.h lualib.h lauxlib.h lua.hpp; do + if test -f src/$header; then + eval sudo $_INSTALL src/$header $incdir/$header; + fi; + done; + fi' + + # Fetch LuaRocks. + - cd $TRAVIS_BUILD_DIR + - 'git clone https://github.com/keplerproject/luarocks.git luarocks-2.2.0' + - cd luarocks-2.2.0 + - git checkout v2.2.0 + + # Compile and install luarocks. + - if test luajit = "$LUA"; then + ./configure --lua-suffix=jit; + else + ./configure; + fi + - 'make build && sudo make install' + + # Tidy up file droppings. + - cd $TRAVIS_BUILD_DIR + - rm -rf lua-5.3.0 lua-5.2.3 lua-5.1.5 LuaJIT-2.0.3 luarocks-2.2.0 + + +install: + # Use Lua 5.3 compatible rocks, where available. + - 'for rock in ldoc specl""; do + if test -z "$rock"; then break; fi; + if luarocks list | grep "^$rock$" >/dev/null; then continue; fi; + sudo luarocks install --server=http://rocks.moonscript.org/manifests/gvvaughan $rock; + done' + + # Fudge timestamps on release branches. + - 'if test -f configure; then + test -f aclocal.m4 && touch aclocal.m4; + sleep 1; touch Makefile.in; + sleep 1; test -f config.h.in && touch config.h.in; + sleep 1; touch configure; + fi' + + # Build from rockspec. + - export ROCKSPEC=stdlib-41.0.0-1.rockspec + - 'test -f "$ROCKSPEC" || ROCKSPEC=stdlib-git-1.rockspec' + - sudo luarocks make $ROCKSPEC LUA="$LUA" + + # Clean up files created by root + - sudo git clean -dfx + - sudo rm -rf slingshot /tmp/ldoc + + script: - # Initial bootstrap to build luarocks-config.lua, before we've - # installed our rocks. - - ./bootstrap --skip-rock-checks - - ./configure LUA="$LUA" - - make $LUAROCKS_CONFIG - LUA="$LUA" LUA_INCDIR="$LUA_INCDIR" V=1 - || cat $LUAROCKS_CONFIG config.log - - # Set Lua and Shell paths up for local luarocks tree. - # this package depends on will be installed. - - eval `$LUAROCKS path` - - export PATH=`pwd`/luarocks/bin:$PATH - - # Install extra rocks into $LUAROCKS_CONFIG rocks tree. - - $LUAROCKS install lyaml; $LUAROCKS install ldoc; $LUAROCKS install specl; - - # Make git rockspec for this stdlib - - make rockspecs LUAROCKS="$LUAROCKS" V=1 - || { $LUAROCKS path; cat $ROCKSPEC; } - - # The git rockspec will rerun bootstrap, and check any rock versions - # in bootstrap.conf:buildreq this time. - - $LUAROCKS make $ROCKSPEC LUA="$LUA" - - # Run self-tests in the `luarocks make` build tree. - - LUA_PATH=`pwd`'/lib/?.lua;'"${LUA_PATH-;}" - LUA_CPATH=`pwd`'/ext/?.so;'"${LUA_CPATH-;}" - LUA_INIT= LUA_INIT_5_2= - make check V=1 + - test -f configure || ./bootstrap --verbose + - test -f Makefile || ./configure --disable-silent-rules LUA="$LUA" + - make + - make check V=1 + + +# Run sanity checks on CI server, ignoring buggy automakes. +after_success: + - '{ _assign="="; + if grep local-checks-to-skip build-aux/sanity-cfg.mk >/dev/null; then + _assign="+="; + fi; + printf "local-checks-to-skip %s sc_vulnerable_makefile_CVE-2012-3386\n" "$_assign"; + } >> build-aux/sanity-cfg.mk' + - 'make syntax-check || : this will usually fail on the release branch' notifications: slack: aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih diff --git a/NEWS b/NEWS deleted file mode 100644 index d564b9c..0000000 --- a/NEWS +++ /dev/null @@ -1,1135 +0,0 @@ -Stdlib NEWS - User visible changes - -* Noteworthy changes in release ?.? (????-??-??) [?] - -** 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`, - `get`, `diff`, `disj`, `eq`, `neg`, `neq`, `prod`, `quot`, and `sum`), - a new `set` operator, 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: - - table.sort (t, lambda "|a,b| a - 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: - - _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: - - _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. - - 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`, `functional.map` and - `functional.reduce` 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. - - - `functional.reduce` now passes all iterator results to the reduce - function along with the accumlator. This means if you were - previously relying on all but the last result being discarded, you - either need to rewrite your reduce function or else select a - different iterator to compensate. Instead of: - - reduce (function (d, v) return d + v end, 2, ipairs, {4, 6, 8}) - - either, rewrite the reduce function: - - reduce (function (d, i, v) return d + v end, 2, ipairs, {4, 6, 8}) - - or, use the index discarding iterator: - - reduce (function (d, v) return d + v end, 2, ielems, {4, 6, 8}) - - - 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`. - - - `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: - - _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: - - 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: - - local std = require "std".monkey_patch () - - - The global namespace is no longer clobbered by `require "std"`. To - get the old behaviour back: - - 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: - - 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: - - 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: - - 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: - - 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: - - 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: - - 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 - :prototype () method that returns the contents of the - new internal `_type` field. This can be overridden during cloning - with, e.g.: - - local Object = require "std.object" - Prototype = Object { _type = "Prototype", } - - - 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. - - 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.: - - 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: - - 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: - - 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: - - 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: - - 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: - - 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. diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..d910233 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,1174 @@ +# Stdlib NEWS - User visible changes + +## Noteworthy changes in release ?.? (????-??-??) [?] + +### 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 + 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`. + + - `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 + :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", } + ``` + + - 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. diff --git a/bootstrap b/bootstrap index 575ce48..0b70e37 100755 --- a/bootstrap +++ b/bootstrap @@ -5,7 +5,7 @@ # Bootstrap an Autotooled package from checked-out sources. # Written by Gary V. Vaughan, 2010 -# Copyright (C) 2010-2014 Free Software Foundation, Inc. +# Copyright (C) 2010-2015 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -820,30 +820,31 @@ slingshot_require_slingshot_submodule () else $require_slingshot_dotgitmodules - if test -f .gitmodules && test -f "slingshot/src/mkrockspecs.in" - then - : All present and correct. - - else + if test -f .gitmodules; then $require_slingshot_path $require_slingshot_url - trap slingshot_cleanup 1 2 13 15 + if test -f "slingshot/src/mkrockspecs.in"; then + : All present and correct. - shallow= - $GIT clone -h 2>&1 |func_grep_q -- --depth \ - && shallow='--depth 365' + else + trap slingshot_cleanup 1 2 13 15 - func_show_eval "$GIT clone $shallow '$slingshot_url' '$slingshot_path'" \ - slingshot_cleanup + shallow= + $GIT clone -h 2>&1 |func_grep_q -- --depth \ + && shallow='--depth 365' - # FIXME: Solaris /bin/sh will try to execute '-' if any of - # these signals are caught after this. - trap - 1 2 13 15 + func_show_eval "$GIT clone $shallow '$slingshot_url' '$slingshot_path'" \ + slingshot_cleanup + + # FIXME: Solaris /bin/sh will try to execute '-' if any of + # these signals are caught after this. + trap - 1 2 13 15 + fi # Make sure we've checked out the correct revision of slingshot. - func_show_eval "$GIT submodule init" \ - && func_show_eval "$GIT submodule update" \ + func_show_eval "$GIT submodule init -- $slingshot_path" \ + && func_show_eval "$GIT submodule update -- $slingshot_path" \ || func_fatal_error "Unable to update slingshot submodule." fi fi @@ -852,6 +853,49 @@ slingshot_require_slingshot_submodule () } +# require_bootstrap_uptodate +# -------------------------- +# Complain if the version of bootstrap in the build-aux directory differs +# from the one we are running. +require_bootstrap_uptodate=slingshot_require_bootstrap_uptodate +slingshot_require_bootstrap_uptodate () +{ + $debug_cmd + + $require_slingshot_submodule + + _G_slingshot_bootstrap=slingshot/bootstrap + + rm -f $progname.new + + if test -f "$_G_slingshot_bootstrap"; then + if func_cmp_s "$progpath" "$_G_slingshot_bootstrap"; then + func_verbose "bootstrap script up to date" + else + cp -f $_G_slingshot_bootstrap $progname.new + func_warning upgrade "\ +An updated slingshot bootstrap script is ready for you in +'$progname.new'. After you've verified that you want the +changes, you can update with: + mv -f $progname.new $progname + ./$progname + +Or you can disable this check permanently by adding the +following to 'bootstrap.conf': + require_bootstrap_uptodate=:" + fi + else + func_warning upgrade "\ +Your slingshot submodule appears to be damagedi, so I can't tell +whether your bootstrap has gone out of sync. Please check for +and undo any local changes, or revert to the slingshot revision +you were using previously, and rerun this script." + fi + + require_bootstrap_uptodate=: +} + + # slingshot_cleanup # ----------------- # Recursively delete everything at $slingshot_path. @@ -2424,7 +2468,7 @@ test -z "$progpath" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/funclib.sh test extract-trace = "$progname" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/options-parser # Set a version string. -scriptversion=2014-01-04.01; # UTC +scriptversion=2014-12-03.16; # UTC # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -2569,6 +2613,69 @@ func_autoconf_configure () } +# func_tool_version_output CMD [FATAL-ERROR-MSG] +# ---------------------------------------------- +# Attempt to run 'CMD --version', discarding errors. The output can be +# ignored by redirecting stdout, and this function used simply to test +# whether the command exists and exits normally when passed a +# '--version' argument. +# When FATAL-ERROR-MSG is given, then this function will display the +# message and exit if running 'CMD --version' returns a non-zero exit +# status. +func_tool_version_output () +{ + $debug_cmd + + _G_cmd=$1 + _G_fatal_error_msg=$2 + + # Some tools, like 'git2cl' produce thousands of lines of output + # unless stdin is /dev/null - in that case we want to return + # successfully without saving all of that output. Other tools, + # such as 'help2man' exit with a non-zero status when stdin comes + # from /dev/null, so we re-execute without /dev/null if that + # happens. This means that occasionally, the output from both calls + # ends up in the result, but the alternative would be to discard the + # output from one call, and hope the other produces something useful. + { $_G_cmd --version /dev/null + _G_status=$? + + test 0 -ne "$_G_status" && test -n "$_G_fatal_error_msg" \ + && func_fatal_error "$_G_fatal_error_msg" + + (exit $_G_status) +} + + +# func_tool_version_number CMD [FATAL-ERROR-MSG] +# ---------------------------------------------- +# Pass arguments to func_tool_version_output, but set +# $func_tool_version_number_result to the last dot delimited digit string +# on the first line of output. +func_tool_version_number () +{ + $debug_cmd + + _G_verout=`func_tool_version_output "$@"` + _G_status=$? + + # A version number starts with a digit following a space on the first + # line of output from `--version`. + _G_verout=`echo "$_G_verout" |sed 1q` + if test -n "$_G_verout"; then + _G_vernum=`expr "$_G_verout" : '.* \([0-9][^ ]*\)'` + fi + + if test -n "$_G_vernum"; then + printf '%s\n' "$_G_vernum" + else + printf '%s\n' "$_G_verout" + fi + + (exit $_G_status) +} + + # func_find_tool ENVVAR NAMES... # ------------------------------ # Search for a required program. Use the value of ENVVAR, if set, @@ -2586,16 +2693,44 @@ func_find_tool () if test -n "$_G_find_tool_res"; then _G_find_tool_error_prefix="\$$find_tool_envvar: " else + _G_find_tool_res= + _G_bestver= for _G_prog do - if func_tool_version_output $_G_prog >/dev/null; then - _G_find_tool_res=$_G_prog - break - fi + _G_find_tool_save_IFS=$IFS + IFS=: + for _G_dir in $PATH; do + IFS=$_G_find_tool_save_IFS + _G_progpath=$_G_dir/$_G_prog + test -r "$_G_progpath" && { + _G_curver=`func_tool_version_number $_G_progpath` + case $_G_bestver,$_G_curver in + ,) + # first non--version responsive prog sticks! + test -n "$_G_progpath" || _G_find_tool_res=$_G_progpath + ;; + ,*) + # first --version responsive prog beats non--version responsive! + _G_find_tool_res=$_G_progpath + _G_bestver=$_G_curver + ;; + *,*) + # another --version responsive prog must be newer to beat previous one! + test "x$_G_curver" = "x$_G_bestver" \ + || func_lt_ver "$_G_curver" "$_G_bestver" \ + || { + _G_find_tool_res=$_G_progpath + _G_bestver=$_G_curver + } + ;; + esac + } + done + IFS=$_G_find_tool_save_IFS done fi if test -n "$_G_find_tool_res"; then - func_tool_version_output >/dev/null $_G_find_tool_res "\ + func_tool_version_number >/dev/null $_G_find_tool_res "\ ${_G_find_tool_error_prefix}Cannot run '$_G_find_tool_res --version'" # Make sure the result is exported to the environment for children @@ -2610,39 +2745,6 @@ One of these is required: } -# func_tool_version_output CMD [FATAL-ERROR-MSG] -# ---------------------------------------------- -# Attempt to run 'CMD --version', discarding errors. The output can be -# ignored by redirecting stdout, and this function used simply to test -# whether the command exists and exits normally when passed a -# '--version' argument. -# When FATAL-ERROR-MSG is given, then this function will display the -# message and exit if running 'CMD --version' returns a non-zero exit -# status. -func_tool_version_output () -{ - $debug_cmd - - _G_cmd=$1 - _G_fatal_error_msg=$2 - - # Some tools, like 'git2cl' produce thousands of lines of output - # unless stdin is /dev/null - in that case we want to return - # successfully without saving all of that output. Other tools, - # such as 'help2man' exit with a non-zero status when stdin comes - # from /dev/null, so we re-execute without /dev/null if that - # happens. This means that occasionally, the output from both calls - # ends up in the result, but the alternative would be to discard the - # output from one call, and hope the other produces something useful. - { $_G_cmd --version /dev/null - _G_status=$? - - test 0 -ne "$_G_status" && test -n "$_G_fatal_error_msg" \ - && func_fatal_error "$_G_fatal_error_msg" - - (exit $_G_status) -} - ## -------------------- ## ## Resource management. ## @@ -2908,7 +3010,7 @@ test extract-trace = "$progname" && func_main "$@" # End: # Set a version string for *this* script. -scriptversion=2014-01-04.01; # UTC +scriptversion=2014-11-04.13; # UTC ## ------------------- ## @@ -3034,10 +3136,13 @@ func_reconfigure () $require_automake_options - # Automake (without 'foreign' option) requires that README exists. + # Automake (without 'foreign' option) requires that NEWS & README exist. case " $automake_options " in " foreign ") ;; - *) func_ensure_README ;; + *) + func_ensure_NEWS + func_ensure_README + ;; esac # Ensure ChangeLog presence. @@ -3336,6 +3441,7 @@ slingshot_copy_files () $require_slingshot_submodule # Make sure we have the latest mkrockspecs + # (the rebootstrap rule in slingshot/GNUmakefile autoruns). make -C slingshot build-aux/mkrockspecs # Update in-tree links. @@ -3367,6 +3473,65 @@ slingshot_ensure_changelog () func_add_hook func_prep slingshot_ensure_changelog +# slingshot_update_travis_yml +# --------------------------- +# When 'travis.yml.in' is listed in $slingshot_files, complain if +# regenerating '.travis.yml' would change it. +slingshot_update_travis_yml () +{ + $debug_cmd + + $require_git + + _G_travis_yml_in=travis.yml.in + _G_travis_yml_out=.travis.yml + + rm -f "$_G_travis_yml_out.new" + + test true = "$GIT" || { + case " "`echo $slingshot_files`" " in + *" travis.yml.in "*) + # Remove trailing blanks so as not to trip sc_trailing_blank in syntax check + test -f "$_G_travis_yml_in" && { + $slingshot_require_travis_extra_rocks + + eval `grep '^ *PACKAGE=' configure | sed 1q` + eval `grep '^ *VERSION=' configure | sed 1q` + + cat "$_G_travis_yml_in" | + sed 's| *$||' | + sed "s|@EXTRA_ROCKS@|`echo $travis_extra_rocks`|g" | + sed "s|@PACKAGE@|$PACKAGE|g" | + sed "s|@VERSION@|$VERSION|g" + + if test -f .slackid; then + read slackid < .slackid + printf '%s\n' '' 'notifications:' " slack: $slackid" + fi + } > "$_G_travis_yml_out.new" + + if test -f "$_G_travis_yml_out"; then + if func_cmp_s "$_G_travis_yml_out" "$_G_travis_yml_out.new"; then + func_verbose "$_G_travis_yml_out is up to date" + rm -f "$_G_travis_yml_out.new" + else + func_warning upgrade "\ +An updated $_G_travis_yml_out script is ready for you in +'$_G_travis_yml_out.new'. After you've verified that you want +the changes, you can update with: + mv -f $_G_travis_yml_out.new $_G_travis_yml_out" + fi + else + func_verbose "creating '$_G_travis_yml_out'" + mv -f "$_G_travis_yml_out.new" "$_G_travis_yml_out" + fi + ;; + esac + } +} +func_add_hook func_fini slingshot_update_travis_yml + + # slingshot_check_rockstree_path # ------------------------------ # Show a warning at the end of bootstrap if --luarocks-tree was passed @@ -3529,6 +3694,29 @@ EOT } +# func_ensure_NEWS +# ---------------- +# Without AM_INIT_AUTOMAKE([foreign]), automake will not run to +# completion with no NEWS file, even though NEWS.md or NEWS.txt +# is often preferable. +func_ensure_NEWS () +{ + $debug_cmd + + test -f NEWS || { + _G_NEWS= + for _G_news in NEWS.txt NEWS.md NEWS.rst; do + test -f "$_G_news" && break + done + + test -f "$_G_news" && $LN_S $_G_news NEWS + func_verbose "$LN_S $_G_news NEWS" + } + + return 0 +} + + # func_ensure_README # ------------------ # Without AM_INIT_AUTOMAKE([foreign]), automake will not run to @@ -3852,7 +4040,7 @@ func_require_autopoint () # -------------------------- # Complain if the version of bootstrap in the gnulib directory differs # from the one we are running. -require_bootstrap_uptodate=func_require_bootstrap_uptodate + func_require_bootstrap_uptodate () { $debug_cmd @@ -4054,7 +4242,7 @@ func_require_buildreq_patch () # The ugly find invocation is necessary to exit with non-zero # status for old find binaries that don't support -exec fully. if test ! -d "$local_gl_dir" \ - || find "$local_gl_dir" -name *.diff -exec false {} \; ; then : + || find "$local_gl_dir" -name "*.diff" -exec false {} \; ; then : else func_append buildreq 'patch - http://www.gnu.org/s/patch ' @@ -4206,7 +4394,7 @@ func_require_git () $opt_skip_git && GIT=true test true = "$GIT" || { - if test -f .git; then + if test -d .git/.; then ($GIT --version) >/dev/null 2>&1 || GIT=true fi } @@ -4424,8 +4612,8 @@ func_require_gnulib_submodule () fi # Make sure we've checked out the correct revision of gnulib. - func_show_eval "$GIT submodule init" \ - && func_show_eval "$GIT submodule update" \ + func_show_eval "$GIT submodule init -- $gnulib_path" \ + && func_show_eval "$GIT submodule update -- $gnulib_path" \ || func_fatal_error "Unable to update gnulib submodule." fi @@ -4529,6 +4717,9 @@ func_require_libtoolize () func_find_tool LIBTOOLIZE libtoolize glibtoolize } + test -n "$LIBTOOLIZE" || func_fatal_error "\ +Please install GNU Libtool, or 'export LIBTOOLIZE=/path/to/libtoolize'." + func_verbose "export LIBTOOLIZE='$LIBTOOLIZE'" # Make sure the search result is visible to subshells @@ -4766,6 +4957,9 @@ func_require_patch () func_find_tool PATCH gpatch patch } + test -n "$PATCH" || func_fatal_error "\ +Please install GNU Patch, or 'export PATCH=/path/to/gnu/patch'." + func_verbose "export PATCH='$PATCH'" # Make sure the search result is visible to subshells @@ -5257,7 +5451,7 @@ func_check_tool () ;; *) save_IFS=$IFS - IFS=: + IFS=${PATH_SEPARATOR-:} for _G_check_tool_path in $PATH; do IFS=$save_IFS if test -x "$_G_check_tool_path/$1"; then @@ -5403,6 +5597,9 @@ func_update_po_files () # Find sha1sum, named gsha1sum on MacPorts, and shasum on MacOS 10.6+. func_find_tool SHA1SUM sha1sum gsha1sum shasum sha1 + test -n "$SHA1SUM" || func_fatal_error "\ +Please install GNU Coreutils, or 'export SHA1SUM=/path/to/sha1sum'." + _G_langs=`cd $_G_ref_po_dir && echo *.po|$SED 's|\.po||g'` test '*' = "$_G_langs" && _G_langs=x for _G_po in $_G_langs; do diff --git a/bootstrap.conf b/bootstrap.conf index a69e0cd..3ffc2e6 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -48,17 +48,18 @@ slingshot_files=' build-aux/specl.mk build-aux/update-copyright m4/ax_lua.m4 - m4/slingshot.m4 travis.yml.in ' +# Prequisite rocks that need to be installed for travis builds to work. +travis_extra_rocks=' + ldoc + specl +' + # No need to do any gnulib-tooling here. gnulib_tool=true -# The not-synced with gnulib warning is bogus until upstream adopts -# the saner bootstrap script. -require_bootstrap_uptodate=: - # Local variables: # mode: shell-script diff --git a/configure.ac b/configure.ac index 16157da..28288bc 100644 --- a/configure.ac +++ b/configure.ac @@ -36,6 +36,5 @@ AC_PROG_EGREP AC_PROG_SED dnl Generate output files -SS_CONFIG_TRAVIS([ldoc specl]) AC_CONFIG_FILES([Makefile build-aux/config.ld]) AC_OUTPUT diff --git a/local.mk b/local.mk index e1548cf..40f4b2f 100644 --- a/local.mk +++ b/local.mk @@ -29,7 +29,7 @@ LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" ## Bootstrap. ## ## ---------- ## -old_NEWS_hash = b53d7090ac89fb479e5f12aa357d6881 +old_NEWS_hash = d41d8cd98f00b204e9800998ecf8427e update_copyright_env = \ UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ diff --git a/slingshot b/slingshot index 97ab1b5..c8f022a 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 97ab1b5a6fcc53f0e6c5aac156645349fd759bb9 +Subproject commit c8f022a76514f8819535dbd6016b8d3c75fd7e50 From 4334983034654cf5bc1ea0087ccfb5c3b35bb673 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 29 Dec 2014 18:07:15 +0000 Subject: [PATCH 466/703] specs: support Lua 5.3 variants of core error messages. * specs/container_spec.yaml, specs/debug_spec.ymal: Also accept slightly different error message formats from Lua 5.3 core. Signed-off-by: Gary V. Vaughan --- specs/container_spec.yaml | 5 ++++- specs/debug_spec.yaml | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 857537f..8f1c333 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -67,7 +67,10 @@ specify std.container: expect (things.count).to_be (nil) - it does not provide object methods: | things = Bag {} - expect (things:count ()).to_raise "attempt to call method 'count'" + expect (things:count ()).to_raise.any_of { + "attempt to call method 'count'", + "attempt to call a nil value (method 'count'" + } - it does retain module functions: things = Bag { apples = 1, oranges = 3 } expect (Bag.count (things)).to_be (4) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index f0240af..970a56a 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -86,7 +86,10 @@ specify std.debug: expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - it does not define the function with _DEBUG set to true: | script = "_DEBUG = true " .. script - expect (luaproc (script)).to_contain_error ":3: attempt to call global 'fn'" + expect (luaproc (script)).to_contain_error.any_of { + ":3: attempt to call global 'fn'", + ":3: attempt to call a nil value (global 'fn')", + } - it warns on every call with _DEBUG.deprecate unset: script = "_DEBUG = {} " .. script expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" @@ -96,7 +99,10 @@ specify std.debug: expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - it warns on every call with _DEBUG.deprecate set to true: | script = "_DEBUG = { deprecate = true } " .. script - expect (luaproc (script)).to_contain_error ":3: attempt to call global 'fn'" + expect (luaproc (script)).to_contain_error.any_of { + ":3: attempt to call global 'fn'", + ":3: attempt to call a nil value (global 'fn')", + } - describe DEPRECATIONMSG: From 97ac6bcf2dd9f6b054f0184945376954255d5263 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 29 Dec 2014 17:46:45 +0000 Subject: [PATCH 467/703] io: pass io.die message as an error() string. * lib/std/io.lua (warnfmt): New function factored out of warn(). Adjust callers. (die): Pass result of warnfmt to error instead of writing directly to stderr. * specs/io_spec.yaml, specs/optparse_spec.yaml: Adjust specifications accordingly. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/io.lua | 13 ++++++++++--- specs/io_spec.yaml | 32 ++++++++++++++++---------------- specs/optparse_spec.yaml | 11 ++++++----- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/NEWS.md b/NEWS.md index d910233..0a96f08 100644 --- a/NEWS.md +++ b/NEWS.md @@ -297,6 +297,9 @@ - `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 diff --git a/lib/std/io.lua b/lib/std/io.lua index 87230b3..f6d198f 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -102,7 +102,7 @@ local function process_files (fn) end -local function warn (msg, ...) +local function warnfmt (msg, ...) local prefix = "" if (prog or {}).name then prefix = prog.name .. ":" @@ -121,7 +121,12 @@ local function warn (msg, ...) end end if #prefix > 0 then prefix = prefix .. " " end - writelines (io.stderr, prefix .. string.format (msg, ...)) + return prefix .. string.format (msg, ...) +end + + +local function warn (msg, ...) + writelines (io.stderr, warnfmt (msg, ...)) end @@ -164,7 +169,9 @@ M = { -- @param ... additional arguments to plug format string specifiers -- @see warn -- @usage die ("oh noes! (%s)", tostring (obj)) - die = X ("die (string, any?*)", function (...) warn (...); error () end), + die = X ("die (string, any?*)", function (...) + error (warnfmt (...), 0) + end), --- Remove the last dirsep delimited element from a path. -- @function dirname diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index de387fb..0be7e1c 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -81,55 +81,55 @@ specify std.io: - context with bad arguments: badargs.diagnose (f, "std.io.die (string, ?any*)") - - it outputs a message to stderr: - expect (luaproc (script)).to_fail_with "By 'eck!\n" - - it ignores `prog.line` without `prog.file` or `prog.name`: + - 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_with "By 'eck!\n" - - it ignores `opts.line` without `opts.program`: + 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_with "By 'eck!\n" + 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_with "name: By 'eck!\n" + 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_with "name:125: By 'eck!\n" + 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_with "file: By 'eck!\n" + 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_with "file:125: By 'eck!\n" + 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_with "name: By 'eck!\n" + 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_with "name:125: By 'eck!\n" + 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_with "file: By 'eck!\n" + 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_with "file:125: By 'eck!\n" + 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_with "program: By 'eck!\n" + 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_with "program:99: By 'eck!\n" + expect (luaproc (script)).to_fail_while_matching ": program:99: By 'eck!\n" - describe dirname: diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index 2280fee..fcf8a93 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -230,20 +230,21 @@ specify std.optparse: end - it prefers `prog.name` to `opts.program`: | code = [[prog = { file = "file", name = "name" }]] - expect (runscript (code)).to_fail_with "name: By 'eck!\n" + expect (runscript (code)).to_fail_while_matching ": name: By 'eck!\n" - it prefers `prog.file` to `opts.program`: | code = [[prog = { file = "file" }]] - expect (runscript (code)).to_fail_with "file: By 'eck!\n" + expect (runscript (code)).to_fail_while_matching ": file: By 'eck!\n" - it appends `prog.line` if any to `prog.file` over using `opts`: | code = [[ prog = { file = "file", line = 125 }; opts.line = 99]] - expect (runscript (code)).to_fail_with "file:125: By 'eck!\n" + expect (runscript (code)). + to_fail_while_matching ": file:125: By 'eck!\n" - it prefixes `opts.program` if any: | - expect (runscript ("")).to_fail_with "program: By 'eck!\n" + expect (runscript ("")).to_fail_while_matching ": program: By 'eck!\n" - it appends `opts.line` if any, to `opts.program`: | code = [[opts.line = 99]] expect (runscript (code)). - to_fail_with "program:99: By 'eck!\n" + to_fail_while_matching ": program:99: By 'eck!\n" - context with io.warn: - before: | From 3ace4f8923203dd5f96719c0a6df7e091950ad28 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 16:08:23 +0000 Subject: [PATCH 468/703] rockspec: Lua 5.4 and higher not yet supported. * rockspec.conf (dependencies): Add lua < 5.4 constraint. Signed-off-by: Gary V. Vaughan --- rockspec.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rockspec.conf b/rockspec.conf index 3376ac9..397ad26 100644 --- a/rockspec.conf +++ b/rockspec.conf @@ -10,7 +10,7 @@ description: pickling, pretty-printing and command-line option parsing. dependencies: -- lua >= 5.1 +- lua >= 5.1, < 5.4 source: url: git://github.com/lua-stdlib/lua-stdlib.git From a609453a0967424cb4d86ea8e09505f7d90f717f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 16:15:07 +0000 Subject: [PATCH 469/703] maint: update copyrights. * COPYING, README.md, bootstrap.conf, configure.ac, local.mk, specs/optparse_spec.yaml, specs/string_spec.yaml: Add 2015 to copyright statement. Signed-off-by: Gary V. Vaughan --- COPYING | 2 +- README.md | 2 +- bootstrap.conf | 2 +- configure.ac | 2 +- local.mk | 2 +- specs/optparse_spec.yaml | 2 +- specs/string_spec.yaml | 10 +++++----- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/COPYING b/COPYING index 8aecd8e..ec03e04 100644 --- a/COPYING +++ b/COPYING @@ -4,7 +4,7 @@ the terms of the MIT license (the same license as Lua itself), unless noted otherwise in the body of that file. ==================================================================== -Copyright (C) 2002-2014 stdlib authors +Copyright (C) 2002-2015 stdlib authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index 4080ff1..23a551d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ by the [stdlib project][github] This is a collection of Lua libraries for Lua 5.1 and 5.2. The -libraries are copyright by their authors 2000-2014 (see the AUTHORS +libraries are copyright by their authors 2000-2015 (see the AUTHORS file for details), and released under the MIT license (the same license as Lua itself). There is no warranty. diff --git a/bootstrap.conf b/bootstrap.conf index 3ffc2e6..bacf914 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -1,6 +1,6 @@ # bootstrap.conf (Stdlib) version 2014-10-04 # -# Copyright (C) 2013-2014 Gary V. Vaughan +# Copyright (C) 2013-2015 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 # This is free software; see the source for copying conditions. There is NO diff --git a/configure.ac b/configure.ac index 28288bc..fe6dc3d 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl configure.ac dnl -dnl Copyright (c) 2012-2014 Free Software Foundation, Inc. +dnl Copyright (C) 2012-2015 Gary V. Vaughan dnl Written by Gary V. Vaughan, 2012 dnl dnl This program is free software; you can redistribute it and/or modify diff --git a/local.mk b/local.mk index 40f4b2f..e3fdc97 100644 --- a/local.mk +++ b/local.mk @@ -1,6 +1,6 @@ # Local Make rules. # -# Copyright (C) 2013-2014 Gary V. Vaughan +# Copyright (C) 2013-2015 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 # # This program is free software; you can redistribute it and/or modify it diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml index fcf8a93..70f874e 100644 --- a/specs/optparse_spec.yaml +++ b/specs/optparse_spec.yaml @@ -8,7 +8,7 @@ specify std.optparse: help = [[ parseme (stdlib spec) 0α1 - Copyright © 2014 Gary V. Vaughan + Copyright © 2015 Gary V. Vaughan This test program comes with ABSOLUTELY NO WARRANTY. Usage: parseme [] ... diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 6d00cb8..321a134 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -690,7 +690,7 @@ specify std.string: - before: subject = "This is a collection of Lua libraries for Lua 5.1 " .. "and 5.2. The libraries are copyright by their authors 2000" .. - "-2014 (see the AUTHORS file for details), and released und" .. + "-2015 (see the AUTHORS file for details), and released und" .. "er the MIT license (the same license as Lua itself). There" .. " is no warranty." @@ -702,21 +702,21 @@ specify std.string: - 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" .. - "-2014 (see the AUTHORS file for details), and\nreleased un" .. + "-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" .. - "-2014 (see the AUTHORS file for\ndetails), and released un" .. + "-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-2014 (see the\n AUTHORS file for d" .. + "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) @@ -724,7 +724,7 @@ specify std.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-2014 (see\n the AUTHORS file for details), and rel" .. + "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: From 0b0c326edd69398825b54e5eb6a12d1da90eccff Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 16:18:24 +0000 Subject: [PATCH 470/703] maint: add 5.3 to compatibility statement. * README.md: Add 5.3 to compatibility statement. * specs/std_spec.yaml (std.version): Update accordingly. Signed-off-by: Gary V. Vaughan --- README.md | 2 +- specs/std_spec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23a551d..d5a82a5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ by the [stdlib project][github] [![Stories in Ready](https://badge.waffle.io/lua-stdlib/lua-stdlib.png?label=ready&title=Ready)](https://waffle.io/lua-stdlib/lua-stdlib) -This is a collection of Lua libraries for Lua 5.1 and 5.2. The +This is a collection of Lua libraries for Lua 5.1, 5.2 and 5.3. The libraries are copyright by their authors 2000-2015 (see the AUTHORS file for details), and released under the MIT license (the same license as Lua itself). There is no warranty. diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 67d90d2..ed7ab5c 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -436,7 +436,7 @@ specify std: - before: std = require "std" ver = std.version - std.version = "standard library for Lua 5.2 / 41" + std.version = "standard library for Lua 5.3 / 41.0.0" - after: std.version = ver - it diagnoses module too old: From 9c8f02bd27d4915ceefc3a739f38fc503f668be3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 16:21:43 +0000 Subject: [PATCH 471/703] configury: add ansicolors to travis_extra_rocks. * bootstrap.conf (buildreq): Bump Specl requirement to 14.1.0. (travis_extra_rocks): Add ansicolors for color specl output. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- bootstrap.conf | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c82fdfb..f514a51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -92,7 +92,7 @@ before_install: install: # Use Lua 5.3 compatible rocks, where available. - - 'for rock in ldoc specl""; do + - 'for rock in ansicolors ldoc specl""; do if test -z "$rock"; then break; fi; if luarocks list | grep "^$rock$" >/dev/null; then continue; fi; sudo luarocks install --server=http://rocks.moonscript.org/manifests/gvvaughan $rock; diff --git a/bootstrap.conf b/bootstrap.conf index bacf914..ae8a642 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -1,4 +1,4 @@ -# bootstrap.conf (Stdlib) version 2014-10-04 +# bootstrap.conf (Stdlib) version 2015-01-03 # # Copyright (C) 2013-2015 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 @@ -31,7 +31,7 @@ buildreq=' git - http://git-scm.com ldoc 1.4.2 http://rocks.moonscript.org/manifests/steved/ldoc-1.4.2-1.rockspec - specl 14 http://rocks.moonscript.org/manifests/gvvaughan/specl-14-1.rockspec + specl 14.1.0 http://rocks.moonscript.org/manifests/gvvaughan/specl-14.1.0-1.rockspec ' # List of slingshot files to link into stdlib tree before autotooling. @@ -53,6 +53,7 @@ slingshot_files=' # Prequisite rocks that need to be installed for travis builds to work. travis_extra_rocks=' + ansicolors ldoc specl ' From 3ab9a401691bd9fb190421eff2167a6f50f54760 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 16:24:46 +0000 Subject: [PATCH 472/703] maint: commit git rockspec to master branch. * .gitignore: Allow git rockspecs. * stdlib-git-1.rockspec: New file. Signed-off-by: Gary V. Vaughan --- .gitignore | 3 ++- stdlib-git-1.rockspec | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 stdlib-git-1.rockspec diff --git a/.gitignore b/.gitignore index 697e22f..c5299c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .DS_Store -/*.rockspec /.autom4te.cfg /.gitmodules /ChangeLog @@ -29,4 +28,6 @@ /m4/ax_lua.m4 /m4/slingshot.m4 /stdlib-*.tar.gz +/stdlib-*.rockspec +!/stdlib-git-*.rockspec /travis.yml.in diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec new file mode 100644 index 0000000..e5ff2df --- /dev/null +++ b/stdlib-git-1.rockspec @@ -0,0 +1,21 @@ +package = "stdlib" +version = "git-1" +description = { + detailed = "stdlib is a library of modules for common programming tasks, including list, table and functional operations, objects, pickling, pretty-printing and command-line option parsing.", + homepage = "http://lua-stdlib.github.io/lua-stdlib", + license = "MIT/X11", + summary = "General Lua Libraries", +} +source = { + url = "git://github.com/lua-stdlib/lua-stdlib.git", +} +dependencies = { + "lua >= 5.1, < 5.4", +} +external_dependencies = nil +build = { + build_command = "./bootstrap && ./configure LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' --prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' --datarootdir='$(PREFIX)' && make clean all", + copy_directories = {}, + install_command = "make install luadir='$(LUADIR)' luaexecdir='$(LIBDIR)'", + type = "command", +} From 1719e2bf74ddf295783f25d1056c0556010da9b0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 17:29:40 +0000 Subject: [PATCH 473/703] std: revert std.ipairs to return all contiguous key pairs 1..n. Now that Lua 5.3 returned to an ipairs implementation that returns contiguous key-value pairs from 1..n, where n is the last non-nil integer key, stdlib can do the same for simpler compliance with all supported Lua versions. * lib/std/base.lua (ipairs): Simplify accordingly. * lib/std.lua.in (ipairs): Update LDocs accordingly. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 27 ++++++++------------------- lib/std.lua.in | 18 +++++++++++------- lib/std/base.lua | 19 +++---------------- 3 files changed, 22 insertions(+), 42 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0a96f08..9f6a538 100644 --- a/NEWS.md +++ b/NEWS.md @@ -93,29 +93,18 @@ in specific type modules: including `std.assert`, `std.eval`, and `std.tostring`. See LDocs for details. - - New `std.ipairs` function that respects `__len` metamethod, while always - iterating from index 1 through #t. This makes it easy to add metamethods - to a table that don't stop at the first `nil` value, among other things: - - ```lua - function maxn (t) - return math.max ( - unpack (filter (lambda '|e|type(e)=="number"', pairs, t)) - ) - end - - function processallargs (x, ...) - for i, v in ipairs (setmetatable ({x, ...}, { __len = maxn })) do - process (i, v) - end - end - ``` + - 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 cleanly, - while respecting `__len` and `__pairs` metamethods respectively. + - 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 array part of any table, while respecting `__len`. diff --git a/lib/std.lua.in b/lib/std.lua.in index c12f727..f6ad356 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -138,11 +138,12 @@ M = { -- for v in std.ielems {"a", "b", "c"} do process (v) end ielems = X ("ielems (table)", base.ielems), - --- An iterator over elements `1..#t`, respecting `__len` metamethod. + --- An iterator over elements of a sequence, until the first `nil` value. -- - -- Unlike Lua 5.1, which ignores `__len`, and Lua 5.2, which looks for - -- and uses `__ipairs`, this iterator always iterates over elements with - -- integer keys starting at 1, up to and including `#t`. + -- Like Lua 5.1 and 5.3, but unlike Lua 5.2 (which looks for and uses the + -- `__ipairs` metamethod), this iterator returns successive key-value + -- pairs with integer keys starting at 1, up to the first `nil` valued + -- pair. -- @function ipairs -- @tparam table t a table -- @treturn function iterator function @@ -151,10 +152,13 @@ M = { -- @see ielems -- @see pairs -- @usage - -- -- don't stop at first nil + -- -- length of sequence -- args = {"first", "second", nil, "last"} - -- setmetatable (args, { __len = std.functional.lambda "=4" }) - -- for i, v in std.ipairs (args) do process (i, v) end + -- --> 1=first + -- --> 2=second + -- for i, v in std.ipairs (args) do + -- print (string.format ("%d=%s", i, v)) + -- end ipairs = X ("ipairs (table)", base.ipairs), --- Return a new table with element order reversed. diff --git a/lib/std/base.lua b/lib/std/base.lua index 9c068cb..899852a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -74,12 +74,12 @@ local function len (t) end +-- Iterate over keys 1..n, where n is the key before the first nil +-- valued ordinal key (like Lua 5.3). local function ipairs (l) - local lenl = len (l) - return function (l, n) n = n + 1 - if n <= lenl then + if l[n] ~= nil then return n, l[n] end end, l, 0 @@ -174,19 +174,6 @@ local function eval (s) end --- Iterate over keys 1..#l, like Lua 5.3. -local function ipairs (l) - local tlen = len (l) - - return function (l, n) - n = n + 1 - if n <= tlen then - return n, l[n] - end - end, l, 0 -end - - local function ielems (l) return wrapiterator (ipairs, l) end From 7fc69fa0ee3ec9ca173f19c57552207764ba1917 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 17:33:25 +0000 Subject: [PATCH 474/703] std: make ripairs respect contiguous integer keys like ipairs. * lib/std/base.lua (ripairs): Start from lowest contiguous integer key with a nil value, and iterate backwards to 1. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 ++- lib/std/base.lua | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9f6a538..e33c9a4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -121,7 +121,8 @@ meet version numbers passed. - New `std.ripairs` function for returning index & value pairs in - reverse order, while respecting `__len`. + 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. diff --git a/lib/std/base.lua b/lib/std/base.lua index 899852a..71e5c80 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -319,12 +319,17 @@ 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, len (t) + 1 + end, t, oob end From 56fe40469fad9bb16b4c62935fa1eceff14bd368 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 17:40:02 +0000 Subject: [PATCH 475/703] std: ireverse only operates on the proper sequence part. * lib/std/base.lua (ireverse): Ignore any holes in the argument table, returning only the reversed proper sequence part. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++-- lib/std/base.lua | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index e33c9a4..e0c45cf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -106,8 +106,8 @@ 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 array part of any - table, while respecting `__len`. + - 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. diff --git a/lib/std/base.lua b/lib/std/base.lua index 71e5c80..2b4efd3 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -200,10 +200,15 @@ local function invert (t) end --- Be careful not to compact holes from `t` when reversing. +-- Be careful to reverse only the valid sequence part of a table. local function ireverse (t) - local r, tlen = {}, len (t) - for i = 1, tlen do r[tlen - i + 1] = t[i] end + local oob = 1 + while t[oob] ~= nil do + oob = oob + 1 + end + + local r = {} + for i = 1, oob - 1 do r[oob - i] = t[i] end return r end From 12d47dd74f14dec0c9d1086bfb183d0d7b22a680 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 18:33:28 +0000 Subject: [PATCH 476/703] maint: don't use math.pow, which some Lua 5.3 builds don't support. When compiled without -DLUA_COMPAT_5_2, Lua 5.3 removes a good chunk of the math library, including math.pow. * lib/std.lua.in (eval): Use math.min in LDocs usage. * specs/functional_spec.yaml (bind, curry, eval): Instead of math.pow, use math.min where sensible, otherwise std.operator.pow. * specs/operator_spec.yaml (pow): Compare with result calculated using ^ infix operator. * specs/std_spec.yaml (eval): Use math.min over math.pow. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 2 +- specs/functional_spec.yaml | 16 ++++++++++------ specs/operator_spec.yaml | 2 +- specs/std_spec.yaml | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index f6ad356..3f12f47 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -122,7 +122,7 @@ M = { -- @function eval -- @string s string of Lua code -- @return result of evaluating `s` - -- @usage std.eval "math.pow (2, 10)" + -- @usage std.eval "math.min (2, 10)" eval = X ("eval (string)", base.eval), --- An iterator over the integer keyed elements of a sequence. diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index e54624d..f4b8a27 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -31,6 +31,8 @@ specify std.functional: - describe bind: - before: + op = require "std.operator" + f = M.bind - it writes an argument passing deprecation warning: @@ -54,11 +56,11 @@ specify std.functional: div = function (a, b) return a / b end expect (f (div, {100}) (25)).to_be (4) - it supports out of order extra arguments: - expect (f (math.pow, {[2] = 3}) (2)).to_be (8) + expect (f (op.pow, {[2] = 3}) (2)).to_be (8) - it supports the legacy api: expect (f (math.min) (2, 3, 4)).to_be (2) expect (f (math.min, 1, 0) (2, 3, 4)).to_be (0) - expect (f (math.pow, nil, 3) (2)).to_be (8) + expect (f (op.pow, nil, 3) (2)).to_be (8) - describe callable: @@ -183,6 +185,8 @@ specify std.functional: - describe curry: - before: + op = require "std.operator" + f = M.curry - context with bad arguments: @@ -197,9 +201,9 @@ specify std.functional: - it evaluates intermediate arguments one at a time: expect (f (math.min, 3) (2) (3) (4)).to_equal (2) - it returns a curried function that can be partially applied: - bin = f (math.pow, 2) (2) - expect (bin (2)).to_be (math.pow (2, 2)) - expect (bin (10)).to_be (math.pow (2, 10)) + bin = f (op.pow, 2) (2) + expect (bin (2)).to_be (op.pow (2, 2)) + expect (bin (10)).to_be (op.pow (2, 10)) - describe eval: @@ -216,7 +220,7 @@ specify std.functional: # Some internal error when eval tries to call uncompilable "=" code. expect (f "=").to_raise () - it evaluates a string of lua code: - expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) + expect (f "math.min (2, 10)").to_be (math.min (2, 10)) - describe filter: diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index 505d041..1b66084 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -88,7 +88,7 @@ specify std.operator: f = M.pow - it returns the power of its arguments: - expect (f (99, 2)).to_be (math.pow (99, 2)) + expect (f (99, 2)).to_be (99 ^ 2) - describe conj: - before: diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index ed7ab5c..f1d428d 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -203,7 +203,7 @@ specify std: # Some internal error when eval tries to call uncompilable "=" code. expect (f "=").to_raise () - it evaluates a string of lua code: - expect (f "math.pow (2, 10)").to_be (math.pow (2, 10)) + expect (f "math.min (2, 10)").to_be (math.min (2, 10)) - describe getmetamethod: From 17a2f0f87d503a64c78d17077a7e6f304b937ac9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 18:39:14 +0000 Subject: [PATCH 477/703] Release version 41.0.0 * NEWS.md: Record release date. --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index e0c45cf..7284b9d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # Stdlib NEWS - User visible changes -## Noteworthy changes in release ?.? (????-??-??) [?] +## Noteworthy changes in release 41.0.0 (2015-01-03) [beta] ### New features From 89083ca912fadc1acd1e5d33a0a96ff33038dad7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Jan 2015 18:39:55 +0000 Subject: [PATCH 478/703] maint: post-release administrivia. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.prev-version b/.prev-version index 425151f..6e90c74 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -40 +41.0.0 diff --git a/NEWS.md b/NEWS.md index 7284b9d..2d4ba05 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # Stdlib NEWS - User visible changes +## Noteworthy changes in release ?.? (????-??-??) [?] + + ## Noteworthy changes in release 41.0.0 (2015-01-03) [beta] ### New features From 8ab6fa9aa681beb73b4ded7e4a4b63e00f508826 Mon Sep 17 00:00:00 2001 From: Gergely Risko Date: Wed, 14 Jan 2015 18:10:50 +0000 Subject: [PATCH 479/703] base: remove spurious case reference from base module. * lib/std/base.lua: Remove spurious reference to case from export table. That function has long since moved to std.functional. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 2b4efd3..b587ca3 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -399,7 +399,6 @@ return { -- std.lua -- assert = assert, - case = case, eval = eval, elems = elems, ielems = ielems, From 1a4e49a53b889b82669a9f12972eb4d149e6dbcd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 14 Jan 2015 19:39:18 +0000 Subject: [PATCH 480/703] slingshot: sync with upstream for Lua 5.3.0 final support. * slingshot: Sync with upstream. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- slingshot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f514a51..3cfa195 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ before_install: # Fetch Lua sources. - cd $TRAVIS_BUILD_DIR - 'if test lua5.3 = "$LUA"; then - curl http://www.lua.org/work/lua-5.3.0-rc3.tar.gz | tar xz; + curl http://www.lua.org/work/lua-5.3.0.tar.gz | tar xz; cd lua-5.3.0; fi' - 'if test lua5.2 = "$LUA"; then diff --git a/slingshot b/slingshot index c8f022a..46630b0 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit c8f022a76514f8819535dbd6016b8d3c75fd7e50 +Subproject commit 46630b09e6dc79717e7af0d00e74ffb0e5e980fa From d571ed1adc29b91a5645eda74968f78ddf5931b6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 14 Jan 2015 20:38:06 +0000 Subject: [PATCH 481/703] slingshot: sync with upstream for working Lua 5.3.0 final support. * slingshot: Sync with upstream. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- slingshot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3cfa195..347a624 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ before_install: # Fetch Lua sources. - cd $TRAVIS_BUILD_DIR - 'if test lua5.3 = "$LUA"; then - curl http://www.lua.org/work/lua-5.3.0.tar.gz | tar xz; + curl http://www.lua.org/ftp/lua-5.3.0.tar.gz | tar xz; cd lua-5.3.0; fi' - 'if test lua5.2 = "$LUA"; then diff --git a/slingshot b/slingshot index 46630b0..c4015aa 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 46630b09e6dc79717e7af0d00e74ffb0e5e980fa +Subproject commit c4015aa6fdf174f8e6517b7213b3927224dbf952 From 99a5550edf0117d62ba6e670fc789f7330fdcd46 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 19 Jan 2015 22:13:23 +0000 Subject: [PATCH 482/703] std: barrel and monkey_patch return module table. Closes #93 In order for things like `std = require "std".barrel ()` to work as documented, these methods must return their parent module table, i.e. in this case, the `std` table. * specs/io_spec.yaml (monkey_patch): Specify correct behaviour. * specs/math_spec.yaml (monkey_patch): Likewise. * specs/string_spec.yaml (monkey_patch): Likewise. * specs/table_spec.yaml (money_patch): Likewise. * specs/std_spec.yaml (barrel, monkey_patch): Likewise. * lib/std.lua.in (barrel, monkey_patch): Return parent module able. * lib/std/io.lua, lib/std/math.lua, lib/std/string.lua, lib/std/table.lua (monkey_patch): Likewise. * NEWS.md (Bug fixes): Update. Reported by Reuben Thomas Signed-off-by: Gary V. Vaughan --- NEWS.md | 5 +++++ lib/std.lua.in | 3 ++- lib/std/io.lua | 2 +- lib/std/math.lua | 2 +- lib/std/string.lua | 2 +- lib/std/table.lua | 2 +- specs/io_spec.yaml | 8 +++++--- specs/math_spec.yaml | 8 +++++--- specs/std_spec.yaml | 22 ++++++++++++++++++++++ specs/string_spec.yaml | 8 +++++--- specs/table_spec.yaml | 8 +++++--- 11 files changed, 53 insertions(+), 17 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2d4ba05..036659a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,11 @@ ## Noteworthy changes in release ?.? (????-??-??) [?] +### Bug fixes + + - `std.barrel` and the various `monkey_patch` functions now return + their parent module table as documented. + ## Noteworthy changes in release 41.0.0 (2015-01-03) [beta] diff --git a/lib/std.lua.in b/lib/std.lua.in index 3f12f47..38807e2 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -29,7 +29,8 @@ local M, monkeys local function monkey_patch (namespace) - return base.copy (namespace or _G, monkeys) + base.copy (namespace or _G, monkeys) + return M end diff --git a/lib/std/io.lua b/lib/std/io.lua index f6d198f..e0a8fde 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -82,7 +82,7 @@ local function monkey_patch (namespace) setmetatable (namespace.io.stdin, mt) end - return namespace.io + return M end diff --git a/lib/std/math.lua b/lib/std/math.lua index b197c21..a441a4c 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -31,7 +31,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G namespace.math = base.copy (namespace.math or {}, M) - return namespace.math + return M end diff --git a/lib/std/string.lua b/lib/std/string.lua index ec5aab3..ba9cdea 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -81,7 +81,7 @@ local function monkey_patch (namespace) string_metatable.__concat = M.__concat string_metatable.__index = M.__index - return namespace.string + return M end diff --git a/lib/std/table.lua b/lib/std/table.lua index cdb32fd..30dbd70 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -162,7 +162,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G namespace.table = base.copy (namespace.table or {}, monkeys) - return namespace.table + return M end diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 0be7e1c..5b7c2dc 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -153,9 +153,11 @@ specify std.io: - context with bad arguments: badargs.diagnose (f, "std.io.monkey_patch (?table)") - - it returns the monkey_patched io entry from namespace: - namespace = {} - expect (f (namespace)).to_be (namespace.io) + # Ideally, `.to_be (M)`, except that M is cloned from a nested context + # by Specl to prevent us from affecting any other examples, thus the + # address is different by now. + - it returns std.io module table: + expect (f {}).to_equal (M) - it injects std.io apis into the given namespace: namespace = {} f (namespace) diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 86e58e5..f198877 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -59,9 +59,11 @@ specify std.math: - context with bad arguments: badargs.diagnose (f, "std.math.monkey_patch (?table)") - - it returns the monkey_patched math entry namespace: - namespace = {} - expect (f (namespace)).to_be (namespace.math) + # Ideally, `.to_be (M)`, except that M is cloned from a nested context + # by Specl to prevent us from affecting any other examples, thus the + # address is different by now. + - it returns std.math module table: + expect (f {}).to_equal (M) - it injects std.math apis into the given namespace: namespace = {} f (namespace) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index f1d428d..d119ad7 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -98,6 +98,11 @@ specify std: - context with bad arguments: badargs.diagnose (f, "std.barrel (?table)") + # Ideally, `.to_be (M)`, except that M is cloned from a nested context + # by Specl to prevent us from affecting any other examples, thus the + # address is different by now. + - it returns std module table: + expect (f (namespace)).to_equal (M) - it installs std monkey patches: for _, api in ipairs (exported_apis) do if type (M[api]) == "function" and @@ -167,6 +172,18 @@ specify std: } do expect (namespace[api]).to_be (fn) end + - 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.strbuf + expect (lazy).to_be (require "std.strbuf") + - it loads submodule functions on demand: + expect (M.object.prototype (M.strbuf {"Lazy"})). + to_be "StrBuf" + - describe elems: - before: @@ -338,6 +355,11 @@ specify std: - context with bad arguments: badargs.diagnose (f, "std.monkey_patch (?table)") + # Ideally, `.to_be (M)`, except that M is cloned from a nested context + # by Specl to prevent us from affecting any other examples, thus the + # address is different by now. + - it returns std module table: + expect (f (t)).to_equal (M) - it installs std module functions: for _, v in ipairs (exported_apis) do if type (M[v]) == "function" and v ~= "barrel" and v ~= "monkey_patch" then diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 321a134..00fb422 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -275,9 +275,11 @@ specify std.string: - context with bad arguments: badargs.diagnose (f, "std.string.monkey_patch (?table)") - - it returns the monkey_patched namespace: - namespace = {} - expect (f (namespace)).to_be (namespace.string) + # Ideally, `.to_be (M)`, except that M is cloned from a nested context + # by Specl to prevent us from affecting any other examples, thus the + # address is different by now. + - it returns std.string module table: + expect (f {}).to_equal (M) - it injects std.string apis into given namespace: namespace = {} f (namespace) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 4ed1003..c6812c3 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -482,9 +482,11 @@ specify std.table: - context with bad arguments: badargs.diagnose (f, "std.table.monkey_patch (?table)") - - it returns the monkey_patched table entry from namespace: - namespace = {} - expect (f (namespace)).to_be (namespace.table) + # Ideally, `.to_be (M)`, except that M is cloned from a nested context + # by Specl to prevent us from affecting any other examples, thus the + # address is different by now. + - it returns std.table module table: + expect (f {}).to_equal (M) - it injects std.table apis into given namespace: namespace = {} f (namespace) From f09dece6697042b6607f894deb64642acf9919d5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 25 Jan 2015 15:17:53 +0000 Subject: [PATCH 483/703] maint: support strict mode. Close #92 * lib/std/base.lua (loadstring): Use rawget to fetch loadstring from _G, to bypass strict mode checks on Lua > 5.2 where _G.loadstring is nil. * lib/std/debug.lua (getfenv): Likewise, for Lua > 5.2 where there is no _G.fgetenv. * lib/std/debug_init/init.lua (_DEBUG): Likewise, for cases where caller did not preload _G._DEBUG. * NEWS.md: Updated. Reported by Gergely Risko Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/base.lua | 2 +- lib/std/debug.lua | 2 +- lib/std/debug_init/init.lua | 11 +++++++---- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 036659a..8cc749f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,9 @@ - `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. + ## Noteworthy changes in release 41.0.0 (2015-01-03) [beta] diff --git a/lib/std/base.lua b/lib/std/base.lua index b587ca3..d8c7dcb 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -24,7 +24,7 @@ local dirsep = string.match (package.config, "^(%S+)\n") -local loadstring = loadstring or load +local loadstring = rawget (_G, "loadstring") or load local unpack = table.unpack or unpack diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 7995839..e68d8e2 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -108,7 +108,7 @@ end --- Extend `debug.getfenv` to unwrap functables correctly. -- @tparam int|function|functable fn target function, or stack level -- @treturn table environment of *fn* -local getfenv = getfenv or function (fn) +local getfenv = rawget (_G, "getfenv") or function (fn) -- Unwrap functable: if type (fn) == "table" then fn = fn.call or (getmetatable (fn) or {}).__call diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index 17bd557..ccdcb69 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -1,12 +1,15 @@ -- Debugging is on by default local M = {} +-- Use rawget to satisfy std.strict. +local _DEBUG = rawget (_G, "_DEBUG") + -- User specified fields. -if type (_G._DEBUG) == "table" then - M._DEBUG = _G._DEBUG +if type (_DEBUG) == "table" then + M._DEBUG = _DEBUG -- Turn everything off. -elseif _G._DEBUG == false then +elseif _DEBUG == false then M._DEBUG = { argcheck = false, call = false, @@ -15,7 +18,7 @@ elseif _G._DEBUG == false then } -- Turn everything on (except _DEBUG.call must be set explicitly). -elseif _G._DEBUG == true then +elseif _DEBUG == true then M._DEBUG = { argcheck = true, call = false, From 3936bbaab45c8028558fb0b9316ef073d3adc13f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 25 Jan 2015 17:30:21 +0000 Subject: [PATCH 484/703] refactor: rearrange strbuf.lua idiomatically. * lib/std/strbuf.lua: Rearrange declarations idiomatically. Signed-off-by: Gary V. Vaughan --- lib/std/strbuf.lua | 64 +++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 937f616..79d186d 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -15,12 +15,45 @@ local base = require "std.base" local Object = require "std.object" {} +local M, StrBuf + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + local function X (decl, fn) return require "std.debug".argscheck ("std.strbuf." .. decl, fn) end +M = { + --- Add a string to a buffer. + -- @static + -- @function concat + -- @string s string to add + -- @treturn StrBuf modified buffer + -- @usage + -- buf = concat (buf, "append this") + concat = X ("concat (StrBuf, string)", base.insert), + + --- Convert a buffer to a string. + -- @static + -- @function tostring + -- @treturn string stringified `buf` + -- @usage + -- string = buf:tostring () + tostring = X ("tostring (StrBuf)", table.concat), +} + + + +--[[ ================== ]]-- +--[[ Type Declarations. ]]-- +--[[ ================== ]]-- + + --- StrBuf prototype object. -- -- Set also inherits all the fields and methods from @@ -35,9 +68,11 @@ end -- buf = buf .. "append to buffer" -- print (buf) -- implicit `tostring` concatenates everything -- os.exit (0) -return Object { +StrBuf = Object { _type = "StrBuf", + __index = M, + --- Support concatenation to StrBuf objects. -- @function __concat -- @tparam StrBuf buffer object @@ -46,7 +81,7 @@ return Object { -- @see concat -- @usage -- buf = buf .. str - __concat = X ("__concat (StrBuf, string)", base.insert), + __concat = base.insert, --- Support fast conversion to Lua string. -- @function __tostring @@ -55,25 +90,8 @@ return Object { -- @see tostring -- @usage -- str = tostring (buf) - __tostring = X ("__tostring (StrBuf)", table.concat), - - - __index = { - --- Add a string to a buffer. - -- @static - -- @function concat - -- @string s string to add - -- @treturn StrBuf modified buffer - -- @usage - -- buf = concat (buf, "append this") - concat = X ("concat (StrBuf, string)", base.insert), - - --- Convert a buffer to a string. - -- @static - -- @function tostring - -- @treturn string stringified `buf` - -- @usage - -- string = buf:tostring () - tostring = X ("tostring (StrBuf)", table.concat), - }, + __tostring = table.concat, } + + +return StrBuf From 4a2a58a3f769eb569d5df61dc7a84a7308b725a8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 25 Jan 2015 17:47:39 +0000 Subject: [PATCH 485/703] strbuf: deprecate strbuf.tostring. * specs/strbuf_spec.yaml: Specify deprecation messages from strbuf.tostring. * lib/std/strbuf.lua (StrBuf.tostring): Deprecate. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 6 ++++++ lib/std/strbuf.lua | 23 ++++++++++++++--------- specs/strbuf_spec.yaml | 21 +++++++++++++++------ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8cc749f..5e7770e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,12 @@ ## Noteworthy changes in release ?.? (????-??-??) [?] +### 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 diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 79d186d..4ebf19e 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -12,6 +12,7 @@ ]] local base = require "std.base" +local debug = require "std.debug" local Object = require "std.object" {} @@ -24,7 +25,7 @@ local M, StrBuf local function X (decl, fn) - return require "std.debug".argscheck ("std.strbuf." .. decl, fn) + return debug.argscheck ("std.strbuf." .. decl, fn) end @@ -37,18 +38,22 @@ M = { -- @usage -- buf = concat (buf, "append this") concat = X ("concat (StrBuf, string)", base.insert), - - --- Convert a buffer to a string. - -- @static - -- @function tostring - -- @treturn string stringified `buf` - -- @usage - -- string = buf:tostring () - tostring = X ("tostring (StrBuf)", table.concat), } +--[[ ============= ]]-- +--[[ Deprecations. ]]-- +--[[ ============= ]]-- + + +local DEPRECATED = debug.DEPRECATED + +M.tostring = DEPRECATED ("41.1", "std.strbuf.tostring", + "use 'tostring (strbuf)' instead", + X ("tostring (StrBuf)", table.concat)) + + --[[ ================== ]]-- --[[ Type Declarations. ]]-- --[[ ================== ]]-- diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index 6cbf72e..e51c92c 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -48,12 +48,21 @@ specify std.strbuf: - describe tostring: - - it can be called from strbuf module: - expect (StrBuf.tostring (b)).to_be "foobar" - - it can be called as a strbuf object method: - expect (b:tostring ()).to_be "foobar" - - it can be called as a strbuf metabethod: - expect (tostring (b)).to_be "foobar" + - context as a module function: + - before: + f = StrBuf.tostring + + - it writes a deprecation warning: + setdebug { deprecate = "nil" } + expect (capture (f, {b})).to_contain_error "was deprecated" + setdebug { deprecate = false } + expect (capture (f, {b})).not_to_contain_error "was deprecated" + - it returns buffered string: + expect (b:tostring ()).to_be "foobar" + + - context as a metamethod: + - it returns buffered string: + expect (tostring (b)).to_be "foobar" - describe concat: From 5aa234a255272ebb84f1f3c00d65b0b26a8ba57f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 25 Jan 2015 18:14:35 +0000 Subject: [PATCH 486/703] strbuf: allow concatenation of StrBuf objects. * specs/strbuf_spec.yaml: Specify new behaviours. * lib/std/strbuf.lua (concat): New function, handles concatenation of StrBuf objects as well as strings. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 7 ++++++ lib/std/strbuf.lua | 22 ++++++++++++++++--- specs/strbuf_spec.yaml | 50 ++++++++++++++++++++++++++++-------------- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5e7770e..3cdc63f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,13 @@ ## Noteworthy changes in release ?.? (????-??-??) [?] +### New features + + - `std.strbuf` can append both strings and other StrBuf instances: + + local a, b = StrBuf { "foo", "bar" }, StrBuf { "baz", "quux" } + a = a .. b --> "foobarbazquux" + ### Deprecations - `std.strbuf.tostring` has been deprecated in favour of `tostring`. diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 4ebf19e..7aa66b7 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -16,9 +16,25 @@ local debug = require "std.debug" local Object = require "std.object" {} +local insert, prototype = base.insert, base.prototype + local M, StrBuf +local function concat (self, x) + if type (x) == "string" then + insert (self, x) + else + assert (prototype (x) == "StrBuf") + for _, v in ipairs (x) do + insert (self, v) + end + end + return self +end + + + --[[ ================= ]]-- --[[ Public Interface. ]]-- --[[ ================= ]]-- @@ -33,11 +49,11 @@ M = { --- Add a string to a buffer. -- @static -- @function concat - -- @string s string to add + -- @tparam string|StrBuf x string or StrBuf to add -- @treturn StrBuf modified buffer -- @usage -- buf = concat (buf, "append this") - concat = X ("concat (StrBuf, string)", base.insert), + concat = X ("concat (StrBuf, string|StrBuf)", concat), } @@ -86,7 +102,7 @@ StrBuf = Object { -- @see concat -- @usage -- buf = buf .. str - __concat = base.insert, + __concat = concat, --- Support fast conversion to Lua string. -- @function __tostring diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index e51c92c..bdf9ceb 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -49,14 +49,13 @@ specify std.strbuf: - describe tostring: - context as a module function: - - before: - f = StrBuf.tostring - - it writes a deprecation warning: setdebug { deprecate = "nil" } - expect (capture (f, {b})).to_contain_error "was deprecated" + expect (capture (StrBuf.tostring, {b})). + to_contain_error "was deprecated" setdebug { deprecate = false } - expect (capture (f, {b})).not_to_contain_error "was deprecated" + expect (capture (StrBuf.tostring, {b})). + not_to_contain_error "was deprecated" - it returns buffered string: expect (b:tostring ()).to_be "foobar" @@ -68,15 +67,32 @@ specify std.strbuf: - describe concat: - before: b = StrBuf {"foo", "bar"} - - it can be called from strbuf module: - b = StrBuf.concat (b, "baz") - expect (object.type (b)).to_be "StrBuf" - expect (StrBuf.tostring (b)).to_be "foobarbaz" - - it can be called as a strbuf object method: - b:concat "baz" - expect (object.type (b)).to_be "StrBuf" - expect (b:tostring()).to_be "foobarbaz" - - it can be called as a strbuf metamethod: - b = b .. "baz" - expect (object.type (b)).to_be "StrBuf" - expect (tostring (b)).to_be "foobarbaz" + bb = StrBuf {"baz", "quux"} + + - context as a module function: + - it appends a string: + b = StrBuf.concat (b, "baz") + expect (object.type (b)).to_be "StrBuf" + expect (tostring (b)).to_be "foobarbaz" + - it appends a StrBuf: + b = StrBuf.concat (b, bb) + expect (object.type (b)).to_be "StrBuf" + expect (tostring (b)).to_be "foobarbazquux" + - context as an object method: + - it appends a string: + b:concat "baz" + expect (object.type (b)).to_be "StrBuf" + expect (tostring(b)).to_be "foobarbaz" + - it appends a StrBuf: + b:concat (bb) + expect (object.type (b)).to_be "StrBuf" + expect (tostring (b)).to_be "foobarbazquux" + - context as a metamethod: + - it appends a string: + b = b .. "baz" + expect (object.type (b)).to_be "StrBuf" + expect (tostring (b)).to_be "foobarbaz" + - it appends a StrBuf: + b = b .. bb + expect (object.type (b)).to_be "StrBuf" + expect (tostring (b)).to_be "foobarbazquux" From a658c375a36ecf0fdc48203e5a7557ad412ed037 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 25 Jan 2015 19:47:37 +0000 Subject: [PATCH 487/703] strbuf: support lazy stringification and concat anything. * specs/strbuf_spec.yaml: Specify correct behaviours, and add lazy stringification example. * lib/std/strbuf.lua (__concat): Simply insert a reference. (__tostring): Make a table of stringified elements, and concat those instead of the object elements proper. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 7 ++++- lib/std/strbuf.lua | 48 ++++++++++++++++++---------------- specs/strbuf_spec.yaml | 59 ++++++++++++++++++++++++++++-------------- 3 files changed, 70 insertions(+), 44 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3cdc63f..49df374 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,11 +4,16 @@ ### New features - - `std.strbuf` can append both strings and other StrBuf instances: + - Anything that responds to `tostring` can be appended to a `std.strbuf`: 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. + ### Deprecations - `std.strbuf.tostring` has been deprecated in favour of `tostring`. diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 7aa66b7..61f18f5 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -16,24 +16,22 @@ local debug = require "std.debug" local Object = require "std.object" {} -local insert, prototype = base.insert, base.prototype +local ielems, insert, prototype = base.ielems, base.insert, base.prototype local M, StrBuf -local function concat (self, x) - if type (x) == "string" then - insert (self, x) - else - assert (prototype (x) == "StrBuf") - for _, v in ipairs (x) do - insert (self, v) - end - end - return self +local function __concat (self, x) + return insert (self, x) end +local function __tostring (self) + local strs = {} + for e in ielems (self) do strs[#strs + 1] = tostring (e) end + return table.concat (strs) +end + --[[ ================= ]]-- --[[ Public Interface. ]]-- @@ -46,14 +44,16 @@ end M = { - --- Add a string to a buffer. + --- Add a object to a buffer. + -- Elements are stringified lazily, so if add a table and then change + -- its contents, the contents of the buffer will be affected too. -- @static -- @function concat - -- @tparam string|StrBuf x string or StrBuf to add + -- @param x object to add to buffer -- @treturn StrBuf modified buffer -- @usage - -- buf = concat (buf, "append this") - concat = X ("concat (StrBuf, string|StrBuf)", concat), + -- buf = buf:concat "append this" {" and", " this"} + concat = X ("concat (StrBuf, any)", __concat), } @@ -67,7 +67,7 @@ local DEPRECATED = debug.DEPRECATED M.tostring = DEPRECATED ("41.1", "std.strbuf.tostring", "use 'tostring (strbuf)' instead", - X ("tostring (StrBuf)", table.concat)) + X ("tostring (StrBuf)", __tostring)) --[[ ================== ]]-- @@ -85,9 +85,11 @@ M.tostring = DEPRECATED ("41.1", "std.strbuf.tostring", -- @usage -- local std = require "std" -- local StrBuf = std.strbuf {} --- local buf = StrBuf {"initial buffer contents"} --- buf = buf .. "append to buffer" --- print (buf) -- implicit `tostring` concatenates everything +-- local a = {1, 2, 3} +-- local b = {a, "five", "six"} +-- a = a .. 4 +-- b = b:concat "seven" +-- print (a, b) --> 1234 1234fivesixseven -- os.exit (0) StrBuf = Object { _type = "StrBuf", @@ -97,12 +99,12 @@ StrBuf = Object { --- Support concatenation to StrBuf objects. -- @function __concat -- @tparam StrBuf buffer object - -- @string s a string + -- @param x a string, or object that can be coerced to a string -- @treturn StrBuf modified *buf* -- @see concat -- @usage - -- buf = buf .. str - __concat = concat, + -- buf = buf .. x + __concat = __concat, --- Support fast conversion to Lua string. -- @function __tostring @@ -111,7 +113,7 @@ StrBuf = Object { -- @see tostring -- @usage -- str = tostring (buf) - __tostring = table.concat, + __tostring = __tostring, } diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index bdf9ceb..bd00020 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -56,6 +56,17 @@ specify std.strbuf: setdebug { deprecate = false } expect (capture (StrBuf.tostring, {b})). not_to_contain_error "was deprecated" + - it returns buffered string: + expect (StrBuf.tostring (b)).to_be "foobar" + + - context as an object method: + - it writes a deprecation warning: + setdebug { deprecate = "nil" } + expect (capture (b.tostring, {b})). + to_contain_error "was deprecated" + setdebug { deprecate = false } + expect (capture (b.tostring, {b})). + not_to_contain_error "was deprecated" - it returns buffered string: expect (b:tostring ()).to_be "foobar" @@ -66,33 +77,41 @@ specify std.strbuf: - describe concat: - before: - b = StrBuf {"foo", "bar"} - bb = StrBuf {"baz", "quux"} + a = StrBuf {"foo", "bar"} + b = StrBuf {"baz", "quux"} - context as a module function: - it appends a string: - b = StrBuf.concat (b, "baz") - expect (object.type (b)).to_be "StrBuf" - expect (tostring (b)).to_be "foobarbaz" + a = StrBuf.concat (a, "baz") + expect (object.type (a)).to_be "StrBuf" + expect (tostring (a)).to_be "foobarbaz" - it appends a StrBuf: - b = StrBuf.concat (b, bb) - expect (object.type (b)).to_be "StrBuf" - expect (tostring (b)).to_be "foobarbazquux" + a = StrBuf.concat (a, b) + expect (object.type (a)).to_be "StrBuf" + expect (tostring (a)).to_be "foobarbazquux" - context as an object method: - it appends a string: - b:concat "baz" - expect (object.type (b)).to_be "StrBuf" - expect (tostring(b)).to_be "foobarbaz" + a = a:concat "baz" + expect (object.type (a)).to_be "StrBuf" + expect (tostring(a)).to_be "foobarbaz" - it appends a StrBuf: - b:concat (bb) - expect (object.type (b)).to_be "StrBuf" - expect (tostring (b)).to_be "foobarbazquux" + a = a:concat (b) + expect (object.type (a)).to_be "StrBuf" + expect (tostring (a)).to_be "foobarbazquux" - context as a metamethod: - it appends a string: - b = b .. "baz" - expect (object.type (b)).to_be "StrBuf" - expect (tostring (b)).to_be "foobarbaz" + a = a .. "baz" + expect (object.type (a)).to_be "StrBuf" + expect (tostring (a)).to_be "foobarbaz" - it appends a StrBuf: - b = b .. bb - expect (object.type (b)).to_be "StrBuf" - expect (tostring (b)).to_be "foobarbazquux" + a = a .. b + expect (object.type (a)).to_be "StrBuf" + expect (tostring (a)).to_be "foobarbazquux" + - it stringifies lazily: + a = StrBuf {1} + b = StrBuf {a, "five"} + a = a:concat (2) + expect (tostring (b)).to_be "12five" + b = StrBuf {tostring (a), "five"} + a = a:concat (3) + expect (tostring (b)).to_be "12five" From c6308094b1b23764376caa5d8d74249ae573f71a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 25 Jan 2015 20:16:51 +0000 Subject: [PATCH 488/703] strbuf: document and test functional copying style. Close #85 * lib/std/strbuf.lua: Improve LDocs. * specs/strbuf_spec.yaml: Add example of functional copying. Signed-off-by: Gary V. Vaughan --- lib/std/strbuf.lua | 10 ++++++++++ specs/strbuf_spec.yaml | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 61f18f5..d23af9a 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -1,6 +1,16 @@ --[[-- String buffers. + Buffers are mutable by default, but being based on objects, they can + also be used in a functional style: + + local StrBuf = require "std.strbuf" {} + local a = StrBuf {"a"} + local b = a:concat "b" -- mutate *a* + print (a, b) --> ab ab + local c = a {} .. "c" -- copy and append + print (a, c) --> ab abc + Prototype Chain --------------- diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index bd00020..88bbc07 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -115,3 +115,7 @@ specify std.strbuf: b = StrBuf {tostring (a), "five"} a = a:concat (3) expect (tostring (b)).to_be "12five" + - it can be non-destructive: + a = StrBuf {1} + b = a {} .. 2 + expect (tostring (a)).to_be "1" From a87a16c7f803c47e06fccf94b5932757e155bd84 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 25 Jan 2015 20:56:28 +0000 Subject: [PATCH 489/703] debug: argcheck requires leading ? for argtypes, to match specl. Close #90 * specs/debug_spec.yaml (argcheck): Change to leading `?` over previous trailing `?` style. * lib/std.lua.in, lib/std/container.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua: Adjust accordingly. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 10 ++++----- lib/std/container.lua | 2 +- lib/std/debug.lua | 10 ++++----- lib/std/functional.lua | 6 ++--- lib/std/io.lua | 8 +++---- lib/std/list.lua | 2 +- lib/std/math.lua | 6 ++--- lib/std/package.lua | 6 ++--- lib/std/string.lua | 24 ++++++++++---------- lib/std/table.lua | 16 +++++++------- lib/std/tree.lua | 4 ++-- specs/debug_spec.yaml | 50 +++++++++++++++++++++--------------------- 12 files changed, 72 insertions(+), 72 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 38807e2..cbf9f74 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -93,7 +93,7 @@ M = { -- @usage -- std.assert (expected ~= nil, "100% unexpected!") -- std.assert (expected ~= nil, "%s unexpected!", expected) - assert = X ("assert (any?, string?, any?*)", base.assert), + assert = X ("assert (?any, ?string, ?any*)", base.assert), --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). -- @@ -104,7 +104,7 @@ M = { -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table module table -- @usage local std = require "std".barrel () - barrel = X ("barrel (table?)", barrel), + barrel = X ("barrel (?table)", barrel), --- An iterator over all elements of a sequence. -- If *t* has a `__pairs` metamethod, use that to iterate. @@ -192,7 +192,7 @@ M = { -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local std = require "std".monkey_patch () - monkey_patch = X ("monkey_patch (table?)", monkey_patch), + monkey_patch = X ("monkey_patch (?table)", monkey_patch), --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. -- @function pairs @@ -218,7 +218,7 @@ M = { -- @usage -- -- posix.version == "posix library for Lua 5.2 / 32" -- posix = require ("posix", "29") - require = X ("require (string, string?, string?, string?)", base.require), + require = X ("require (string, ?string, ?string, ?string)", base.require), --- An iterator like ipairs, but in reverse. -- Apart from the order of the elments returned, this function follows @@ -238,7 +238,7 @@ M = { -- @usage -- -- {1=baz,foo=bar} -- print (std.tostring {foo="bar","baz"}) - tostring = X ("tostring (any?)", base.tostring), + tostring = X ("tostring (?any)", base.tostring), version = "General Lua libraries / @VERSION@", } diff --git a/lib/std/container.lua b/lib/std/container.lua index eb21aa4..2b682c3 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -197,7 +197,7 @@ local function X (decl, fn) end local M = { - mapfields = X ("mapfields (table, table|object, table?)", mapfields), + mapfields = X ("mapfields (table, table|object, ?table)", mapfields), } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index e68d8e2..a432e26 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -154,13 +154,13 @@ if _DEBUG.argcheck then --- Normalize a list of type names. - -- @tparam table t list of type names, trailing "?" as required + -- @tparam table t list of type names, leading "?" as required -- @treturn table a new list with "?" stripped, "nil" appended if so, -- and with duplicates stripped. local function normalize (t) local i, r, add_nil = 1, {}, false for _, v in ipairs (t) do - local m = v:match "^(.+)%?$" + local m = v:match "^%?(.+)$" if m then add_nil = true r[m] = r[m] or i @@ -623,10 +623,10 @@ M = { -- -- A very common pattern is to have a list of possible types including -- "nil" when the argument is optional. Rather than writing long-hand - -- as above, append a question mark to at least one of the list types - -- and omit the explicit "nil" entry: + -- as above, prepend a question mark to the list of types and omit the + -- explicit "nil" entry: -- - -- argcheck ("table.copy", 2, "boolean|:nometa?", predicate) + -- argcheck ("table.copy", 2, "?boolean|:nometa", predicate) -- -- Normally, you should not need to use the `level` parameter, as the -- default is to blame the caller of the function using `argcheck` in diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 026d7b7..7ea89a6 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -268,7 +268,7 @@ local M = { -- @return function with *argt* arguments already bound -- @usage -- cube = bind (std.operator.pow, {[2] = 3}) - bind = X ("bind (func, any?*)", bind), + bind = X ("bind (func, ?any*)", bind), --- Identify callable types. -- @function callable @@ -296,7 +296,7 @@ local M = { -- string = function () return "string" end, -- function (s) error ("unhandled type: " .. s) end, -- }) - case = X ("case (any?, #table)", case), + case = X ("case (?any, #table)", case), --- Collect the results of an iterator. -- @function collect @@ -467,7 +467,7 @@ local M = { -- @treturn functable memoized function -- @usage -- local fast = memoize (function (...) --[[ slow code ]] end) - memoize = X ("memoize (func, func?)", memoize), + memoize = X ("memoize (func, ?func)", memoize), --- No operation. -- This function ignores all arguments, and returns no values. diff --git a/lib/std/io.lua b/lib/std/io.lua index e0a8fde..a50ea0e 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -169,7 +169,7 @@ M = { -- @param ... additional arguments to plug format string specifiers -- @see warn -- @usage die ("oh noes! (%s)", tostring (obj)) - die = X ("die (string, any?*)", function (...) + die = X ("die (string, ?any*)", function (...) error (warnfmt (...), 0) end), @@ -190,7 +190,7 @@ M = { -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the `std.io` module table -- @usage local io = require "std.io".monkey_patch () - monkey_patch = X ("monkey_patch (table?)", monkey_patch), + monkey_patch = X ("monkey_patch (?table)", monkey_patch), --- Process files specified on the command-line. -- Each filename is made the default input source with `io.input`, and @@ -262,7 +262,7 @@ M = { -- if not _G.opts.keep_going then -- require "std.io".warn "oh noes!" -- end - warn = X ("warn (string, any?*)", warn), + warn = X ("warn (string, ?any*)", warn), --- Write values adding a newline after each. -- @function writelines @@ -270,7 +270,7 @@ M = { -- 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), + writelines = X ("writelines (?file|string|number, ?string|number*)", writelines), } diff --git a/lib/std/list.lua b/lib/std/list.lua index 8932028..19c6c44 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -150,7 +150,7 @@ M = { -- @usage -- --> {3, 4, 5} -- list.sub ({1, 2, 3, 4, 5, 6}, 3, 5) - sub = X ("sub (List, int?, int?)", sub), + sub = X ("sub (List, ?int, ?int)", sub), --- Return a list with its first element removed. -- @static diff --git a/lib/std/math.lua b/lib/std/math.lua index a441a4c..2a87904 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -59,14 +59,14 @@ M = { -- @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), + floor = X ("floor (number, ?int)", floor), --- Overwrite core `math` methods with `std` enhanced versions. -- @function monkey_patch -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage require "std.math".monkey_patch () - monkey_patch = X ("monkey_patch (table?)", monkey_patch), + monkey_patch = X ("monkey_patch (?table)", monkey_patch), --- Round a number to a given number of decimal places -- @function round @@ -74,7 +74,7 @@ M = { -- @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), + round = X ("round (number, ?int)", round), } diff --git a/lib/std/package.lua b/lib/std/package.lua index 0c13a64..8f19713 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -127,7 +127,7 @@ M = { -- @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), + find = X ("find (string, string, ?int, ?boolean|:plain)", find), --- Insert a new element into a `package.path` like string of paths. -- @function insert @@ -147,7 +147,7 @@ M = { -- @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), + mappath = X ("mappath (string, function, ?any*)", mappath), --- Normalize a path list. -- Removing redundant `.` and `..` directories, and keep only the first @@ -168,7 +168,7 @@ M = { -- 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), + remove = X ("remove (string, ?int)", remove), } diff --git a/lib/std/string.lua b/lib/std/string.lua index ba9cdea..383f7ac 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -321,7 +321,7 @@ M = { -- 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), + 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. @@ -330,7 +330,7 @@ M = { -- @param[opt] ... arguments to format -- @return formatted string -- @usage print (format "100% stdlib!") - format = X ("format (string, any?*)", format), + format = X ("format (string, ?any*)", format), --- Remove leading matter from a string. -- @function ltrim @@ -338,7 +338,7 @@ M = { -- @string[opt="%s+"] r leading pattern -- @treturn string *s* with leading *r* stripped -- @usage print ("got: " .. ltrim (userinput)) - ltrim = X ("ltrim (string, string?)", + ltrim = X ("ltrim (string, ?string)", function (s, r) return (s:gsub ("^" .. (r or "%s+"), "")) end), --- Overwrite core `string` methods with `std` enhanced versions. @@ -349,7 +349,7 @@ M = { -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local string = require "std.string".monkey_patch () - monkey_patch = X ("monkey_patch (table?)", monkey_patch), + monkey_patch = X ("monkey_patch (?table)", monkey_patch), --- Write a number using SI suffixes. -- The number is always written to 3 s.f. @@ -378,7 +378,7 @@ M = { -- @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), + pad = X ("pad (string, int, ?string)", pad), --- Convert a value to a string. -- The string can be passed to `functional.eval` to retrieve the value. @@ -397,7 +397,7 @@ M = { -- @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), + prettytostring = X ("prettytostring (?any, ?string, ?string)", prettytostring), --- Turn tables into strings with recursion detection. -- N.B. Functions calling render should not recurse, or recursion @@ -417,7 +417,7 @@ M = { -- lambda '=_4.."=".._5', lambda '= _4 and "," or ""', -- lambda '=","') -- end - render = X ("render (any?, func, func, func, func, func, table?)", render), + render = X ("render (?any, func, func, func, func, func, ?table)", render), --- Remove trailing matter from a string. -- @function rtrim @@ -425,7 +425,7 @@ M = { -- @string[opt="%s+"] r trailing pattern -- @treturn string *s* with trailing *r* stripped -- @usage print ("got: " .. rtrim (userinput)) - rtrim = X ("rtrim (string, string?)", + rtrim = X ("rtrim (string, ?string)", function (s, r) return (s:gsub ((r or "%s+") .. "$", "")) end), --- Split a string at a given separator. @@ -436,7 +436,7 @@ M = { -- @string[opt="%s+"] sep separator pattern -- @return list of strings -- @usage words = split "a very short sentence" - split = X ("split (string, string?)", base.split), + split = X ("split (string, ?string)", base.split), --- Do `string.find`, returning a table of captures. -- @function tfind @@ -449,7 +449,7 @@ M = { -- @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), + tfind = X ("tfind (string, string, ?int, ?boolean|:plain)", tfind), --- Remove leading and trailing matter from a string. -- @function trim @@ -457,7 +457,7 @@ M = { -- @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), + trim = X ("trim (string, ?string)", trim), --- Wrap a string into a paragraph. -- @function wrap @@ -468,7 +468,7 @@ M = { -- @treturn string *s* wrapped to *w* columns -- @usage -- print (wrap (copyright, 72, 4)) - wrap = X ("wrap (string, int?, int?, int?)", wrap), + wrap = X ("wrap (string, ?int, ?int, ?int)", wrap), } diff --git a/lib/std/table.lua b/lib/std/table.lua index 30dbd70..f2a718f 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -211,7 +211,7 @@ M = { -- @see clone_select -- @usage -- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") - clone = X ("clone (table, [table], boolean|:nometa?)", + clone = X ("clone (table, [table], ?boolean|:nometa)", function (...) return merge_allfields ({}, ...) end), --- Make a partial clone of a table. @@ -227,7 +227,7 @@ M = { -- @see merge_select -- @usage -- partialcopy = clone_select (original, {"this", "and_this"}, true) - clone_select = X ("clone_select (table, [table], boolean|:nometa?)", + clone_select = X ("clone_select (table, [table], ?boolean|:nometa)", function (...) return merge_namedfields ({}, ...) end), --- Turn a list of pairs into a table. @@ -326,7 +326,7 @@ M = { -- @see clone -- @see merge_select -- @usage merge (_G, require "std.debug", {say = "log"}, ":nometa") - merge = X ("merge (table, table, [table], boolean|:nometa?)", merge_allfields), + merge = X ("merge (table, table, [table], ?boolean|:nometa)", merge_allfields), --- Destructively merge another table's named fields into *table*. -- @@ -341,7 +341,7 @@ M = { -- @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_select = X ("merge_select (table, table, [table], ?boolean|:nometa)", merge_namedfields), --- Overwrite core `table` methods with `std` enhanced versions. @@ -349,7 +349,7 @@ M = { -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table the module table -- @usage local table = require "std.table".monkey_patch () - monkey_patch = X ("monkey_patch (table?)", monkey_patch), + monkey_patch = X ("monkey_patch (?table)", monkey_patch), --- Make a table with a default value for unset keys. -- @function new @@ -357,7 +357,7 @@ M = { -- @tparam[opt={}] table t initial table -- @treturn table table whose unset elements are *x* -- @usage t = new (0) - new = X ("new (any?, table?)", new), + new = X ("new (?any, ?table)", new), --- Make an ordered list of keys in table. -- @function okeys @@ -397,7 +397,7 @@ M = { -- --> {1, 2, 5} -- t = {1, 2, "x", 5} -- remove (t, 3) == "x" and t - remove = X ("remove (table, int?)", remove), + remove = X ("remove (table, ?int)", remove), --- Shape a table according to a list of dimensions. -- @@ -439,7 +439,7 @@ M = { -- @tparam[opt=std.operator.lt] comparator c ordering function callback -- @return *t* with keys sorted accordind to *c* -- @usage table.concat (sort (object)) - sort = X ("sort (table, function?)", sort), + sort = X ("sort (table, ?function)", sort), --- Make the list of values of a table. -- @function values diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 91b7665..c5463c9 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -154,7 +154,7 @@ Tree = Container { -- @param[opt] v value -- @usage -- function bindkey (keylist, fn) keymap[keylist] = fn end - __newindex = X ("__newindex (Tree, any, any?)", + __newindex = X ("__newindex (Tree, any, ?any)", function (tr, i, v) if prototype (i) == "table" then for n = 1, len (i) - 1 do @@ -182,7 +182,7 @@ Tree = Container { -- copy = clone (tr) -- copy[2].two=5 -- assert (tr[2].two == 2) - clone = X ("clone (table, boolean|:nometa?)", clone), + clone = X ("clone (table, ?boolean|:nometa)", clone), --- Tree iterator which returns just numbered leaves, in order. -- @static diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 970a56a..8d12846 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -489,38 +489,38 @@ specify std.debug: expect (fn ("#table|table", {})).not_to_raise "any error" - context with an optional type element: - it diagnoses mismatched elements: - expect (fn ("boolean?", "string")). + expect (fn ("?boolean", "string")). to_raise "boolean or nil expected, got string" - expect (fn ("boolean?|:symbol", {})). + expect (fn ("?boolean|:symbol", {})). to_raise "boolean, :symbol or nil expected, got empty table" - it matches nil against a single type: - expect (fn ("any?", nil)).not_to_raise "any error" - expect (fn ("boolean?", nil)).not_to_raise "any error" - expect (fn ("string?", nil)).not_to_raise "any error" + expect (fn ("?any", nil)).not_to_raise "any error" + expect (fn ("?boolean", nil)).not_to_raise "any error" + expect (fn ("?string", nil)).not_to_raise "any error" - it matches nil against a list of types: - expect (fn ("boolean?|table", nil)).not_to_raise "any error" - expect (fn ("string?|table", nil)).not_to_raise "any error" - expect (fn ("table?|#table", nil)).not_to_raise "any error" - expect (fn ("#table?|table", nil)).not_to_raise "any error" + expect (fn ("?boolean|table", nil)).not_to_raise "any error" + expect (fn ("?string|table", nil)).not_to_raise "any error" + expect (fn ("?table|#table", nil)).not_to_raise "any error" + expect (fn ("?#table|table", nil)).not_to_raise "any error" - it matches nil against a list of optional types: - expect (fn ("boolean?|table?", nil)).not_to_raise "any error" - expect (fn ("string?|table?", nil)).not_to_raise "any error" - expect (fn ("table?|#table?", nil)).not_to_raise "any error" - expect (fn ("#table?|table?", nil)).not_to_raise "any error" + expect (fn ("?boolean|?table", nil)).not_to_raise "any error" + expect (fn ("?string|?table", nil)).not_to_raise "any error" + expect (fn ("?table|?#table", nil)).not_to_raise "any error" + expect (fn ("?#table|?table", nil)).not_to_raise "any error" - it matches any named type: - expect (fn ("any?", false)).not_to_raise "any error" - expect (fn ("boolean?", false)).not_to_raise "any error" - expect (fn ("string?", "string")).not_to_raise "any error" + expect (fn ("?any", false)).not_to_raise "any error" + expect (fn ("?boolean", false)).not_to_raise "any error" + expect (fn ("?string", "string")).not_to_raise "any error" - it matches any type from a list: - expect (fn ("boolean?|table", {})).not_to_raise "any error" - expect (fn ("string?|table", {0})).not_to_raise "any error" - expect (fn ("table?|#table", {})).not_to_raise "any error" - expect (fn ("#table?|table", {})).not_to_raise "any error" + expect (fn ("?boolean|table", {})).not_to_raise "any error" + expect (fn ("?string|table", {0})).not_to_raise "any error" + expect (fn ("?table|#table", {})).not_to_raise "any error" + expect (fn ("?#table|table", {})).not_to_raise "any error" - it matches any type from a list with several optional specifiers: - expect (fn ("boolean?|table?", {})).not_to_raise "any error" - expect (fn ("string?|table?", {0})).not_to_raise "any error" - expect (fn ("table?|table?", {})).not_to_raise "any error" - expect (fn ("#table?|table?", {})).not_to_raise "any error" + expect (fn ("?boolean|?table", {})).not_to_raise "any error" + expect (fn ("?string|?table", {0})).not_to_raise "any error" + expect (fn ("?table|?table", {})).not_to_raise "any error" + expect (fn ("?#table|?table", {})).not_to_raise "any error" - describe debug: @@ -587,7 +587,7 @@ specify std.debug: local debug = require "std.debug_init" local argscheck = require "std.debug".argscheck local function inner () return "MAGIC" end - local wrapped = argscheck ("inner (any?)", inner) + local wrapped = argscheck ("inner (?any)", inner) os.exit (wrapped == inner and 0 or 1) ]] expect (luaproc (script)).to_succeed () From a471f4471c11f9664a8cfa360aa6062773e6c7bb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 Jan 2015 11:11:55 +0000 Subject: [PATCH 490/703] refactor: change `types` symbol name to `argtypes`. * lib/std/debug.lua (argscheck): Change `types` symbol name to `argtypes`. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index a432e26..309f4e2 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -426,35 +426,35 @@ if _DEBUG.argcheck then function argscheck (decl, inner) -- Parse "fname (argtype, argtype, argtype...)". - local fname, types = decl:match "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" - if types == "" then - types = {} - elseif types then - types = split (types, ",%s*") + local fname, argtypes = decl:match "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" + if argtypes == "" then + argtypes = {} + elseif argtypes then + argtypes = split (argtypes, ",%s*") else fname = decl:match "([%w_][%.%d%w_]*)" end - -- If the final element of types ends with "*", then set max to a - -- sentinel value to denote type-checking of *all* remaining - -- unchecked arguments against that type-spec is required. - local max, fin = len (types), (last (types) or ""):match "^(.+)%*$" + -- If the final element of argtypes ends with "*", then set max to a + -- sentinel value to denote type-checking of *all* remaining unchecked + -- arguments against that type-spec is required. + local max, fin = len (argtypes), (last (argtypes) or ""):match "^(.+)%*$" if fin then max = math.huge - types[len (types)] = fin + argtypes[len (argtypes)] = fin end -- For optional arguments wrapped in square brackets, make sure -- type-specs allow for passing or omitting an argument of that -- type. - local typec, type_specs = len (types), permutations (types) + local typec, type_specs = len (argtypes), permutations (argtypes) return function (...) local args = {...} local argc, bestmismatch, at = maxn (args), 0, 0 - for i, types in ipairs (type_specs) do - local mismatch = match (types, args, max == math.huge) + for i, argtypes in ipairs (type_specs) do + local mismatch = match (argtypes, args, max == math.huge) if mismatch == nil then bestmismatch = nil break -- every argument matched its type-spec @@ -464,15 +464,15 @@ if _DEBUG.argcheck then end if bestmismatch ~= nil then - -- Report an error for all possible types at bestmismatch index. + -- Report an error for all possible argtypes at bestmismatch index. local expected if max == math.huge and bestmismatch >= typec then - expected = normalize (split (types[typec], "|")) + expected = normalize (split (argtypes[typec], "|")) else local tables = {} - for i, types in ipairs (type_specs) do - if types[bestmismatch] then - insert (tables, types[bestmismatch]) + for i, argtypes in ipairs (type_specs) do + if argtypes[bestmismatch] then + insert (tables, argtypes[bestmismatch]) end end expected = merge (unpack (tables)) @@ -480,8 +480,8 @@ if _DEBUG.argcheck then local i = bestmismatch -- For "table of things", check all elements are a thing too. - if types[i] then - local check, contents = types[i]:match "^(%S+) of (%S-)s?$" + if argtypes[i] then + local check, contents = argtypes[i]:match "^(%S+) of (%S-)s?$" if contents and type (args[i]) == "table" then for k, v in pairs (args[i]) do if not checktype (contents, v) then From 1d0b338d04c5e3b0b06fd85a0a1c04317cebbcd6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 Jan 2015 16:59:09 +0000 Subject: [PATCH 491/703] debug: use trailing `...` instead of `*` with argscheck. * specs/debug_spec.yaml (argscheck): Add specifications for continuation argument behavious with `...`. * lib/std/debug.lua (argscheck): Use `...` instead of `*` as the syntax for optional repeats of the final parameter type. Adjust all callers. * NEWS.md (Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 7 +++++++ lib/std.lua.in | 2 +- lib/std/debug.lua | 6 +++--- lib/std/functional.lua | 12 ++++++------ lib/std/io.lua | 10 +++++----- lib/std/list.lua | 2 +- lib/std/package.lua | 4 ++-- lib/std/string.lua | 2 +- specs/debug_spec.yaml | 18 ++++++++++++++++++ 9 files changed, 44 insertions(+), 19 deletions(-) diff --git a/NEWS.md b/NEWS.md index 49df374..f44ac06 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,6 +28,13 @@ - stdlib modules are all `std.strict` compliant; require "std.strict" before requiring other modules no longer raises an error. +### Incompatible changes + + - `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] diff --git a/lib/std.lua.in b/lib/std.lua.in index cbf9f74..3947e9e 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -93,7 +93,7 @@ M = { -- @usage -- std.assert (expected ~= nil, "100% unexpected!") -- std.assert (expected ~= nil, "%s unexpected!", expected) - assert = X ("assert (?any, ?string, ?any*)", base.assert), + assert = X ("assert (?any, ?string, ?any...)", base.assert), --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). -- diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 309f4e2..95d7113 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -435,10 +435,10 @@ if _DEBUG.argcheck then fname = decl:match "([%w_][%.%d%w_]*)" end - -- If the final element of argtypes ends with "*", then set max to a + -- If the final element of argtypes ends with "...", then set max to a -- sentinel value to denote type-checking of *all* remaining unchecked -- arguments against that type-spec is required. - local max, fin = len (argtypes), (last (argtypes) or ""):match "^(.+)%*$" + local max, fin = len (argtypes), (last (argtypes) or ""):match "^(.+)%.%.%.$" if fin then max = math.huge argtypes[len (argtypes)] = fin @@ -662,7 +662,7 @@ M = { --- Wrap a function definition with argument type and arity checking. -- In addition to checking that each argument type matches the corresponding -- element in the *types* table with `argcheck`, if the final element of - -- *types* ends with an asterisk, remaining unchecked arguments are checked + -- *types* ends with an elipsis, remaining unchecked arguments are checked -- against that type. -- @function argscheck -- @string decl function type declaration string diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 7ea89a6..10ffbaf 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -268,7 +268,7 @@ local M = { -- @return function with *argt* arguments already bound -- @usage -- cube = bind (std.operator.pow, {[2] = 3}) - bind = X ("bind (func, ?any*)", bind), + bind = X ("bind (func, ?any...)", bind), --- Identify callable types. -- @function callable @@ -308,7 +308,7 @@ local M = { -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} - collect = X ("collect ([func], any*)", base.collect), + collect = X ("collect ([func], any...)", base.collect), --- Compose functions. -- @function compose @@ -323,7 +323,7 @@ local M = { -- @usage -- vpairs = compose (table.invert, ipairs) -- for v, i in vpairs {"a", "b", "c"} do process (v, i) end - compose = X ("compose (func*)", compose), + compose = X ("compose (func...)", compose), --- A rudimentary condition-case statement. -- If *expr* is "truthy" return *branch* if given, otherwise *expr* @@ -368,7 +368,7 @@ local M = { -- @usage -- --> {2, 4} -- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) - filter = X ("filter (func, [func], any*)", filter), + filter = X ("filter (func, [func], any...)", filter), --- Fold a binary function left associatively. -- If parameter *d* is omitted, the first element of *t* is used, @@ -440,7 +440,7 @@ local M = { -- @usage -- --> {1, 4, 9, 16} -- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) - map = X ("map (func, [func], any*)", map), + map = X ("map (func, [func], any...)", map), --- Map a function over a table of argument lists. -- @function map_with @@ -489,7 +489,7 @@ local M = { -- @usage -- --> 2 ^ 3 ^ 4 ==> 4096 -- reduce (std.operator.pow, 2, std.ielems, {3, 4}) - reduce = X ("reduce (func, any, [func], any*)", reduce), + reduce = X ("reduce (func, any, [func], any...)", reduce), --- Zip a table of tables. -- Make a new table, with lists of elements at the same index in the diff --git a/lib/std/io.lua b/lib/std/io.lua index a50ea0e..780c958 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -148,7 +148,7 @@ M = { -- @return path without trailing separator -- @see catfile -- @usage dirpath = catdir ("", "absolute", "directory") - catdir = X ("catdir (string*)", function (...) + catdir = X ("catdir (string...)", function (...) return table.concat ({...}, dirsep):gsub("^$", dirsep) end), @@ -159,7 +159,7 @@ M = { -- @see catdir -- @see splitdir -- @usage filepath = catfile ("relative", "path", "filename") - catfile = X ("catfile (string*)", base.catfile), + catfile = X ("catfile (string...)", base.catfile), --- Die with error. -- This function uses the same rules to build a message prefix @@ -169,7 +169,7 @@ M = { -- @param ... additional arguments to plug format string specifiers -- @see warn -- @usage die ("oh noes! (%s)", tostring (obj)) - die = X ("die (string, ?any*)", function (...) + die = X ("die (string, ?any...)", function (...) error (warnfmt (...), 0) end), @@ -262,7 +262,7 @@ M = { -- if not _G.opts.keep_going then -- require "std.io".warn "oh noes!" -- end - warn = X ("warn (string, ?any*)", warn), + warn = X ("warn (string, ?any...)", warn), --- Write values adding a newline after each. -- @function writelines @@ -270,7 +270,7 @@ M = { -- 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), + writelines = X ("writelines (?file|string|number, ?string|number...)", writelines), } diff --git a/lib/std/list.lua b/lib/std/list.lua index 19c6c44..dd86f68 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -113,7 +113,7 @@ M = { -- @usage -- --> {1, 2, 3, {4, 5}, 6, 7} -- list.concat ({1, 2, 3}, {{4, 5}, 6, 7}) - concat = X ("concat (List, List|table*)", concat), + concat = X ("concat (List, List|table...)", concat), --- Prepend an item to a list. -- @static diff --git a/lib/std/package.lua b/lib/std/package.lua index 8f19713..f2bd5c8 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -147,7 +147,7 @@ M = { -- @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), + mappath = X ("mappath (string, function, ?any...)", mappath), --- Normalize a path list. -- Removing redundant `.` and `..` directories, and keep only the first @@ -159,7 +159,7 @@ M = { -- @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), + normalize = X ("normalize (string...)", normalize), --- Remove any element from a `package.path` like string of paths. -- @function remove diff --git a/lib/std/string.lua b/lib/std/string.lua index 383f7ac..d4f5849 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -330,7 +330,7 @@ M = { -- @param[opt] ... arguments to format -- @return formatted string -- @usage print (format "100% stdlib!") - format = X ("format (string, ?any*)", format), + format = X ("format (string, ?any...)", format), --- Remove leading matter from a string. -- @function ltrim diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 8d12846..a826f74 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -655,6 +655,24 @@ specify std.debug: expect (wrapped ("two")).to_be "MAGIC" expect (wrapped (1, "two")).to_be "MAGIC" + - context when checking variable argument function: + - before: + _, badarg = init (M, "", "inner") + wrapped = f ("inner (string, int...)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "string")) + expect (wrapped ("foo")).to_raise (badarg (2, "int")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badarg (12, "int", "boolean")) + - it accepts correct argument types: + expect (wrapped ("foo", 1)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + - describe say: - before: | From 29ef2ab7617ef5496335b40022e7956d233e28f0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 Jan 2015 18:07:37 +0000 Subject: [PATCH 492/703] debug: argscheck accepts bracketed final parameter. * specs/debug_spec.yaml (argscheck): Add examples for bracketed final parameters. * lib/std/debug.lua (argscheck): Handle bracketed final parameter and satisfy new specs. Adjust all callers accordingly. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std.lua.in | 2 +- lib/std/debug.lua | 14 +++++++++++++- lib/std/functional.lua | 2 +- lib/std/io.lua | 10 +++++----- lib/std/package.lua | 2 +- lib/std/string.lua | 2 +- specs/debug_spec.yaml | 38 +++++++++++++++++++++++++++++++++++++- 8 files changed, 62 insertions(+), 11 deletions(-) diff --git a/NEWS.md b/NEWS.md index f44ac06..1fa3782 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,9 @@ `tostring` also changes the contents of the buffer. See LDocs for an example. + - `debug.argscheck` accepts square brackets around final optional + parameters in preference to requiring prepending `?` or `nil|`. + ### Deprecations - `std.strbuf.tostring` has been deprecated in favour of `tostring`. diff --git a/lib/std.lua.in b/lib/std.lua.in index 3947e9e..3724ad3 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -93,7 +93,7 @@ M = { -- @usage -- std.assert (expected ~= nil, "100% unexpected!") -- std.assert (expected ~= nil, "%s unexpected!", expected) - assert = X ("assert (?any, ?string, ?any...)", base.assert), + assert = X ("assert (?any, ?string, [any...])", base.assert), --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). -- diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 95d7113..00bf942 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -424,13 +424,25 @@ if _DEBUG.argcheck then end + -- Pattern to extract: fname ([types]?[, types]*) + local args_pat = "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" + + -- Pattern to normalize: [types], [types...], or [types]... + local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" + function argscheck (decl, inner) -- Parse "fname (argtype, argtype, argtype...)". - local fname, argtypes = decl:match "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" + local fname, argtypes = decl:match (args_pat) if argtypes == "" then argtypes = {} elseif argtypes then argtypes = split (argtypes, ",%s*") + + -- normalize final `[types]` to `?types` + local types, ellipsis = (last (argtypes) or ""):match (last_pat) + if types then + argtypes[#argtypes] = "?" .. types .. ellipsis + end else fname = decl:match "([%w_][%.%d%w_]*)" end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 10ffbaf..5226194 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -268,7 +268,7 @@ local M = { -- @return function with *argt* arguments already bound -- @usage -- cube = bind (std.operator.pow, {[2] = 3}) - bind = X ("bind (func, ?any...)", bind), + bind = X ("bind (func, [any...])", bind), --- Identify callable types. -- @function callable diff --git a/lib/std/io.lua b/lib/std/io.lua index 780c958..85ac7aa 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -169,7 +169,7 @@ M = { -- @param ... additional arguments to plug format string specifiers -- @see warn -- @usage die ("oh noes! (%s)", tostring (obj)) - die = X ("die (string, ?any...)", function (...) + die = X ("die (string, [any...])", function (...) error (warnfmt (...), 0) end), @@ -214,7 +214,7 @@ M = { -- 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|nil)", readlines), + readlines = X ("readlines (?file|string)", readlines), --- Perform a shell command and return its output. -- @function shell @@ -231,7 +231,7 @@ M = { -- @return contents of file or handle, or nil if error -- @see process_files -- @usage contents = slurp (filename) - slurp = X ("slurp (file|string|nil)", slurp), + slurp = X ("slurp (?file|string)", slurp), --- Split a directory path into components. -- Empty components are retained: the root directory becomes `{"", ""}`. @@ -262,7 +262,7 @@ M = { -- if not _G.opts.keep_going then -- require "std.io".warn "oh noes!" -- end - warn = X ("warn (string, ?any...)", warn), + warn = X ("warn (string, [any...])", warn), --- Write values adding a newline after each. -- @function writelines @@ -270,7 +270,7 @@ M = { -- 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), + writelines = X ("writelines (?file|string|number, [string|number...])", writelines), } diff --git a/lib/std/package.lua b/lib/std/package.lua index f2bd5c8..9700a20 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -147,7 +147,7 @@ M = { -- @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), + mappath = X ("mappath (string, function, [any...])", mappath), --- Normalize a path list. -- Removing redundant `.` and `..` directories, and keep only the first diff --git a/lib/std/string.lua b/lib/std/string.lua index d4f5849..ca0e151 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -330,7 +330,7 @@ M = { -- @param[opt] ... arguments to format -- @return formatted string -- @usage print (format "100% stdlib!") - format = X ("format (string, ?any...)", format), + format = X ("format (string, [any...])", format), --- Remove leading matter from a string. -- @function ltrim diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index a826f74..a10442c 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -655,7 +655,7 @@ specify std.debug: expect (wrapped ("two")).to_be "MAGIC" expect (wrapped (1, "two")).to_be "MAGIC" - - context when checking variable argument function: + - context when checking final ellipsis function: - before: _, badarg = init (M, "", "inner") wrapped = f ("inner (string, int...)", mkmagic) @@ -673,6 +673,42 @@ specify std.debug: expect (wrapped ("foo", 1, 2)).to_be "MAGIC" expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + - context when checking final parameter: + - context with trailing ellipsis: + - before: + _, badarg = init (M, "", "inner") + wrapped = f ("inner (string, [int]...)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "string")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badarg (2, "?int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "?int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badarg (12, "?int", "boolean")) + - it accepts correct argument types: + expect (wrapped ("foo")).to_be "MAGIC" + expect (wrapped ("foo", 1)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + - context with inner ellipsis: + - before: + _, badarg = init (M, "", "inner") + wrapped = f ("inner (string, [int...])", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "string")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badarg (2, "?int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "?int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badarg (12, "?int", "boolean")) + - it accepts correct argument types: + expect (wrapped ("foo")).to_be "MAGIC" + expect (wrapped ("foo", 1)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + - describe say: - before: | From 8b082db938163b7a2e5d69fd91de47302e7fe334 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 Jan 2015 21:59:27 +0000 Subject: [PATCH 493/703] debug: process [final|parm|types] without nil substitution hack. * specs/debug_spec.yaml (argscheck): Specify behaviours for bracketed final parameter more thoroughly. * lib/std/debug.lua (argscheck): Remove hack of replacing brackets in bracketed last parameter by 'or nil'; we really run permutations without a bracketed final parameter now. (permutations): Simplify accordingly; we don't need all the sentinel gunk to track final argument nils now. (normalize): Simplify, and fix a related bug where nils could get dropped from the type list by a double increment. * lib/std/functional.lua (bind): This really is a '?any...', requiring explicit nils, and not a [any...] optional argument. * specs/debug_spec.yaml: Adjust badargs calls as necessary. * specs/io_spec.yaml (writelines): Now that this implementation is a step ahead of the current Specl release generator, manually write out bad argument examples. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++- lib/std/debug.lua | 57 ++++++++++++------------------------------ lib/std/functional.lua | 2 +- specs/debug_spec.yaml | 51 +++++++++++++++++++++++++++++++------ specs/io_spec.yaml | 19 +++++++------- 5 files changed, 74 insertions(+), 59 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1fa3782..8f33859 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,7 +15,9 @@ an example. - `debug.argscheck` accepts square brackets around final optional - parameters in preference to requiring prepending `?` or `nil|`. + parameters, which is distinct to the old way of prepending `?` or + `nil|` because no spurious "or nil" is reported for type mismatches + against a final bracketed argument. ### Deprecations diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 00bf942..1ab0d7f 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -158,26 +158,21 @@ if _DEBUG.argcheck then -- @treturn table a new list with "?" stripped, "nil" appended if so, -- and with duplicates stripped. local function normalize (t) - local i, r, add_nil = 1, {}, false + local r, seen, add_nil = {}, {}, false for _, v in ipairs (t) do local m = v:match "^%?(.+)$" if m then - add_nil = true - r[m] = r[m] or i - i = i + 1 - elseif v then - r[v] = r[v] or i - i = i + 1 + add_nil, v = true, m + end + if not seen[v] then + r[#r + 1] = v + seen[v] = true end end if add_nil then - r["nil"] = r["nil"] or i + r[#r + 1] = "nil" end - - -- Invert the return table. - local t = {} - for v, i in pairs (r) do t[i] = v end - return t + return r end @@ -217,13 +212,8 @@ if _DEBUG.argcheck then -- @tparam table types a list of expected types by argument position -- @treturn table set of possible type lists local function permutations (types) - local p, sentinel = {{}}, {"optional arg"} + local p = {{}} for i, v in ipairs (types) do - -- Remove sentinels before appending `v` to each list. - for _, v in ipairs (p) do - if last (v) == sentinel then table.remove (v) end - end - local opt = v:match "%[(.+)%]" if opt == nil then -- Append non-optional type-spec to each permutation. @@ -236,26 +226,8 @@ if _DEBUG.argcheck then p[b + o] = copy (p[b]) insert (p[b], opt) end - - -- Leave a marker for optional argument in final position. - for _, v in ipairs (p) do - insert (v, sentinel) - end end end - - -- Replace sentinels with "nil". - for i, v in ipairs (p) do - if v[#v] == sentinel then - table.remove (v) - if #v > 0 then - v[#v] = v[#v] .. "|nil" - else - v[1] = "nil" - end - end - end - return p end @@ -438,10 +410,10 @@ if _DEBUG.argcheck then elseif argtypes then argtypes = split (argtypes, ",%s*") - -- normalize final `[types]` to `?types` + -- normalize final `[types...]` to `[types]...` local types, ellipsis = (last (argtypes) or ""):match (last_pat) if types then - argtypes[#argtypes] = "?" .. types .. ellipsis + argtypes[#argtypes] = "[" .. types .. "]" .. ellipsis end else fname = decl:match "([%w_][%.%d%w_]*)" @@ -466,7 +438,8 @@ if _DEBUG.argcheck then local argc, bestmismatch, at = maxn (args), 0, 0 for i, argtypes in ipairs (type_specs) do - local mismatch = match (argtypes, args, max == math.huge) + local allargs = max == math.huge or (#argtypes == 0 and #type_specs > 1) + local mismatch = match (argtypes, args, allargs) if mismatch == nil then bestmismatch = nil break -- every argument matched its type-spec @@ -479,7 +452,9 @@ if _DEBUG.argcheck then -- Report an error for all possible argtypes at bestmismatch index. local expected if max == math.huge and bestmismatch >= typec then - expected = normalize (split (argtypes[typec], "|")) + -- remove [] and ... before normalization + local last = (argtypes[typec] or ""):match (last_pat) + expected = normalize (split (last or argtypes[typec], "|")) else local tables = {} for i, argtypes in ipairs (type_specs) do diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 5226194..10ffbaf 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -268,7 +268,7 @@ local M = { -- @return function with *argt* arguments already bound -- @usage -- cube = bind (std.operator.pow, {[2] = 3}) - bind = X ("bind (func, [any...])", bind), + bind = X ("bind (func, ?any...)", bind), --- Identify callable types. -- @function callable diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index a10442c..7ad5d97 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -628,12 +628,27 @@ specify std.debug: - it accepts correct argument types: expect (wrapped ({}, nop)).to_be "MAGIC" + - context when checking nil argument function: + - before: + _, badarg = init (M, "", "inner") + wrapped = f ("inner (?int, string)", mkmagic) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) + expect (wrapped (1, false)).to_raise (badarg (2, "string", "boolean")) + expect (wrapped (nil, false)).to_raise (badarg (2, "string", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, "foo", nop)).to_raise (badarg (3)) + expect (wrapped (nil, "foo", nop)).to_raise (badarg (3)) + - it accepts correct argument types: + expect (wrapped (1, "foo")).to_be "MAGIC" + expect (wrapped (nil, "foo")).to_be "MAGIC" + - context when checking optional argument function: - before: _, badarg = init (M, "", "inner") wrapped = f ("inner ([int])", mkmagic) - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) + expect (wrapped (false)).to_raise (badarg (1, "int", "boolean")) - it diagnoses too many arguments: expect (wrapped (1, nop)).to_raise (badarg (2)) - it accepts correct argument types: @@ -655,6 +670,28 @@ specify std.debug: expect (wrapped ("two")).to_be "MAGIC" expect (wrapped (1, "two")).to_be "MAGIC" + - context when checking final optional multi-argument function: + - before: + _, badarg = init (M, "", "inner") + wrapped = f ("inner (?any, ?string, [any])", mkmagic) + - it diagnoses wrong argument types: + expect (wrapped (1, false)).to_raise (badarg (2, "string or nil", "boolean")) + expect (wrapped (nil, false)).to_raise (badarg (2, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, "two", 3, false)).to_raise (badarg (4)) + expect (wrapped (nil, "two", 3, false)).to_raise (badarg (4)) + expect (wrapped (1, nil, 3, false)).to_raise (badarg (4)) + expect (wrapped (nil, nil, 3, false)).to_raise (badarg (4)) + - it accepts correct argument types: + expect (wrapped ()).to_be "MAGIC" + expect (wrapped (1)).to_be "MAGIC" + expect (wrapped (nil, "two")).to_be "MAGIC" + expect (wrapped (1, "two")).to_be "MAGIC" + expect (wrapped (nil, nil, 3)).to_be "MAGIC" + expect (wrapped (1, nil, 3)).to_be "MAGIC" + expect (wrapped (nil, "two", 3)).to_be "MAGIC" + expect (wrapped ("one", "two", 3)).to_be "MAGIC" + - context when checking final ellipsis function: - before: _, badarg = init (M, "", "inner") @@ -682,10 +719,10 @@ specify std.debug: expect (wrapped ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badarg (2, "?int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "?int", "boolean")) + expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badarg (12, "?int", "boolean")) + to_raise (badarg (12, "int", "boolean")) - it accepts correct argument types: expect (wrapped ("foo")).to_be "MAGIC" expect (wrapped ("foo", 1)).to_be "MAGIC" @@ -699,10 +736,10 @@ specify std.debug: expect (wrapped ()).to_raise (badarg (1, "string")) - it diagnoses wrong argument types: expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badarg (2, "?int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "?int", "boolean")) + expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badarg (12, "?int", "boolean")) + to_raise (badarg (12, "int", "boolean")) - it accepts correct argument types: expect (wrapped ("foo")).to_be "MAGIC" expect (wrapped ("foo", 1)).to_be "MAGIC" diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 5b7c2dc..d35b5db 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -412,15 +412,16 @@ specify std.io: h:close () os.remove (name) - - context with bad arguments: | - badargs.diagnose (f, "std.io.writelines (?file|string|number, ?string|number*)") - - closed = io.open (name, "r") closed:close () - examples { - ["it diagnoses closed file argument"] = function () - expect (f (closed)).to_raise (badarg (1, "?file|string|number", "closed file")) - end - } + - context with bad arguments: + - 'it diagnoses argument #1 type not FILE*, string, number or nil': + expect (f (false)).to_raise (badarg (1, "?file|string|number", "boolean")) + - 'it diagnoses argument #2 type not string, number or nil': + expect (f (1, false)).to_raise (badarg (2, "string|number", "boolean")) + - 'it diagnoses argument #3 type not string, number or nil': + expect (f (1, 2, false)).to_raise (badarg (3, "string|number", "boolean")) + - it diagnoses closed file argument: | + closed = io.open (name, "r") closed:close () + expect (f (closed)).to_raise (badarg (1, "?file|string|number", "closed file")) - it does not close the file handle upon completion: expect (io.type (h)).not_to_be "closed file" From 66196e9c6119f48170f2b51787d41a702c6b1a73 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 Jan 2015 22:56:42 +0000 Subject: [PATCH 494/703] debug: more accurate too many arguments diagnostics. Close #76 * specs/debug_spec.yaml (argscheck): Specify behaviour when arguments match by skipping bracketed optional parameters, but for one unmatched argument at the end. * lib/std/debug.lua (permutations): Rename function from this noun... (permute): ...to this verb. (argscheck): Use the matching permutation to decide whether too many arguments were passed, unless the last parameter has an ellipsis denoting any number of matching arguments are allowed. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 14 ++++++++++++-- lib/std/debug.lua | 23 ++++++++++++----------- specs/debug_spec.yaml | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8f33859..feed4ee 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,8 +15,8 @@ an example. - `debug.argscheck` accepts square brackets around final optional - parameters, which is distinct to the old way of prepending `?` or - `nil|` because no spurious "or nil" is reported for type mismatches + parameters, which is distinct to the old way of appending `?` or + `|nil` because no spurious "or nil" is reported for type mismatches against a final bracketed argument. ### Deprecations @@ -33,8 +33,18 @@ - 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. + ### 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- diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 1ab0d7f..e015002 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -211,7 +211,7 @@ if _DEBUG.argcheck then --- Calculate permutations of type lists with and without [optionals]. -- @tparam table types a list of expected types by argument position -- @treturn table set of possible type lists - local function permutations (types) + local function permute (types) local p = {{}} for i, v in ipairs (types) do local opt = v:match "%[(.+)%]" @@ -431,21 +431,21 @@ if _DEBUG.argcheck then -- For optional arguments wrapped in square brackets, make sure -- type-specs allow for passing or omitting an argument of that -- type. - local typec, type_specs = len (argtypes), permutations (argtypes) + local typec, permutations = len (argtypes), permute (argtypes) return function (...) local args = {...} - local argc, bestmismatch, at = maxn (args), 0, 0 + local argc, bestmismatch, atperm = maxn (args), 0, 0 - for i, argtypes in ipairs (type_specs) do - local allargs = max == math.huge or (#argtypes == 0 and #type_specs > 1) - local mismatch = match (argtypes, args, allargs) + for i, permutation in ipairs (permutations) do + local allargs = max == math.huge or (#permutation == 0 and #permutations > 1) + local mismatch = match (permutation, args, allargs) if mismatch == nil then - bestmismatch = nil + bestmismatch, atperm = nil, i break -- every argument matched its type-spec end - if mismatch > bestmismatch then bestmismatch, at = mismatch, i end + if mismatch > bestmismatch then bestmismatch, atperm = mismatch, i end end if bestmismatch ~= nil then @@ -457,7 +457,7 @@ if _DEBUG.argcheck then expected = normalize (split (last or argtypes[typec], "|")) else local tables = {} - for i, argtypes in ipairs (type_specs) do + for i, argtypes in ipairs (permutations) do if argtypes[bestmismatch] then insert (tables, argtypes[bestmismatch]) end @@ -482,8 +482,9 @@ if _DEBUG.argcheck then argerror (fname, i, formaterror (expected, args[i]), 2) end - if argc > max then - error (toomanyargmsg (fname, max, argc), 2) + local argmax = max == math.huge and max or math.min (max, #permutations[atperm]) + if argc > argmax then + error (toomanyargmsg (fname, argmax, argc), 2) end -- Propagate outer environment to inner function. diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 7ad5d97..ec9945f 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -746,6 +746,23 @@ specify std.debug: expect (wrapped ("foo", 1, 2)).to_be "MAGIC" expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + - context with too many args: + - before: + _, badarg = init (M, "", "inner") + wrapped = f ("inner ([string], int)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "string or int")) + expect (wrapped ("one")).to_raise (badarg (2, "int")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "string or int", "boolean")) + expect (wrapped ("one", false)).to_raise (badarg (2, "int", "boolean")) + - it diagnoses too many arguments: + expect (wrapped ("one", 2, false)).to_raise (badarg (3)) + expect (wrapped (1, false)).to_raise (badarg (2)) + - it accepts correct argument types: + expect (wrapped (1)).to_be "MAGIC" + expect (wrapped ("one", 2)).to_be "MAGIC" + - describe say: - before: | From ed7b528aa375dbe1a301d42e1523a5b3868bab4a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 Jan 2015 23:15:14 +0000 Subject: [PATCH 495/703] set: don't argcheck metamethods. Lua will not call metamethods unless the metatables are compatible, so don't waste time rechecking the argument types. * lib/std/set.lua (__add, __sub, __mul, __div, __le, __lt) (__tostring): Remove superfluous argscheck invocations. Signed-off-by: Gary V. Vaughan --- lib/std/set.lua | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/std/set.lua b/lib/std/set.lua index 580f3bb..dacb33a 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -162,7 +162,7 @@ Set = Container { -- @see union -- @usage -- union = set1 + set2 - __add = X ("__add (Set, Set)", union), + __add = union, --- Difference operator. -- @static @@ -173,7 +173,7 @@ Set = Container { -- @see difference -- @usage -- difference = set1 - set2 - __sub = X ("__sub (Set, Set)", difference), + __sub = difference, --- Intersection operator. -- @static @@ -184,7 +184,7 @@ Set = Container { -- @see intersection -- @usage -- intersection = set1 * set2 - __mul = X ("__mul (Set, Set)", intersection), + __mul = intersection, --- Symmetric difference operator. -- @function __div @@ -195,7 +195,7 @@ Set = Container { -- @see symmetric_difference -- @usage -- symmetric_difference = set1 / set2 - __div = X ("__div (Set, Set)", symmetric_difference), + __div = symmetric_difference, --- Subset operator. -- @static @@ -206,7 +206,7 @@ Set = Container { -- @see subset -- @usage -- issubset = set1 <= set2 - __le = X ("__le (Set, Set)", subset), + __le = subset, --- Proper subset operator. -- @static @@ -218,20 +218,19 @@ Set = Container { -- @see proper_subset -- @usage -- ispropersubset = set1 < set2 - __lt = X ("__lt (Set, Set)", proper_subset), + __lt = proper_subset, -- Return a string representation of this set. -- @treturn string string representation of a set. -- @see std.tostring - __tostring = X ("__tostring (Set)", - function (self) - local keys = {} - for k in pairs (self) do - keys[#keys + 1] = tostring (k) - end - table.sort (keys) - return prototype (self) .. " {" .. table.concat (keys, ", ") .. "}" - end), + __tostring = function (self) + local keys = {} + for k in pairs (self) do + keys[#keys + 1] = tostring (k) + end + table.sort (keys) + return prototype (self) .. " {" .. table.concat (keys, ", ") .. "}" + end, _functions = { From ef1dcf71a339aedad6516741d36fd2b14f6e9134 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 26 Jan 2015 23:15:14 +0000 Subject: [PATCH 496/703] tree: don't argcheck metamethods. Lua will not call metamethods unless the metatables are compatible, so don't waste time rechecking the argument types. * lib/std/tree.lua (__index, __newindex): Remove superfluous argscheck invocations. Signed-off-by: Gary V. Vaughan --- lib/std/tree.lua | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index c5463c9..2feaedc 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -137,14 +137,13 @@ Tree = Container { -- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? -- @usage -- del_other_window = keymap[{"C-x", "4", KEY_DELETE}] - __index = X ("__index (Tree, any)", - function (tr, i) - if prototype (i) == "table" then - return reduce (operator.get, tr, ielems, i) - else - return rawget (tr, i) - end - end), + __index = function (tr, i) + if prototype (i) == "table" then + return reduce (operator.get, tr, ielems, i) + else + return rawget (tr, i) + end + end, --- Deep insertion. -- @static @@ -154,20 +153,19 @@ Tree = Container { -- @param[opt] v value -- @usage -- function bindkey (keylist, fn) keymap[keylist] = fn end - __newindex = X ("__newindex (Tree, any, ?any)", - function (tr, i, v) - if prototype (i) == "table" then - for n = 1, len (i) - 1 do - if prototype (tr[i[n]]) ~= "Tree" then - rawset (tr, i[n], Tree {}) - end - tr = tr[i[n]] - end - rawset (tr, last (i), v) - else - rawset (tr, i, v) - end - end), + __newindex = function (tr, i, v) + if prototype (i) == "table" then + for n = 1, len (i) - 1 do + if prototype (tr[i[n]]) ~= "Tree" then + rawset (tr, i[n], Tree {}) + end + tr = tr[i[n]] + end + rawset (tr, last (i), v) + else + rawset (tr, i, v) + end + end, _functions = { --- Make a deep copy of a tree, including any metatables. From 66cc7d38273b4285aaa7f195f1926f31bdf7bef2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 27 Jan 2015 21:12:45 +0000 Subject: [PATCH 497/703] refactor: separate argscheck inner wrapper into its own function. * lib/std/debug.lua (empty): New function. (diagnose): New function... (argscheck): ...factored out of here. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 140 +++++++++++++++++++++++++--------------------- 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index e015002..e6ffa9c 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -366,6 +366,73 @@ if _DEBUG.argcheck then end + local function empty (t) return not next (t) end + + + -- Pattern to normalize: [types...] to [types]... + local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" + + --- Diagnose mismatches between *valuelist* and type *permutations*. + -- @string fname name of function + -- @tparam table valuelist normalized list of actual values to be checked + -- @int maxvalues maximum number of values permitted, or `math.huge` + -- for unlimited values + -- @tparam table typelist normalized list of type specs to verify + -- @tparam table permutations list of lists of valid typelist permutations + local function diagnose (fname, valuelist, maxvalues, typelist, permutations) + local maxtypes, bestmismatch, listlen = #typelist, 0, 0 + for i, list in ipairs (permutations) do + local allargs = maxvalues == math.huge or (empty (list) and #permutations > 1) + local mismatch = match (list, valuelist, allargs) + if mismatch == nil then + bestmismatch, listlen = nil, #list + break -- every *valuelist* matched types from this *list* + elseif mismatch > bestmismatch then + bestmismatch, listlen = mismatch, #list + end + end + + if bestmismatch ~= nil then + -- Report an error for all possible types at bestmismatch index. + local i, expected = bestmismatch + if maxvalues == math.huge and i >= maxtypes then + -- remove [] and ... before normalization + local last = (typelist[maxtypes] or ""):match (last_pat) + expected = normalize (split (last or typelist[maxtypes], "|")) + else + local tables = {} + for _, list in ipairs (permutations) do + if list[i] then + insert (tables, list[i]) + end + end + expected = merge (unpack (tables)) + end + + -- For "table of things", check all elements are a thing too. + if typelist[i] then + local check, contents = typelist[i]:match "^(%S+) of (%S-)s?$" + if contents and type (valuelist[i]) == "table" then + for k, v in pairs (valuelist[i]) do + if not checktype (contents, v) then + argerror (fname, i, formaterror (expected, v, k), 3) + end + end + end + end + + -- Otherwise the argument type itself was mismatched. + argerror (fname, i, formaterror (expected, valuelist[i]), 3) + end + + local n = maxn (valuelist) + local max = maxvalues == math.huge and maxvalues or math.min (maxvalues, listlen) + if n > max then + error (toomanyargmsg (fname, max, n), 3) + end + end + + function argcheck (name, i, expected, actual, level) level = level or 2 expected = normalize (split (expected, "|")) @@ -399,9 +466,6 @@ if _DEBUG.argcheck then -- Pattern to extract: fname ([types]?[, types]*) local args_pat = "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" - -- Pattern to normalize: [types], [types...], or [types]... - local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" - function argscheck (decl, inner) -- Parse "fname (argtype, argtype, argtype...)". local fname, argtypes = decl:match (args_pat) @@ -411,9 +475,9 @@ if _DEBUG.argcheck then argtypes = split (argtypes, ",%s*") -- normalize final `[types...]` to `[types]...` - local types, ellipsis = (last (argtypes) or ""):match (last_pat) + local types, dots = last (argtypes):match (last_pat) if types then - argtypes[#argtypes] = "[" .. types .. "]" .. ellipsis + argtypes[#argtypes] = "[" .. types .. "]" .. dots end else fname = decl:match "([%w_][%.%d%w_]*)" @@ -422,72 +486,20 @@ if _DEBUG.argcheck then -- If the final element of argtypes ends with "...", then set max to a -- sentinel value to denote type-checking of *all* remaining unchecked -- arguments against that type-spec is required. - local max, fin = len (argtypes), (last (argtypes) or ""):match "^(.+)%.%.%.$" + local maxargs, fin = #argtypes, (last (argtypes) or ""):match "^(.+)%.%.%.$" if fin then - max = math.huge - argtypes[len (argtypes)] = fin + maxargs = math.huge + argtypes[#argtypes] = fin end - -- For optional arguments wrapped in square brackets, make sure - -- type-specs allow for passing or omitting an argument of that - -- type. - local typec, permutations = len (argtypes), permute (argtypes) + -- Calculate type permutations once as an upvalue. + local permutations = permute (argtypes) return function (...) - local args = {...} - local argc, bestmismatch, atperm = maxn (args), 0, 0 - - for i, permutation in ipairs (permutations) do - local allargs = max == math.huge or (#permutation == 0 and #permutations > 1) - local mismatch = match (permutation, args, allargs) - if mismatch == nil then - bestmismatch, atperm = nil, i - break -- every argument matched its type-spec - end - - if mismatch > bestmismatch then bestmismatch, atperm = mismatch, i end - end - - if bestmismatch ~= nil then - -- Report an error for all possible argtypes at bestmismatch index. - local expected - if max == math.huge and bestmismatch >= typec then - -- remove [] and ... before normalization - local last = (argtypes[typec] or ""):match (last_pat) - expected = normalize (split (last or argtypes[typec], "|")) - else - local tables = {} - for i, argtypes in ipairs (permutations) do - if argtypes[bestmismatch] then - insert (tables, argtypes[bestmismatch]) - end - end - expected = merge (unpack (tables)) - end - local i = bestmismatch - - -- For "table of things", check all elements are a thing too. - if argtypes[i] then - local check, contents = argtypes[i]:match "^(%S+) of (%S-)s?$" - if contents and type (args[i]) == "table" then - for k, v in pairs (args[i]) do - if not checktype (contents, v) then - argerror (fname, i, formaterror (expected, v, k), 2) - end - end - end - end - - -- Otherwise the argument type itself was mismatched. - argerror (fname, i, formaterror (expected, args[i]), 2) - end - - local argmax = max == math.huge and max or math.min (max, #permutations[atperm]) - if argc > argmax then - error (toomanyargmsg (fname, argmax, argc), 2) - end + diagnose (fname, {...}, maxargs, argtypes, permutations) -- Propagate outer environment to inner function. + local x = math.max -- ??? getfenv(1) fails if we remove this ??? setfenv (inner, getfenv (1)) return inner (...) From b1b48cfbfcb3e3654a304d2e1d36929cc1075df3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 28 Jan 2015 09:03:25 +0000 Subject: [PATCH 498/703] debug: argscheck supports return type checking. Close #89 * specs/spec_helper.lua (badargs.result): Fallback implementation until next Specl release. (init): Use it to return a `badresult` function. * specs/debug_spec.yaml: Specifiy behaviours with return type checking. * lib/std/debug.lua (toomanyargmsg): Factor this... (toomanymsg): ...into this more general function. (M): Adjust accordingly. (resulterror): New function modelled after argerror, but tailored for result type mismatch error reporting. (has_dots): Symbol to name use of math.huge for denoting an ellipsis was stripped from the type list. (stripellipsis): New function. (diagnose): Refactored to take a vtable of arguments instead of an ever longer list of parameters. (argscheck): Build a vtable to call diagnose for argument type checking. Add another diagnose() with a separate vtable for result type checking. Update LDocs. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 19 ++++- lib/std/debug.lua | 133 ++++++++++++++++++++++++------- specs/debug_spec.yaml | 178 ++++++++++++++++++++++++++++++++++++------ specs/spec_helper.lua | 33 +++++++- 4 files changed, 304 insertions(+), 59 deletions(-) diff --git a/NEWS.md b/NEWS.md index feed4ee..9276e23 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,8 +6,10 @@ - Anything that responds to `tostring` can be appended to a `std.strbuf`: - local a, b = StrBuf { "foo", "bar" }, StrBuf { "baz", "quux" } - a = a .. b --> "foobarbazquux" + ```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 @@ -16,9 +18,20 @@ - `debug.argscheck` accepts square brackets around final optional parameters, which is distinct to the old way of appending `?` or - `|nil` because no spurious "or nil" is reported for type mismatches + `|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 + checkedfn = argscheck ("fname (?any...) => 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. + ### Deprecations - `std.strbuf.tostring` has been deprecated in favour of `tostring`. diff --git a/lib/std/debug.lua b/lib/std/debug.lua index e6ffa9c..369ec32 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -126,9 +126,9 @@ local getfenv = rawget (_G, "getfenv") or function (fn) end -local function toomanyargmsg (name, expect, actual) - local fmt = "bad argument #%d to '%s' (no more than %d argument%s expected, got %d)" - return string.format (fmt, expect + 1, name, expect, expect == 1 and "" or "s", actual) +local function toomanymsg (bad, to, name, expect, actual) + local s = "bad %s #%d %s '%s' (no more than %d %s%s expected, got %d)" + return s:format (bad, expect + 1, to, name, expect, bad, expect == 1 and "" or "s", actual) end @@ -138,6 +138,15 @@ if _DEBUG.argcheck then local copy, prototype = base.copy, base.prototype + local function resulterror (name, i, extramsg, level) + level = level or 1 + local s = string.format ("bad result #%d from '%s'", i, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) + end + --- Concatenate a table of strings using ", " and " or " delimiters. -- @tparam table alternatives a table of strings -- @treturn string string of elements from alternatives delimited by ", " @@ -368,21 +377,22 @@ if _DEBUG.argcheck then local function empty (t) return not next (t) end + -- Constant marker for whether to process ellipsis value. + local has_dots = math.huge -- Pattern to normalize: [types...] to [types]... local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" --- Diagnose mismatches between *valuelist* and type *permutations*. - -- @string fname name of function -- @tparam table valuelist normalized list of actual values to be checked - -- @int maxvalues maximum number of values permitted, or `math.huge` - -- for unlimited values - -- @tparam table typelist normalized list of type specs to verify - -- @tparam table permutations list of lists of valid typelist permutations - local function diagnose (fname, valuelist, maxvalues, typelist, permutations) + -- @tparam table argt table of precalculated values and handler functiens + local function diagnose (valuelist, argt) + local maxvalues, typelist, permutations = + argt.maxvalues, argt.typelist, argt.permutations + local maxtypes, bestmismatch, listlen = #typelist, 0, 0 for i, list in ipairs (permutations) do - local allargs = maxvalues == math.huge or (empty (list) and #permutations > 1) + local allargs = maxvalues == has_dots or (empty (list) and #permutations > 1) local mismatch = match (list, valuelist, allargs) if mismatch == nil then bestmismatch, listlen = nil, #list @@ -395,7 +405,7 @@ if _DEBUG.argcheck then if bestmismatch ~= nil then -- Report an error for all possible types at bestmismatch index. local i, expected = bestmismatch - if maxvalues == math.huge and i >= maxtypes then + if maxvalues == has_dots and i >= maxtypes then -- remove [] and ... before normalization local last = (typelist[maxtypes] or ""):match (last_pat) expected = normalize (split (last or typelist[maxtypes], "|")) @@ -415,20 +425,20 @@ if _DEBUG.argcheck then if contents and type (valuelist[i]) == "table" then for k, v in pairs (valuelist[i]) do if not checktype (contents, v) then - argerror (fname, i, formaterror (expected, v, k), 3) + argt.badtype (i, formaterror (expected, v, k), 3) end end end end -- Otherwise the argument type itself was mismatched. - argerror (fname, i, formaterror (expected, valuelist[i]), 3) + argt.badtype (i, formaterror (expected, valuelist[i]), 3) end local n = maxn (valuelist) - local max = maxvalues == math.huge and maxvalues or math.min (maxvalues, listlen) + local max = maxvalues == has_dots and maxvalues or math.min (maxvalues, listlen) if n > max then - error (toomanyargmsg (fname, max, n), 3) + error (argt.badcount (max, n), 3) end end @@ -463,6 +473,11 @@ if _DEBUG.argcheck then end + local function stripellipsis (t) + return (t[#t] or ""):match "^(.+)%.%.%.$" + end + + -- Pattern to extract: fname ([types]?[, types]*) local args_pat = "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" @@ -483,26 +498,68 @@ if _DEBUG.argcheck then fname = decl:match "([%w_][%.%d%w_]*)" end - -- If the final element of argtypes ends with "...", then set max to a - -- sentinel value to denote type-checking of *all* remaining unchecked - -- arguments against that type-spec is required. - local maxargs, fin = #argtypes, (last (argtypes) or ""):match "^(.+)%.%.%.$" - if fin then - maxargs = math.huge - argtypes[#argtypes] = fin + -- Support final element of argtypes ends with "..." + local maxargs, final = #argtypes, stripellipsis (argtypes) + if final then + maxargs, argtypes[#argtypes] = has_dots, final end - -- Calculate type permutations once as an upvalue. - local permutations = permute (argtypes) + -- Precalculate vtables once to make multiple calls faster. + local input, output = { + typelist = argtypes, + badcount = function (...) + return toomanymsg ("argument", "to", fname, ...) + end, + badtype = function (...) argerror (fname, ...) end, + maxvalues = maxargs, + permutations = permute (argtypes), + } + + -- Parse "... => returntype, returntype, returntype...". + local returntypes = decl:match "=>%s*(.+)%s*$" + if returntypes then + returntypes = split (returntypes, ",%s*") + + -- normalize final `[types...]` to `[types]...` + local types, dots = last (returntypes):match (last_pat) + if types then + returntypes[#returntypes] = "[" .. types .. "]" .. dots + end + + -- Support final element of returntypes ends with "..." + local maxreturns, final = #returntypes, stripellipsis (returntypes) + if final then + maxreturns, returntypes[#returntypes] = has_dots, final + end + + output = { + typelist = returntypes, + badcount = function (...) + return toomanymsg ("result", "from", fname, ...) + end, + badtype = function (...) resulterror (fname, ...) end, + maxvalues = maxreturns, + permutations = permute (returntypes), + } + end return function (...) - diagnose (fname, {...}, maxargs, argtypes, permutations) + -- Diagnose bad inputs. + diagnose ({...}, input) -- Propagate outer environment to inner function. local x = math.max -- ??? getfenv(1) fails if we remove this ??? setfenv (inner, getfenv (1)) - return inner (...) + -- Execute. + local results = {inner (...)} + + -- Diagnose bad outputs. + if returntypes then + diagnose (results, output) + end + + return unpack (results) end end @@ -662,13 +719,29 @@ M = { --- Wrap a function definition with argument type and arity checking. -- In addition to checking that each argument type matches the corresponding -- element in the *types* table with `argcheck`, if the final element of - -- *types* ends with an elipsis, remaining unchecked arguments are checked - -- against that type. + -- *types* ends with an ellipsis, remaining unchecked arguments are checked + -- against that type: + -- + -- format = argscheck ("string.format (string, ?any...)", string.format) + -- + -- If an argument can be omitted entirely, then put its type specification + -- in square brackets: + -- + -- insert = argscheck ("table.insert (table, [int], ?any)", table.insert) + -- + -- Similarly returt types can be checked with the same list syntax as + -- arguments: + -- + -- len = argscheck ("string.len (string) => int", string.len) + -- -- @function argscheck -- @string decl function type declaration string -- @func inner function to wrap with argument checking -- @usage - -- M.square = argscheck ("util.square (number)", function (n) return n * n end) + -- local case = argscheck ("std.functional.case (?any, #table) => [any...]", + -- function (with, branches) + -- ... + -- end) argscheck = argscheck, --- Print a debugging message to `io.stderr`. @@ -696,7 +769,7 @@ M = { -- if table.maxn {...} > 1 then -- io.stderr:write ("module.fname", 7, table.maxn {...}) -- ... - toomanyargmsg = toomanyargmsg, + toomanyargmsg = function (...) return toomanymsg ("argument", "to", ...) end, --- Trace function calls. -- Use as debug.sethook (trace, "cr"), which is done automatically diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index ec9945f..c3583f8 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -573,11 +573,14 @@ specify std.debug: ]], tostring (name), tostring (spec)) end - f, badarg = init (M, this_module, "argscheck") + f = M.argscheck mkmagic = function () return "MAGIC" end wrapped = f ("inner ()", mkmagic) + _, badarg, badresult = init (M, "", "inner") + id = function (...) return (table.unpack or unpack) {...} end + - it returns the wrapped function: expect (wrapped).not_to_be (inner) expect (wrapped ()).to_be "MAGIC" @@ -593,8 +596,6 @@ specify std.debug: expect (luaproc (script)).to_succeed () - context when checking zero argument function: - - before: - _, badarg = init (M, "", "inner") - it diagnoses too many arguments: expect (wrapped (false)).to_raise (badarg (1)) - it accepts correct argument types: @@ -602,7 +603,6 @@ specify std.debug: - context when checking single argument function: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner (#table)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "non-empty table")) @@ -615,7 +615,6 @@ specify std.debug: - context when checking multi-argument function: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner (table, function)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "table")) @@ -630,7 +629,6 @@ specify std.debug: - context when checking nil argument function: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner (?int, string)", mkmagic) - it diagnoses wrong argument types: expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) @@ -643,21 +641,8 @@ specify std.debug: expect (wrapped (1, "foo")).to_be "MAGIC" expect (wrapped (nil, "foo")).to_be "MAGIC" - - context when checking optional argument function: - - before: - _, badarg = init (M, "", "inner") - wrapped = f ("inner ([int])", mkmagic) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, nop)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - expect (wrapped (1)).to_be "MAGIC" - - context when checking optional multi-argument function: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner ([int], string)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "int or string")) @@ -672,7 +657,6 @@ specify std.debug: - context when checking final optional multi-argument function: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner (?any, ?string, [any])", mkmagic) - it diagnoses wrong argument types: expect (wrapped (1, false)).to_raise (badarg (2, "string or nil", "boolean")) @@ -694,7 +678,6 @@ specify std.debug: - context when checking final ellipsis function: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner (string, int...)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "string")) @@ -710,10 +693,19 @@ specify std.debug: expect (wrapped ("foo", 1, 2)).to_be "MAGIC" expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" - - context when checking final parameter: + - context when checking optional final parameter: + - context with single argument: + - before: + wrapped = f ("inner ([int])", mkmagic) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "int", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, nop)).to_raise (badarg (2)) + - it accepts correct argument types: + expect (wrapped ()).to_be "MAGIC" + expect (wrapped (1)).to_be "MAGIC" - context with trailing ellipsis: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner (string, [int]...)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "string")) @@ -730,7 +722,6 @@ specify std.debug: expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" - context with inner ellipsis: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner (string, [int...])", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "string")) @@ -748,7 +739,6 @@ specify std.debug: - context with too many args: - before: - _, badarg = init (M, "", "inner") wrapped = f ("inner ([string], int)", mkmagic) - it diagnoses missing arguments: expect (wrapped ()).to_raise (badarg (1, "string or int")) @@ -763,6 +753,144 @@ specify std.debug: expect (wrapped (1)).to_be "MAGIC" expect (wrapped ("one", 2)).to_be "MAGIC" + - context when checking single return value function: + - before: | + wrapped = f ("inner (?any...) => #table", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "non-empty table")) + - it diagnoses wrong result types: + expect (wrapped {}). + to_raise (badresult (1, "non-empty table", "empty table")) + - it diagnoses too many results: + expect (wrapped ({1}, 2, nop, "", false)).to_raise (badresult (1, 5)) + - it accepts correct results: + expect ({wrapped {1}}).to_equal {{1}} + + - context when checking multi-return value function: + - before: + wrapped = f ("inner (?any...) => int, string", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "int")) + expect (wrapped (1)).to_raise (badresult (2, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int", "boolean")) + expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", false)).to_raise (badresult (3)) + - it accepts correct argument types: + expect ({wrapped (1, "two")}).to_equal {1, "two"} + + - context when checking nil return specifier: + - before: + wrapped = f ("inner (?any...) => ?int, string", id) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) + expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) + expect (wrapped (nil, false)).to_raise (badresult (2, "string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "foo", nop)).to_raise (badresult (3)) + expect (wrapped (nil, "foo", nop)).to_raise (badresult (3)) + - it accepts correct result types: + expect ({wrapped (1, "foo")}).to_equal {1, "foo"} + expect ({wrapped (nil, "foo")}).to_equal {[2] = "foo"} + + - context when checking optional multi-return value function: + - before: + wrapped = f ("inner (?any...) => [int], string", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "int or string")) + expect (wrapped (1)).to_raise (badresult (2, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", nop)).to_raise (badresult (3)) + - it accepts correct result types: + expect ({wrapped ("two")}).to_equal {"two"} + expect ({wrapped (1, "two")}).to_equal {1, "two"} + + - context when checking final optional multi-return value function: + - before: + wrapped = f ("inner (?any...) => ?any, ?string, [any]", id) + - it diagnoses wrong result types: + expect (wrapped (1, false)).to_raise (badresult (2, "string or nil", "boolean")) + expect (wrapped (nil, false)).to_raise (badresult (2, "string or nil", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", 3, false)).to_raise (badresult (4)) + expect (wrapped (nil, "two", 3, false)).to_raise (badresult (4)) + expect (wrapped (1, nil, 3, false)).to_raise (badresult (4)) + expect (wrapped (nil, nil, 3, false)).to_raise (badresult (4)) + - it accepts correct result types: + expect ({wrapped ()}).to_equal {} + expect ({wrapped (1)}).to_equal {1} + expect ({wrapped (nil, "two")}).to_equal {[2]="two"} + expect ({wrapped (1, "two")}).to_equal {1, "two"} + expect ({wrapped (nil, nil, 3)}).to_equal {[3]=3} + expect ({wrapped (1, nil, 3)}).to_equal {1, [3]=3} + expect ({wrapped (nil, "two", 3)}).to_equal {[2]="two", [3]=3} + expect ({wrapped ("one", "two", 3)}).to_equal {"one", "two", 3} + + - context when checking optional final result: + - context with single result: + - before: + wrapped = f ("inner (?any...) => [int]", id) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, nop)).to_raise (badresult (2)) + - it accepts correct result types: + expect ({wrapped ()}).to_equal {} + expect ({wrapped (1)}).to_equal {1} + - context with trailing ellipsis: + - before: + wrapped = f ("inner (?any...) => string, [int]...", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badresult (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badresult (3, "int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badresult (12, "int", "boolean")) + - it accepts correct result types: + expect ({wrapped ("foo")}).to_equal {"foo"} + expect ({wrapped ("foo", 1)}).to_equal {"foo", 1} + expect ({wrapped ("foo", 1, 2)}).to_equal {"foo", 1, 2} + expect ({wrapped ("foo", 1, 2, 5)}).to_equal {"foo", 1, 2, 5} + - context with inner ellipsis: + - before: + wrapped = f ("inner (?any...) => string, [int...]", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badresult (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badresult (3, "int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badresult (12, "int", "boolean")) + - it accepts correct result types: + expect ({wrapped ("foo")}).to_equal {"foo"} + expect ({wrapped ("foo", 1)}).to_equal {"foo", 1} + expect ({wrapped ("foo", 1, 2)}).to_equal {"foo", 1, 2} + expect ({wrapped ("foo", 1, 2, 5)}).to_equal {"foo", 1, 2, 5} + + - context with too many results: + - before: + wrapped = f ("inner (?any...) => [string], int", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "string or int")) + expect (wrapped "one").to_raise (badresult (2, "int")) + - it diagnoses wrong result types: + expect (wrapped (false)). + to_raise (badresult (1, "string or int", "boolean")) + expect (wrapped ("one", false)). + to_raise (badresult (2, "int", "boolean")) + - it diagnoses too many results: + expect (wrapped ("one", 2, false)).to_raise (badresult (3)) + expect (wrapped (1, false)).to_raise (badresult (2)) + - it accepts correct argument types: + expect ({wrapped (1)}).to_equal {1} + expect ({wrapped ("one", 2)}).to_equal {"one", 2} + - describe say: - before: | diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index b5dc3b9..7209106 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -16,6 +16,7 @@ package.path = std.package.normalize ( 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" @@ -25,10 +26,40 @@ local LUA = os.getenv "LUA" or "lua" setdebug = require "std.debug"._setdebug +-- In case we're not using a bleeding edge release of Specl... +badargs.result = badargs.result or function (fname, i, want, got) + if want == nil then i, want = i - 1, i end -- numbers only for narg error + + 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 + return M[fname], + function (...) return badargs.format (name, ...) end, + function (...) return badargs.result (name, ...) end end From 08563a7a582d2e8c5b780077eff69cf41234aa51 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 28 Jan 2015 16:10:19 +0000 Subject: [PATCH 499/703] refactor: clean up maxvalues calculation in debug.argscheck. * lib/std/debug.lua (stripellipsis): Remove. (markdots): New function encapsulating stripellipsis, maxvalue calculation and optional normalization. (argscheck): Simplify accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 77 ++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 369ec32..dd2a619 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -217,12 +217,33 @@ if _DEBUG.argcheck then end + -- Constant marker for whether to process ellipsis value. + local HAS_DOTS = math.huge + + + --- Strip trailing ellipsis from final argument if any, storing maximum + -- number of values that can be matched directly in `t.maxvalues`. + -- @tparam table t table to act on + -- @treturn table *t* with ellipsis stripped and maxvalues field set + local function markdots (t) + local last = t[#t] + t.maxvalues = #t + if last then + t[#t] = last:gsub ("%.%.%.(%]?)$", function (bracket) + t.maxvalues = HAS_DOTS + return bracket + end) + end + return t + end + + --- Calculate permutations of type lists with and without [optionals]. - -- @tparam table types a list of expected types by argument position + -- @tparam table typelist a list of expected types by argument position -- @treturn table set of possible type lists - local function permute (types) + local function permute (typelist) local p = {{}} - for i, v in ipairs (types) do + for i, v in ipairs (typelist) do local opt = v:match "%[(.+)%]" if opt == nil then -- Append non-optional type-spec to each permutation. @@ -377,9 +398,6 @@ if _DEBUG.argcheck then local function empty (t) return not next (t) end - -- Constant marker for whether to process ellipsis value. - local has_dots = math.huge - -- Pattern to normalize: [types...] to [types]... local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" @@ -387,12 +405,12 @@ if _DEBUG.argcheck then -- @tparam table valuelist normalized list of actual values to be checked -- @tparam table argt table of precalculated values and handler functiens local function diagnose (valuelist, argt) - local maxvalues, typelist, permutations = - argt.maxvalues, argt.typelist, argt.permutations + local typelist, permutations = argt.typelist, argt.permutations + local maxvalues = typelist.maxvalues local maxtypes, bestmismatch, listlen = #typelist, 0, 0 for i, list in ipairs (permutations) do - local allargs = maxvalues == has_dots or (empty (list) and #permutations > 1) + local allargs = maxvalues == HAS_DOTS or (empty (list) and #permutations > 1) local mismatch = match (list, valuelist, allargs) if mismatch == nil then bestmismatch, listlen = nil, #list @@ -405,7 +423,7 @@ if _DEBUG.argcheck then if bestmismatch ~= nil then -- Report an error for all possible types at bestmismatch index. local i, expected = bestmismatch - if maxvalues == has_dots and i >= maxtypes then + if maxvalues == HAS_DOTS and i >= maxtypes then -- remove [] and ... before normalization local last = (typelist[maxtypes] or ""):match (last_pat) expected = normalize (split (last or typelist[maxtypes], "|")) @@ -436,7 +454,7 @@ if _DEBUG.argcheck then end local n = maxn (valuelist) - local max = maxvalues == has_dots and maxvalues or math.min (maxvalues, listlen) + local max = maxvalues == HAS_DOTS and maxvalues or math.min (maxvalues, listlen) if n > max then error (argt.badcount (max, n), 3) end @@ -473,11 +491,6 @@ if _DEBUG.argcheck then end - local function stripellipsis (t) - return (t[#t] or ""):match "^(.+)%.%.%.$" - end - - -- Pattern to extract: fname ([types]?[, types]*) local args_pat = "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" @@ -485,25 +498,13 @@ if _DEBUG.argcheck then -- Parse "fname (argtype, argtype, argtype...)". local fname, argtypes = decl:match (args_pat) if argtypes == "" then - argtypes = {} + argtypes = markdots {} elseif argtypes then - argtypes = split (argtypes, ",%s*") - - -- normalize final `[types...]` to `[types]...` - local types, dots = last (argtypes):match (last_pat) - if types then - argtypes[#argtypes] = "[" .. types .. "]" .. dots - end + argtypes = markdots (split (argtypes, ",%s*")) else fname = decl:match "([%w_][%.%d%w_]*)" end - -- Support final element of argtypes ends with "..." - local maxargs, final = #argtypes, stripellipsis (argtypes) - if final then - maxargs, argtypes[#argtypes] = has_dots, final - end - -- Precalculate vtables once to make multiple calls faster. local input, output = { typelist = argtypes, @@ -511,26 +512,13 @@ if _DEBUG.argcheck then return toomanymsg ("argument", "to", fname, ...) end, badtype = function (...) argerror (fname, ...) end, - maxvalues = maxargs, permutations = permute (argtypes), } -- Parse "... => returntype, returntype, returntype...". local returntypes = decl:match "=>%s*(.+)%s*$" if returntypes then - returntypes = split (returntypes, ",%s*") - - -- normalize final `[types...]` to `[types]...` - local types, dots = last (returntypes):match (last_pat) - if types then - returntypes[#returntypes] = "[" .. types .. "]" .. dots - end - - -- Support final element of returntypes ends with "..." - local maxreturns, final = #returntypes, stripellipsis (returntypes) - if final then - maxreturns, returntypes[#returntypes] = has_dots, final - end + returntypes = markdots (split (returntypes, ",%s*")) output = { typelist = returntypes, @@ -538,7 +526,6 @@ if _DEBUG.argcheck then return toomanymsg ("result", "from", fname, ...) end, badtype = function (...) resulterror (fname, ...) end, - maxvalues = maxreturns, permutations = permute (returntypes), } end From 0bad98e8ef42e64c854b9b04fb5ddc816b5c7ca3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 29 Jan 2015 23:19:58 +0000 Subject: [PATCH 500/703] refactor: store parameter dots property against each permuted list. Rather than deciding whether a typelist has continuation dots in the last position once for the entire table of permuted type match lists, add it to each permutation with continuation dots. * lib/std/debug.lua (markdots): Simplify. (permute): Call it for each new permutation row. (projectuniq): New function to select and de-deduplicate all values from the table of permuted type lists at a given index. (diagnose): Rewrite to work with new format permutations. (argscheck): Simplify accordingly. (match): Remove allargs parameter, and always check all arguments. (merge, HAS_DOTS): Remove. No longer used. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 165 ++++++++++++++++++++++------------------------ 1 file changed, 78 insertions(+), 87 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index dd2a619..7e3dda2 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -205,56 +205,37 @@ if _DEBUG.argcheck then end - --- Merge |-delimited type-specs, omitting duplicates. - -- @string ... type-specs - -- @treturn table list of merged and normalized type-specs - local function merge (...) - local i, t = 1, {} - for _, v in argpairs {...} do - v:gsub ("([^|]+)", function (m) t[i] = m; i = i + 1 end) - end - return normalize (t) - end - - - -- Constant marker for whether to process ellipsis value. - local HAS_DOTS = math.huge - - --- Strip trailing ellipsis from final argument if any, storing maximum -- number of values that can be matched directly in `t.maxvalues`. -- @tparam table t table to act on -- @treturn table *t* with ellipsis stripped and maxvalues field set - local function markdots (t) - local last = t[#t] - t.maxvalues = #t - if last then - t[#t] = last:gsub ("%.%.%.(%]?)$", function (bracket) - t.maxvalues = HAS_DOTS - return bracket - end) - end - return t + local function markdots (t, v) + return (v:gsub ("%.%.%.$", function () t.dots = true return "" end)) end --- Calculate permutations of type lists with and without [optionals]. - -- @tparam table typelist a list of expected types by argument position + -- @tparam table t a list of expected types by argument position -- @treturn table set of possible type lists - local function permute (typelist) + local function permute (t) + if t[#t] then t[#t] = t[#t]:gsub ("%]%.%.%.$", "...]") end + local p = {{}} - for i, v in ipairs (typelist) do - local opt = v:match "%[(.+)%]" - if opt == nil then + for i, v in ipairs (t) do + local optional = v:match "%[(.+)%]" + + if optional == nil then -- Append non-optional type-spec to each permutation. - for b = 1, len (p) do insert (p[b], v) end + for b = 1, #p do + insert (p[b], markdots (p[b], v)) + end else -- Duplicate all existing permutations, and add optional type-spec -- to the unduplicated permutations. - local o = len (p) + local o = #p for b = 1, o do p[b + o] = copy (p[b]) - insert (p[b], opt) + insert (p[b], markdots (p[b], optional)) end end end @@ -262,22 +243,19 @@ if _DEBUG.argcheck then end - --- Return index of the first mismatch between types and args, or `nil`. - -- @tparam table types a list of expected types by argument position - -- @tparam table args a table of arguments to compare - -- @tparam boolean allargs whether to match all arguments - -- @treturn int|nil position of first mismatch in *types* - local function match (types, args, allargs) - local typec, argc = len (types), maxn (args) - for i = 1, typec do - local ok = pcall (argcheck, "pcall", i, types[i], args[i]) + --- Return index of the first mismatch between types and values, or `nil`. + -- @tparam table typelist a list of expected types + -- @tparam table valuelist a table of arguments to compare + -- @treturn int|nil position of first mismatch in *typelist* + local function match (typelist, valuelist) + local n = #typelist + for i = 1, n do -- normal parameters + local ok = pcall (argcheck, "pcall", i, typelist[i], valuelist[i]) if not ok then return i end end - if allargs then - for i = typec + 1, argc do - local ok = pcall (argcheck, name, i, types[typec], args[i]) - if not ok then return i end - end + for i = n + 1, maxn (valuelist) do -- additional values against final type + local ok = pcall (argcheck, "pcall", i, typelist[n], valuelist[i]) + if not ok then return i end end end @@ -396,6 +374,26 @@ if _DEBUG.argcheck then end + local function projectuniq (fkey, tt) + -- project + local t = {} + for _, u in ipairs (tt) do + t[#t + 1] = u[fkey] + end + + -- split and remove duplicates + local r, s = {}, {} + for _, e in ipairs (t) do + for _, v in ipairs (normalize (split (e, "|"))) do + if s[v] == nil then + r[#r + 1], s[v] = v, true + end + end + end + return r + end + + local function empty (t) return not next (t) end -- Pattern to normalize: [types...] to [types]... @@ -405,58 +403,53 @@ if _DEBUG.argcheck then -- @tparam table valuelist normalized list of actual values to be checked -- @tparam table argt table of precalculated values and handler functiens local function diagnose (valuelist, argt) - local typelist, permutations = argt.typelist, argt.permutations - local maxvalues = typelist.maxvalues + local permutations = argt.permutations - local maxtypes, bestmismatch, listlen = #typelist, 0, 0 - for i, list in ipairs (permutations) do - local allargs = maxvalues == HAS_DOTS or (empty (list) and #permutations > 1) - local mismatch = match (list, valuelist, allargs) + local bestmismatch, t = 0 + for i, typelist in ipairs (permutations) do + local mismatch = match (typelist, valuelist) if mismatch == nil then - bestmismatch, listlen = nil, #list - break -- every *valuelist* matched types from this *list* + bestmismatch, t = nil, nil + break -- every *valuelist* matched types from this *typelist* elseif mismatch > bestmismatch then - bestmismatch, listlen = mismatch, #list + bestmismatch, t = mismatch, permutations[i] end end if bestmismatch ~= nil then -- Report an error for all possible types at bestmismatch index. local i, expected = bestmismatch - if maxvalues == HAS_DOTS and i >= maxtypes then - -- remove [] and ... before normalization - local last = (typelist[maxtypes] or ""):match (last_pat) - expected = normalize (split (last or typelist[maxtypes], "|")) + if t.dots and i > #t then + expected = normalize (split (t[#t], "|")) else - local tables = {} - for _, list in ipairs (permutations) do - if list[i] then - insert (tables, list[i]) - end - end - expected = merge (unpack (tables)) + expected = projectuniq (i, permutations) end - -- For "table of things", check all elements are a thing too. + -- This relies on the `permute()` algorithm leaving the longest + -- possible permutation (with dots if necessary) at permutations[1]. + local typelist = permutations[1] + + -- For "container of things", check all elements are a thing too. if typelist[i] then - local check, contents = typelist[i]:match "^(%S+) of (%S-)s?$" - if contents and type (valuelist[i]) == "table" then - for k, v in pairs (valuelist[i]) do - if not checktype (contents, v) then - argt.badtype (i, formaterror (expected, v, k), 3) - end - end - end + local check, contents = typelist[i]:match "^(%S+) of (%S-)s?$" + if contents and type (valuelist[i]) == "table" then + for k, v in pairs (valuelist[i]) do + if not checktype (contents, v) then + argt.badtype (i, formaterror (expected, v, k), 3) + end + end + end end -- Otherwise the argument type itself was mismatched. - argt.badtype (i, formaterror (expected, valuelist[i]), 3) + if t.dots or #t >= maxn (valuelist) then + argt.badtype (i, formaterror (expected, valuelist[i]), 3) + end end - local n = maxn (valuelist) - local max = maxvalues == HAS_DOTS and maxvalues or math.min (maxvalues, listlen) - if n > max then - error (argt.badcount (max, n), 3) + local n, t = maxn (valuelist), t or permutations[1] + if t and t.dots == nil and n > #t then + error (argt.badcount (#t, n), 3) end end @@ -498,16 +491,15 @@ if _DEBUG.argcheck then -- Parse "fname (argtype, argtype, argtype...)". local fname, argtypes = decl:match (args_pat) if argtypes == "" then - argtypes = markdots {} + argtypes = {} elseif argtypes then - argtypes = markdots (split (argtypes, ",%s*")) + argtypes = split (argtypes, ",%s*") else fname = decl:match "([%w_][%.%d%w_]*)" end -- Precalculate vtables once to make multiple calls faster. local input, output = { - typelist = argtypes, badcount = function (...) return toomanymsg ("argument", "to", fname, ...) end, @@ -518,10 +510,9 @@ if _DEBUG.argcheck then -- Parse "... => returntype, returntype, returntype...". local returntypes = decl:match "=>%s*(.+)%s*$" if returntypes then - returntypes = markdots (split (returntypes, ",%s*")) + returntypes = split (returntypes, ",%s*") output = { - typelist = returntypes, badcount = function (...) return toomanymsg ("result", "from", fname, ...) end, From c3b34cc6a708e69ca1cde597dd0ede4f8a20d4f1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 00:11:52 +0000 Subject: [PATCH 501/703] debug: support variant return type lists with argscheck. Sometimes we need to say `returns an int or nil,errmsg', the syntax is `=> int or nil, string`, because `=> ?int, string` will wrongly accept, say, `3, "oh noes!"`. * specs/debug_spec.yaml (argscheck): Specify behaviours with variant return type lists. * lib/std/debug.lua (argscheck): Parse all variants and add them to the accepted type list permutations table. Update LDocs. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++-- lib/std/debug.lua | 19 +++++++++++++++++-- specs/debug_spec.yaml | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9276e23..6d1795e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -25,12 +25,12 @@ specified as: ```lua - checkedfn = argscheck ("fname (?any...) => nil, string", fname) + 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. + type specification. Alternative result type groups are separated by "or". ### Deprecations diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 7e3dda2..9c50942 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -510,14 +510,24 @@ if _DEBUG.argcheck then -- Parse "... => returntype, returntype, returntype...". local returntypes = decl:match "=>%s*(.+)%s*$" if returntypes then - returntypes = split (returntypes, ",%s*") + local i, permutations = 0, {} + for _, group in ipairs (split (returntypes, "%s+or%s+")) do + returntypes = split (group, ",%s*") + for _, t in ipairs (permute (returntypes)) do + i = i + 1 + permutations[i] = t + end + end + + -- Ensure the longest permutation is first in the list. + table.sort (permutations, function (a, b) return #a > #b end) output = { badcount = function (...) return toomanymsg ("result", "from", fname, ...) end, badtype = function (...) resulterror (fname, ...) end, - permutations = permute (returntypes), + permutations = permutations, } end @@ -712,6 +722,11 @@ M = { -- -- len = argscheck ("string.len (string) => int", string.len) -- + -- Additionally, variant return type lists can be listed like this: + -- + -- open = argscheck ("io.open (string, ?string) => file or nil, string", + -- io.open) + -- -- @function argscheck -- @string decl function type declaration string -- @func inner function to wrap with argument checking diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index c3583f8..ee7536c 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -766,6 +766,17 @@ specify std.debug: - it accepts correct results: expect ({wrapped {1}}).to_equal {{1}} + - context with variant single return value function: + - before: + wrapped = f ("inner (?any...) => int or nil", id) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, nop)).to_raise (badresult (2)) + - it accepts correct result types: + expect ({wrapped ()}).to_equal {} + expect ({wrapped (1)}).to_equal {1} + - context when checking multi-return value function: - before: wrapped = f ("inner (?any...) => int, string", id) @@ -794,6 +805,36 @@ specify std.debug: expect ({wrapped (1, "foo")}).to_equal {1, "foo"} expect ({wrapped (nil, "foo")}).to_equal {[2] = "foo"} + - context when checking variant multi-return value function: + - before: + wrapped = f ("inner (?any...) => int, string or string", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "int or string")) + expect (wrapped (1)).to_raise (badresult (2, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", nop)).to_raise (badresult (3)) + - it accepts correct result types: + expect ({wrapped ("two")}).to_equal {"two"} + expect ({wrapped (1, "two")}).to_equal {1, "two"} + + - context when checking variant nil,errmsg pattern function: + - before: + wrapped = f ("inner (?any...) => int, string or nil, string", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (2, "string")) + expect (wrapped (1)).to_raise (badresult (2, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) + expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", nop)).to_raise (badresult (3)) + expect (wrapped (nil, "errmsg", nop)).to_raise (badresult (3)) + - it accepts correct result types: + expect ({wrapped (1, "two")}).to_equal {1, "two"} + expect ({wrapped (nil, "errmsg")}).to_equal {[2] = "errmsg"} + - context when checking optional multi-return value function: - before: wrapped = f ("inner (?any...) => [int], string", id) From e86c6c05f6b58ae0f195c8bd034ac11df5b74153 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 13:59:55 +0000 Subject: [PATCH 502/703] debug: unpack to maxn (t) for equivalence between Lua versions. luajit alone stops unpacking at the first nil valued index, so we have to pass explicit limits to make it behave like Lua 5.1 through 5.3. * lib/std/debug.lua (argscheck): unpack from 1 to maxn (results). * specs/debug_spec.yaml (argscheck): Adjust id implementation to calculate maximum numeric index and unpack all results up to that value. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 2 +- specs/debug_spec.yaml | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 9c50942..8af5085 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -547,7 +547,7 @@ if _DEBUG.argcheck then diagnose (results, output) end - return unpack (results) + return unpack (results, 1, maxn (results)) end end diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index ee7536c..c59a4b1 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -579,7 +579,13 @@ specify std.debug: wrapped = f ("inner ()", mkmagic) _, badarg, badresult = init (M, "", "inner") - id = function (...) return (table.unpack or unpack) {...} end + id = function (...) + local n, argt = 0, {...} + for k in pairs (argt) do + if type (k) == "number" and k > n then n = k end + end + return (table.unpack or unpack) (argt, 1, n) + end - it returns the wrapped function: expect (wrapped).not_to_be (inner) From c25cfe92df84a2b0562c9fbcf7d52d2004c53e8c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 15:49:14 +0000 Subject: [PATCH 503/703] table: new unpack method that behaves consistently across Lua versions. * specs/table_spec.yaml (unpack): Specify behaviours for unpack, especially when dealing with holes. * lib/std/base.lua (unpack): Wrapper for the core function, but defaulting the final index argument to table.maxn. * lib/std/table.lua (unpack): Export from here. Add LDocs. * lib/std/debug.lua, lib/std/functional.lua, lib/std/list.lua: Use safer base.unpack. * specs/spec_helper.lua (maxn, unpack): Make safe versions available to spec examples. * specs/debug_spec.yaml: Simplify id implementation. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++++ lib/std/base.lua | 27 +++++++++++++++++---------- lib/std/debug.lua | 2 +- lib/std/functional.lua | 5 ++--- lib/std/list.lua | 2 +- lib/std/package.lua | 5 ++--- lib/std/table.lua | 9 +++++++++ specs/debug_spec.yaml | 8 +------- specs/spec_helper.lua | 22 +++++++++++++++++++++- specs/table_spec.yaml | 19 ++++++++++++++++++- 10 files changed, 76 insertions(+), 27 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6d1795e..cc2b474 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,6 +32,10 @@ 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`. diff --git a/lib/std/base.lua b/lib/std/base.lua index d8c7dcb..11e9dd9 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -25,7 +25,6 @@ local dirsep = string.match (package.config, "^(%S+)\n") local loadstring = rawget (_G, "loadstring") or load -local unpack = table.unpack or unpack local function argerror (name, i, extramsg, level) @@ -86,6 +85,22 @@ local function ipairs (l) end +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 + + +local _unpack = table.unpack or unpack + +local function unpack (t, i, j) + return _unpack (t, i or 1, j or maxn (t)) +end + + local function collect (ifn, ...) local argt = {...} if not callable (ifn) then @@ -248,15 +263,6 @@ local function leaves (it, tr) end -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 - - local function merge (dest, src) for k, v in pairs (src) do dest[k] = dest[k] or v end return dest @@ -442,6 +448,7 @@ return { last = last, len = len, maxn = maxn, + unpack = unpack, -- tree.lua -- leaves = leaves, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 8af5085..916ef4d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -34,10 +34,10 @@ local base = require "std.base" local _DEBUG = debug_init._DEBUG local argerror = base.argerror +local unpack = base.unpack local split, tostring = base.split, base.tostring local insert, last, len, maxn = base.insert, base.last, base.len, base.maxn local ipairs, pairs = base.ipairs, base.pairs -local unpack = table.unpack or unpack local M diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 10ffbaf..c9ce357 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -11,11 +11,10 @@ local base = require "std.base" local debug = require "std.debug" -local ielems, ipairs, ireverse, len, pairs = - base.ielems, base.ipairs, base.ireverse, base.len, base.pairs +local ielems, ipairs, ireverse, len, pairs, unpack = + base.ielems, base.ipairs, base.ireverse, base.len, base.pairs, base.unpack local callable, reduce = base.callable, base.reduce local loadstring = loadstring or load -local unpack = table.unpack or unpack local function bind (fn, ...) diff --git a/lib/std/list.lua b/lib/std/list.lua index dd86f68..fc169e3 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -21,7 +21,7 @@ local ipairs, pairs = base.ipairs, base.pairs local len = base.len local compare = base.compare local prototype = base.prototype -local unpack = table.unpack or unpack +local unpack = base.unpack local M, List diff --git a/lib/std/package.lua b/lib/std/package.lua index 9700a20..55c0309 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -16,7 +16,8 @@ local debug = require "std.debug" local catfile, escape_pattern, invert = base.catfile, base.escape_pattern, base.invert -local ipairs, pairs, split = base.ipairs, base.pairs, base.split +local ipairs, pairs, split, unpack = + base.ipairs, base.pairs, base.split, base.unpack local M @@ -80,8 +81,6 @@ local function normalize (...) end -local unpack = table.unpack or unpack - local function insert (pathstrings, ...) local paths = split (pathstrings, pathsep) table.insert (paths, ...) diff --git a/lib/std/table.lua b/lib/std/table.lua index f2a718f..e12d02e 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -441,6 +441,15 @@ M = { -- @usage table.concat (sort (object)) sort = X ("sort (table, ?function)", sort), + --- Enhance core *table.unpack* to always unpack up to `maxn (t)`. + -- @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)", base.unpack), + --- Make the list of values of a table. -- @function values -- @tparam table t any table diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index c59a4b1..3ab069e 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -579,13 +579,7 @@ specify std.debug: wrapped = f ("inner ()", mkmagic) _, badarg, badresult = init (M, "", "inner") - id = function (...) - local n, argt = 0, {...} - for k in pairs (argt) do - if type (k) == "number" and k > n then n = k end - end - return (table.unpack or unpack) (argt, 1, n) - end + id = function (...) return unpack {...} end - it returns the wrapped function: expect (wrapped).not_to_be (inner) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 7209106..9c60717 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -3,7 +3,6 @@ local hell = require "specl.shell" local std = require "specl.std" badargs = require "specl.badargs" -unpack = table.unpack or unpack local top_srcdir = os.getenv "top_srcdir" or "." local top_builddir = os.getenv "top_builddir" or "." @@ -26,6 +25,27 @@ local LUA = os.getenv "LUA" or "lua" setdebug = require "std.debug"._setdebug +-- 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 + + +-- 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, i or 1, j or maxn (t)) +end + + -- In case we're not using a bleeding edge release of Specl... badargs.result = badargs.result or function (fname, i, want, got) if want == nil then i, want = i - 1, i end -- numbers only for narg error diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index c6812c3..6ff2cad 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -7,7 +7,8 @@ before: | "enpair", "flatten", "insert", "invert", "keys", "len", "maxn", "merge", "merge_select", "monkey_patch", "new", "okeys", "pack", "project", - "remove", "shape", "size", "sort", "values" } + "remove", "shape", "size", "sort", "unpack", + "values" } deprecations = { "clone_rename", "metamethod", "ripairs", "totable" } M = require "std.table" @@ -734,6 +735,22 @@ specify std.table: expect (f (object)).to_be (t) +- 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 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} } From f2f9065f5fa3e62cef7bebb953b268349dfed0b6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 16:13:19 +0000 Subject: [PATCH 504/703] configury: bump release version to 41.1.0. * configure.ac (AC_INIT): Bump release version to 41.1.0. Signed-off-by: Gary V. Vaughan --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index fe6dc3d..2037fe5 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [41.0.0], [http://github.com/lua-stdlib/lua-stdlib/issues]) +AC_INIT([stdlib], [41.1.0], [http://github.com/lua-stdlib/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) From a761361f4f2a8f17765d789b0b76b719e8072fd1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 16:25:30 +0000 Subject: [PATCH 505/703] slingshot: sync with upstream. * slingshot: Sync with upstream. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- slingshot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 347a624..14dd40e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -107,7 +107,7 @@ install: fi' # Build from rockspec. - - export ROCKSPEC=stdlib-41.0.0-1.rockspec + - export ROCKSPEC=stdlib-41.1.0-1.rockspec - 'test -f "$ROCKSPEC" || ROCKSPEC=stdlib-git-1.rockspec' - sudo luarocks make $ROCKSPEC LUA="$LUA" diff --git a/slingshot b/slingshot index c4015aa..2070c2a 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit c4015aa6fdf174f8e6517b7213b3927224dbf952 +Subproject commit 2070c2ac7fbf5344aa13e0ec6e5eb6eb8e75066d From 474c3f33dc09f53211c69946d197b091c7aeaeb2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 17:51:03 +0000 Subject: [PATCH 506/703] package: normalize leaves valid /../.. sequences unmolested. * specs/package_spec.yaml (normalize): Add examples of correct behaviour with multiple .. directories. * lib/std/package.lua (normalize): Detect redundant .. components correctly, and only remove them when it's safe to do so. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 2 ++ lib/std/package.lua | 27 ++++++++++++++++++++++++--- specs/package_spec.yaml | 8 ++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index cc2b474..b747e84 100644 --- a/NEWS.md +++ b/NEWS.md @@ -56,6 +56,8 @@ 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 diff --git a/lib/std/package.lua b/lib/std/package.lua index 55c0309..562c531 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -66,11 +66,32 @@ local function normalize (...) gsub (catfile ("^[^", "]"), catfile (".", "%0")): gsub (catfile ("", "%.", ""), dirsep): gsub (catfile ("", "%.$"), ""): - gsub (catfile ("", "[^", "]+", "%.%.", ""), dirsep): - gsub (catfile ("", "[^", "]+", "%.%.$"), ""): - gsub (catfile ("%.", "%..", ""), catfile ("..", "")): + gsub (catfile ("^%.", "%..", ""), catfile ("..", "")): gsub (catfile ("", "$"), "") + -- Carefully remove redundant /foo/../ matches. + repeat + local again = false + path = path:gsub (catfile ("", "([^", "]+)", "%.%.", ""), + function (dir1) + if dir1 == ".." then -- don't remove /../../ + return catfile ("", "..", "..", "") + else + again = true + return dirsep + end + end): + gsub (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 diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index 2a4832e..170cd5b 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -123,6 +123,14 @@ specify std.package: 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 path_mark: From de61ecd23cc7703eb665fe008ad67d97e9ce128a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 17:57:05 +0000 Subject: [PATCH 507/703] Release version 41.1.0 * NEWS.md: Record release date. --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index b747e84..3e608f8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # Stdlib NEWS - User visible changes -## Noteworthy changes in release ?.? (????-??-??) [?] +## Noteworthy changes in release 41.1.0 (2015-01-30) [stable] ### New features From 427629dee39043b29690ec0032270fb88764d34e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 17:57:56 +0000 Subject: [PATCH 508/703] maint: post-release administrivia. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.prev-version b/.prev-version index 6e90c74..9472133 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -41.0.0 +41.1.0 diff --git a/NEWS.md b/NEWS.md index 3e608f8..1c5557b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # Stdlib NEWS - User visible changes +## Noteworthy changes in release ?.? (????-??-??) [?] + + ## Noteworthy changes in release 41.1.0 (2015-01-30) [stable] ### New features From c96afddf47cedeb2b328ff8ddcf9ad3ca03b6751 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 30 Jan 2015 18:12:07 +0000 Subject: [PATCH 509/703] doc: add missing parameter LDoc for debug:markdots. * lib/std/debug.lua (markdots): Add missing LDocs for v parameter. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 916ef4d..c6512cd 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -208,6 +208,7 @@ if _DEBUG.argcheck then --- Strip trailing ellipsis from final argument if any, storing maximum -- number of values that can be matched directly in `t.maxvalues`. -- @tparam table t table to act on + -- @string v element added to *t*, to match against ... suffix -- @treturn table *t* with ellipsis stripped and maxvalues field set local function markdots (t, v) return (v:gsub ("%.%.%.$", function () t.dots = true return "" end)) From e005123e076b3cbc30c349874349bfb24fd0618c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 31 Jan 2015 12:40:17 +0000 Subject: [PATCH 510/703] std: fix an infinite loop in std.barrel() with Lua 5.3. Close #94 * lib/std/base.lua (maxn): This function is called from any function wrapped by debug.argscheck, which calls debug.diagnose. Calling std.barrel monkey-patches the global pairs function, so we can't call that from here, otherwise it will in turn use the monkey-patched pairs, which calls debug.diagnose to check for argument errors before the original maxn call has finished. Reported by Reuben Thomas Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 11e9dd9..aa76597 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -85,9 +85,11 @@ local function ipairs (l) end +local _pairs = pairs + local maxn = table.maxn or function (t) local n = 0 - for k in pairs (t) do + for k in _pairs (t) do if type (k) == "number" and k > n then n = k end end return n From 3e5735575c8d26f11f31c86572c7b4f2bcfbeda8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 31 Jan 2015 12:48:00 +0000 Subject: [PATCH 511/703] configury: bump release version to 41.1.1. * configure.ac (AC_INIT): Bump release version to 41.1.1. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- NEWS.md | 5 +++++ configure.ac | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 14dd40e..6d1e898 100644 --- a/.travis.yml +++ b/.travis.yml @@ -107,7 +107,7 @@ install: fi' # Build from rockspec. - - export ROCKSPEC=stdlib-41.1.0-1.rockspec + - export ROCKSPEC=stdlib-41.1.1-1.rockspec - 'test -f "$ROCKSPEC" || ROCKSPEC=stdlib-git-1.rockspec' - sudo luarocks make $ROCKSPEC LUA="$LUA" diff --git a/NEWS.md b/NEWS.md index 1c5557b..e4f9211 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,11 @@ ## Noteworthy changes in release ?.? (????-??-??) [?] +### 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] diff --git a/configure.ac b/configure.ac index 2037fe5..545f6a8 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [41.1.0], [http://github.com/lua-stdlib/lua-stdlib/issues]) +AC_INIT([stdlib], [41.1.1], [http://github.com/lua-stdlib/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) From fefcce889f6d6409fd0d49b47dc883ab508bb22e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 31 Jan 2015 12:50:00 +0000 Subject: [PATCH 512/703] Release version 41.1.1 * NEWS.md: Record release date. --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index e4f9211..723f57f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # Stdlib NEWS - User visible changes -## Noteworthy changes in release ?.? (????-??-??) [?] +## Noteworthy changes in release 41.1.1 (2015-01-31) [stable] ### Bug fixes From d99c9a34e2d5ee8ef8ad0073f6efe7c206daf63e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 31 Jan 2015 12:50:47 +0000 Subject: [PATCH 513/703] maint: post-release administrivia. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.prev-version b/.prev-version index 9472133..f0384dc 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -41.1.0 +41.1.1 diff --git a/NEWS.md b/NEWS.md index 723f57f..2df6063 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # Stdlib NEWS - User visible changes +## Noteworthy changes in release ?.? (????-??-??) [?] + + ## Noteworthy changes in release 41.1.1 (2015-01-31) [stable] ### Bug fixes From 10f7c8b16369f22fa54ddb02dabf77ed33e6ed23 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 6 Feb 2015 14:54:41 +0000 Subject: [PATCH 514/703] list: std.ireverse is not a full replacement for list.reverse. * lib/std/list.lua (reverse): Describe a full replacement with functional.compose in deprecation warning message. Reported by Reuben Thomas Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index fc169e3..ad04c06 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -438,9 +438,9 @@ M.relems = DEPRECATED ("41", "'std.list.relems'", m.relems = DEPRECATED ("41", "'std.list:relems'", relems) M.reverse = DEPRECATED ("41", "'std.list.reverse'", - "use 'std.ireverse' instead", reverse) + "compose 'std.list' and 'std.ireverse' instead", reverse) m.reverse = DEPRECATED ("41", "'std.list:reverse'", - "use 'std.ireverse' instead", reverse) + "compose 'std.list' and 'std.ireverse' instead", reverse) M.shape = DEPRECATED ("41", "'std.list.shape'", "use 'std.table.shape' instead", shape) From 5f51c2ed6d5e6c22d2ae2a82f0491104cc8c0054 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Wed, 25 Feb 2015 21:56:41 +0000 Subject: [PATCH 515/703] base: require does not work with non-string module version fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simple fix: run “tostring” on the version field. It may be desired to refuse certain types. The motivating case here is that std.optparse has version set to number 0. --- lib/std/base.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index aa76597..53e41f7 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -373,7 +373,7 @@ local _require = require local function require (module, min, too_big, pattern) local m = _require (module) - local v = (m.version or m._VERSION or ""):match (pattern or "([%.%d]+)%D*$") + local v = tostring (m.version or m._VERSION or ""):match (pattern or "([%.%d]+)%D*$") if min then assert (vcompare (v, min) >= 0, "require '" .. module .. "' with at least version " .. min .. ", but found version " .. v) From a475aadf02ed339ddc6fa80c425cac391ad287a8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 26 Feb 2015 21:27:47 +0000 Subject: [PATCH 516/703] maint: fix .gitignore problems. Close #97. * .gitignore: Some versions of git disallow !re-include inside a directory that has been excluded. Add a trailing '/*' to support those versions. (/build-aux/config.ld.in): Re-include this source file. (/doc/config.ld): Remove exclusion of no longer generated file, now that config.ld is also kept in build-aux. Reported by Reuben Thomas Signed-off-by: Gary V. Vaughan --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c5299c8..1d2c4f3 100644 --- a/.gitignore +++ b/.gitignore @@ -12,13 +12,13 @@ /README /aclocal.m4 /autom4te.cache -/build-aux/ +/build-aux/* !/build-aux/sanity-cfg.mk +!/build-aux/config.ld.in /config.log /config.status /configure /doc/classes -/doc/config.ld /doc/files /doc/index.html /doc/ldoc.css From 65b993bc20bee377fe1833d0cfda22ccd57784eb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 31 Jan 2015 16:54:38 +0000 Subject: [PATCH 517/703] std: fix argscheck declaration for std.getmetamethod. * lib/std.lua.in (getmetamethod): This function works on anything the Lua can add a metatable to. Adjust LDocs and argscheck declaration to support that. * specs/std_spec.yaml (getmetamethod): Adjust badargs parameters accordingly. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 5 +++++ lib/std.lua.in | 8 ++++---- specs/std_spec.yaml | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2df6063..a81f337 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,11 @@ ## Noteworthy changes in release ?.? (????-??-??) [?] +### Bug fixes + + - `std.getmetamethod` no longer rejects non-table subjects when + `_DEBUG.argcheck` is set. + ## Noteworthy changes in release 41.1.1 (2015-01-31) [stable] diff --git a/lib/std.lua.in b/lib/std.lua.in index 3724ad3..c9effd1 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -176,13 +176,13 @@ M = { -- for e in rielems (l) do process (e) end ireverse = X ("ireverse (table)", base.ireverse), - --- Return named metamethod, if any, otherwis `nil`. + --- Return named metamethod, if any, otherwise `nil`. -- @function getmetamethod - -- @tparam table t table to get metamethod of - -- @string n name of metamethod to get + -- @param x item to act on + -- @string n name of metamethod to lookup -- @treturn function|nil metamethod function, or `nil` if no metamethod -- @usage lookup = std.getmetamethod (require "std.object", "__index") - getmetamethod = X ("getmetamethod (object|table, string)", base.getmetamethod), + getmetamethod = X ("getmetamethod (?any, string)", base.getmetamethod), --- Overwrite core methods and metamethods with `std` enhanced versions. -- diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index d119ad7..46eadb5 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -228,7 +228,7 @@ specify std: f = M.getmetamethod - context with bad arguments: - badargs.diagnose (f, "std.getmetamethod (object|table, string)") + badargs.diagnose (f, "std.getmetamethod (?any, string)") - context with a table: - before: From 4c4714ccdd56025271b1bb9d43eb61b17d8fc5d2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 14:13:48 +0000 Subject: [PATCH 518/703] std: new npairs and rnpairs iterators. * specs/std_spec.yaml (npairs, rnpairs): Specify correct behaviour for these new iterators. * lib/std/debug.lua (argpairs): Move from here... * lib/std/base.lua (npairs): ...to here. (rnpairs): New function. * lib/std.lua.in (npairs, rnpairs): Reexport from here. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 12 ++++++++++ lib/std.lua.in | 28 +++++++++++++++++++++++ lib/std/base.lua | 24 ++++++++++++++++++++ lib/std/debug.lua | 20 ----------------- specs/std_spec.yaml | 55 +++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 117 insertions(+), 22 deletions(-) diff --git a/NEWS.md b/NEWS.md index a81f337..58fcf0d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,18 @@ ## Noteworthy changes in release ?.? (????-??-??) [?] +### 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 + ``` + ### Bug fixes - `std.getmetamethod` no longer rejects non-table subjects when diff --git a/lib/std.lua.in b/lib/std.lua.in index c9effd1..925a750 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -151,6 +151,7 @@ M = { -- @treturn table *t*, the table being iterated over -- @treturn int *index*, the previous iteration index -- @see ielems + -- @see npairs -- @see pairs -- @usage -- -- length of sequence @@ -194,6 +195,18 @@ M = { -- @usage local std = require "std".monkey_patch () monkey_patch = X ("monkey_patch (?table)", monkey_patch), + --- Ordered iterator for integer keyed values. + -- Like ipairs, but does not stop until the largest integer key. + -- @function npairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table t + -- @see ipairs + -- @see rnpairs + -- @usage + -- for i,v in npairs {"one", nil, "three"} do ... end + npairs = X ("npairs (table)", base.npairs), + --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. -- @function pairs -- @tparam table t a table @@ -228,9 +241,24 @@ M = { -- @treturn function iterator function -- @treturn table *t* -- @treturn number `#t + 1` + -- @see ipairs + -- @see rnpairs -- @usage for i, v = ripairs (t) do ... end ripairs = X ("ripairs (table)", base.ripairs), + --- An iterator like npairs, but in reverse. + -- Apart from the order of the elments 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 + -- for i,v in rnpairs {"one", nil, "three"} do ... end + rnpairs = X ("rnpairs (table)", base.rnpairs), + --- Enhance core `tostring` to render table contents as a string. -- @function tostring -- @param x object to convert to string diff --git a/lib/std/base.lua b/lib/std/base.lua index 53e41f7..a18d3f4 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -271,6 +271,16 @@ local function merge (dest, src) end +local function npairs (t) + local i, n = 0, maxn (t) + return function (t) + i = i + 1 + if i <= n then return i, t[i] end + end, + t, i +end + + local function prototype (o) return (getmetatable (o) or {})._type or io.type (o) or type (o) end @@ -346,6 +356,18 @@ local function ripairs (t) end +local function rnpairs (t) + local oob = maxn (t) + 1 + + return function (t, n) + n = n - 1 + if n > 0 then + return n, t[n] + end + end, t, oob +end + + local function split (s, sep) local r, patt = {} if sep == "" then @@ -412,8 +434,10 @@ return { ielems = ielems, ipairs = ipairs, ireverse = ireverse, + npairs = npairs, pairs = pairs, ripairs = ripairs, + rnpairs = rnpairs, require = require, tostring = tostring, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index c6512cd..6d3346d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -185,26 +185,6 @@ if _DEBUG.argcheck then end - --- Ordered iterator for integer keyed values. - -- Like ipairs, but does not stop at the first nil value. - -- @tparam table t a table - -- @treturn function iterator function - -- @treturn table t - -- @usage - -- for i,v in argpairs {"one", nil, "three"} do print (i, v) end - local function argpairs (t) - local i, max = 0, 0 - for k in pairs (t) do - if type (k) == "number" and k > max then max = k end - end - return function (t) - i = i + 1 - if i <= max then return i, t[i] end - end, - t, true - end - - --- Strip trailing ellipsis from final argument if any, storing maximum -- number of values that can be matched directly in `t.maxvalues`. -- @tparam table t table to act on diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 46eadb5..32ec591 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -3,8 +3,9 @@ before: | global_table = "_G" exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", - "ielems", "ipairs", "ireverse", "monkey_patch", "pairs", - "require", "ripairs", "tostring", "version" } + "ielems", "ipairs", "ireverse", "monkey_patch", "npairs", + "pairs", "require", "ripairs", "rnpairs", "tostring", + "version" } -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -368,6 +369,31 @@ specify std: end +- 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 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 @@ -504,6 +530,31 @@ specify std: 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 works with the empty list: + t = {} + for k, v in f {} do t[k] = v end + expect (t).to_equal {} + + - describe tostring: - before: f = M.tostring From 70c55e740574e9466c3eb9e19604f4e95d9712fb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 01:25:43 +0000 Subject: [PATCH 519/703] functional: ensure bind propagates nil arguments. * specs/functional_spec.yaml (bind): Add example to show behaviours with nil arguments. * lib/std/functional.lua (bind): Simplify. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 2 ++ lib/std/functional.lua | 31 ++++++++++++++----------------- specs/functional_spec.yaml | 3 +++ 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/NEWS.md b/NEWS.md index 58fcf0d..f13934b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,6 +19,8 @@ - `std.getmetamethod` no longer rejects non-table subjects when `_DEBUG.argcheck` is set. + - `functional.bind` propagates nil valued arguments correctly. + ## Noteworthy changes in release 41.1.1 (2015-01-31) [stable] diff --git a/lib/std/functional.lua b/lib/std/functional.lua index c9ce357..c536d7f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -11,16 +11,17 @@ local base = require "std.base" local debug = require "std.debug" -local ielems, ipairs, ireverse, len, pairs, unpack = - base.ielems, base.ipairs, base.ireverse, base.len, base.pairs, base.unpack -local callable, reduce = base.callable, base.reduce +local ielems, ipairs, ireverse, npairs, pairs = + base.ielems, base.ipairs, base.ireverse, base.npairs, base.pairs +local callable, copy, len, reduce, unpack = + base.callable, base.copy, base.len, base.reduce, base.unpack local loadstring = loadstring or load local function bind (fn, ...) - local argt = {...} - if type (argt[1]) == "table" and argt[2] == nil then - argt = argt[1] + local bound = {...} + if type (bound[1]) == "table" and bound[2] == nil then + bound = bound[1] else io.stderr:write (debug.DEPRECATIONMSG ("39", "multi-argument 'std.functional.bind'", @@ -28,17 +29,13 @@ local function bind (fn, ...) end return function (...) - local arg = {} - for i, v in pairs (argt) do - arg[i] = v - end - local i = 1 - for _, v in ipairs {...} do - while arg[i] ~= nil do i = i + 1 end - arg[i] = v - end - return fn (unpack (arg)) - end + local argt, i = copy (bound), 1 + for _, v in npairs {...} do + while argt[i] ~= nil do i = i + 1 end + argt[i], i = v, i + 1 + end + return fn (unpack (argt)) + end end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index f4b8a27..e0eab5f 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -57,6 +57,9 @@ specify std.functional: expect (f (div, {100}) (25)).to_be (4) - it supports out of order extra arguments: expect (f (op.pow, {[2] = 3}) (2)).to_be (8) + - it propagates nil arguments correctly: + expect ({f (M.id, {[2]="b", [4]="d"}) (nil, 3, 5, 6, nil)}). + to_equal {nil, "b", 3, "d", 5, 6, nil} - it supports the legacy api: expect (f (math.min) (2, 3, 4)).to_be (2) expect (f (math.min, 1, 0) (2, 3, 4)).to_be (0) From 0ab71bbed29ace7dccd7efd48c34af0117834b58 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 01:25:43 +0000 Subject: [PATCH 520/703] functional: ensure compose propagates nil arguments. * specs/functional_spec.yaml (compose): Add examples to show behaviours with nil arguments. * lib/std/functional.lua (compose): Simplify. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 ++- lib/std/functional.lua | 18 +++++++----------- specs/functional_spec.yaml | 3 +++ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/NEWS.md b/NEWS.md index f13934b..73ad0e3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,7 +19,8 @@ - `std.getmetamethod` no longer rejects non-table subjects when `_DEBUG.argcheck` is set. - - `functional.bind` propagates nil valued arguments correctly. + - `functional.bind` and `functional.compose` propagate nil valued + arguments correctly. ## Noteworthy changes in release 41.1.1 (2015-01-31) [stable] diff --git a/lib/std/functional.lua b/lib/std/functional.lua index c536d7f..34335af 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -49,19 +49,15 @@ end local function compose (...) - local arg = {...} - local fns, n = arg, #arg - for i = 1, n do - local f = fns[i] - end + local fns = {...} return function (...) - local arg = {...} - for i = 1, n do - arg = {fns[i] (unpack (arg))} - end - return unpack (arg) - end + local argt = {...} + for _, fn in npairs (fns) do + argt = {fn (unpack (argt))} + end + return unpack (argt) + end end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index e0eab5f..6e3ac7c 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -153,6 +153,9 @@ specify std.functional: - it composes a single function correctly: expect (f (M.id) (1)).to_be (1) + - it propagates nil arguments correctly: + expect ({f (M.id) (1, nil, nil, 4)}).to_equal {1, nil, nil, 4} + expect ({f (M.id, M.id) (1, nil, nil, 4)}).to_equal {1, nil, nil, 4} - it composes functions in the correct order: expect (f (math.sin, math.cos) (1)). to_be (math.cos (math.sin (1))) From 0b49deaf1f94691fa807e7a11c4ee34c985234f9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 01:25:43 +0000 Subject: [PATCH 521/703] functional: ensure collect propagates nil arguments. * specs/functional_spec.yaml (collect): Add examples to show behaviours with nil arguments. * lib/std/functional.lua (collect): Use npairs as a default iterator. Detect iterator arity when collecting result table. * NEWS.md (Bug fixes): Update. (Incompatible changes): Note change of default iterator. Signed-off-by: Gary V. Vaughan --- NEWS.md | 9 +++++++-- lib/std/base.lua | 40 ++++++++++++++++++++++++-------------- lib/std/functional.lua | 2 +- specs/functional_spec.yaml | 6 ++++-- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/NEWS.md b/NEWS.md index 73ad0e3..1c4552d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,8 +19,13 @@ - `std.getmetamethod` no longer rejects non-table subjects when `_DEBUG.argcheck` is set. - - `functional.bind` and `functional.compose` propagate nil valued - arguments correctly. + - `functional.bind`, `functional.collect` and `functional.compose` + propagate nil valued arguments correctly. + +### 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] diff --git a/lib/std/base.lua b/lib/std/base.lua index a18d3f4..8b98f96 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -103,21 +103,6 @@ local function unpack (t, i, j) end -local function collect (ifn, ...) - local argt = {...} - if not callable (ifn) then - ifn, argt = ipairs, {ifn, ...} - end - - local r = {} - for k, v in ifn (unpack (argt)) do - if v == nil then k, v = #r + 1, k end - r[k] = v - end - return r -end - - local function compare (l, m) local lenl, lenm = len (l), len (m) for i = 1, math.min (lenl, lenm) do @@ -281,6 +266,31 @@ local function npairs (t) end +local function collect (ifn, ...) + local argt, r = {...}, {} + if not callable (ifn) then + ifn, argt = npairs, {ifn, ...} + end + + -- How many return values from ifn? + local arity = 1 + for e, v in ifn (unpack (argt)) do + if v then arity, r = 2, {} break end + -- Build an arity-1 result table on first pass... + r[#r + 1] = e + end + + if arity == 2 then + -- ...oops, it was arity-2 all along, start again! + for k, v in ifn (unpack (argt)) do + r[k] = v + end + end + + return r +end + + local function prototype (o) return (getmetatable (o) or {})._type or io.type (o) or type (o) end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 34335af..c7f3f2c 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -292,7 +292,7 @@ local M = { --- Collect the results of an iterator. -- @function collect - -- @func[opt=std.ipairs] ifn iterator function + -- @func[opt=std.npairs] ifn iterator function -- @param ... *ifn* arguments -- @treturn table of results from running *ifn* on *args* -- @see filter diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 6e3ac7c..4c4b764 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -140,8 +140,10 @@ specify std.functional: - it collects a table of key:value iterator results: t = {"first", second="two", last=3} expect (f (pairs, t)).to_equal (t) - - it defaults to ipairs iteration: - expect (f {1, 2, [5]=5, a="b", c="d"}).to_equal {1, 2} + - it propagates nil arguments correctly: + expect (f {"a", nil, nil, "d", "e"}).to_equal {"a", [4]="d", [5]="e"} + - it defaults to npairs iteration: + expect (f {1, 2, [5]=5, a="b", c="d"}).to_equal {1, 2, [5]=5} - describe compose: From 4fee8580c7736f2cff56f35033498841abbf1d04 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 01:25:43 +0000 Subject: [PATCH 522/703] functional: ensure filter propagates nil arguments. * specs/functional_spec.yaml (filter): Add example to show behaviour with nil arguments. * lib/std/functional.lua (filter): Detect iterator arity when collecting result table. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++-- lib/std/functional.lua | 38 ++++++++++++++++++++++++++++---------- specs/functional_spec.yaml | 5 ++++- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1c4552d..6e8da94 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,8 +19,8 @@ - `std.getmetamethod` no longer rejects non-table subjects when `_DEBUG.argcheck` is set. - - `functional.bind`, `functional.collect` and `functional.compose` - propagate nil valued arguments correctly. + - `functional.bind`, `functional.collect`, `functional.compose` and + `functional.filter` propagate nil valued arguments correctly. ### Incompatible changes diff --git a/lib/std/functional.lua b/lib/std/functional.lua index c7f3f2c..eb33a79 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -87,26 +87,44 @@ end local function filter (pfn, ifn, ...) - local argt = {...} + local argt, r = {...}, {} if not callable (ifn) then ifn, argt = pairs, {ifn, ...} end local nextfn, state, k = ifn (unpack (argt)) + local t = {nextfn (state, k)} -- table of iteration 1 + local arity = #t -- How many return values from ifn? + + if arity == 1 then + local v = t[1] + while v ~= nil do -- until iterator returns nil + if pfn (unpack (t)) then -- pass all iterator results to p + r[#r + 1] = v + end + + t = {nextfn (state, v)} -- maintain loop invariant + v = t[1] - local r = {} -- new results table - while t[1] ~= nil do -- until iterator returns nil - k = t[1] - if pfn (unpack (t)) then -- pass all iterator results to p - if t[2] ~= nil then - r[k] = t[2] -- k,v = t[1],t[2] - else - r[#r + 1] = k -- k,v = #r + 1,t[1] + if #t > 1 then -- unless we discover arity is not 1 after all + arity, r = #t, {} break end end - t = {nextfn (state, k)} -- maintain loop invariant end + + if arity > 1 then + -- No need to start over here, because either: + -- (i) arity was never 1, and the original value of t is correct + -- (ii) arity used to be 1, but we only consumed nil values, so the + -- current t with arity > 1 is the correct next value to use + while t[1] ~= nil do + local k = t[1] + if pfn (unpack (t)) then r[k] = t[2] end + t = {nextfn (state, k)} + end + end + return r end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 4c4b764..33eeebe 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -234,7 +234,7 @@ specify std.functional: - describe filter: - before: elements = {"a", "b", "c", "d", "e"} - inverse = require "std.table".invert (elements) + inverse = {a=1, b=2, c=3, d=4, e=5} f = M.filter @@ -246,6 +246,9 @@ specify std.functional: - it iterates through element keys: expect (f (M.id, base.ielems, elements)).to_equal {"a", "b", "c", "d", "e"} expect (f (M.id, base.elems, inverse)).to_contain.a_permutation_of {1, 2, 3, 4, 5} + - it propagates nil arguments correctly: + t = {"a", nil, nil, "d", "e"} + expect (f (M.id, base.npairs, t)).to_equal (t) - it passes all iteration result values to filter predicate: t = {} f (function (k, v) t[k] = v end, pairs, elements) From 86bc051100ceb3622ce7b92ac14b8ce6c00364ca Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 01:25:43 +0000 Subject: [PATCH 523/703] functional: ensure map propagates nil arguments. * specs/functional_spec.yaml (map): Add examples to show behaviours with nil arguments. * lib/std/functional.lua (map): Detect iterator arity when collecting result table. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 5 +++-- lib/std/functional.lua | 23 +++++++++++++++++------ specs/functional_spec.yaml | 7 ++++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6e8da94..b91db43 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,8 +19,9 @@ - `std.getmetamethod` no longer rejects non-table subjects when `_DEBUG.argcheck` is set. - - `functional.bind`, `functional.collect`, `functional.compose` and - `functional.filter` propagate nil valued arguments correctly. + - `functional.bind`, `functional.collect`, `functional.compose`, + `functional.filter` and `functional.map` propagate nil valued + arguments correctly. ### Incompatible changes diff --git a/lib/std/functional.lua b/lib/std/functional.lua index eb33a79..78c28ce 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -212,7 +212,7 @@ end, id) local function map (mapfn, ifn, ...) - local argt = {...} + local argt, r = {...}, {} if not callable (ifn) or not next (argt) then ifn, argt = pairs, {ifn, ...} end @@ -220,15 +220,26 @@ local function map (mapfn, ifn, ...) local nextfn, state, k = ifn (unpack (argt)) local mapargs = {nextfn (state, k)} - local r = {} + local arity = 1 while mapargs[1] ~= nil do - k = mapargs[1] local d, v = mapfn (unpack (mapargs)) - if v == nil then d, v = #r + 1, d end if v ~= nil then - r[d] = v + arity, r = 2, {} break + end + r[#r + 1] = d + mapargs = {nextfn (state, mapargs[1])} + end + + if arity > 1 then + -- No need to start over here, because either: + -- (i) arity was never 1, and the original value of mapargs is correct + -- (ii) arity used to be 1, but we only consumed nil values, so the + -- current mapargs with arity > 1 is the correct next value to use + while mapargs[1] ~= nil do + local k, v = mapfn (unpack (mapargs)) + r[k] = v + mapargs = {nextfn (state, mapargs[1])} end - mapargs = {nextfn (state, k)} end return r end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 33eeebe..abc3fa7 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -382,7 +382,7 @@ specify std.functional: - describe map: - before: elements = {"a", "b", "c", "d", "e"} - inverse = require "std.table".invert (elements) + inverse = {a=1, b=2, c=3, d=4, e=5} f = M.map @@ -394,6 +394,11 @@ specify std.functional: - it iterates through elements: expect (f (M.id, ipairs, elements)).to_equal (elements) expect (f (M.id, pairs, inverse)).to_contain.a_permutation_of (elements) + - it propagates nil arguments correctly: + t = {"a", nil, nil, "d", "e"} + expect (f (M.id, base.npairs, t)).to_equal (t) + t = {nil, nil, 3, 4} + expect (f (M.id, base.npairs, t)).to_equal (t) - it passes all iteration result values to map function: t = {} f (function (k, v) t[k] = v end, pairs, elements) From a14c5dfc1c372b18c3b9006002f138f56da74cb3 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Fri, 27 Feb 2015 16:59:33 +0000 Subject: [PATCH 524/703] std/io.lua: adjust catdir's return to 1 value --- lib/std/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/io.lua b/lib/std/io.lua index 85ac7aa..e086db6 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -149,7 +149,7 @@ M = { -- @see catfile -- @usage dirpath = catdir ("", "absolute", "directory") catdir = X ("catdir (string...)", function (...) - return table.concat ({...}, dirsep):gsub("^$", dirsep) + return (table.concat ({...}, dirsep):gsub("^$", dirsep)) end), --- Concatenate one or more directories and a filename into a path. From 5833d8ac8253c4f93598d93949b03b5a206d7957 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 17:38:43 +0000 Subject: [PATCH 525/703] functional: add examples to show reduce handling nil arguments. * specs/functional_spec.yaml (reduce): Add examples to show behaviours with nil arguments. Signed-off-by: Gary V. Vaughan --- specs/functional_spec.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index abc3fa7..8a64958 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -701,6 +701,12 @@ specify std.functional: - it calls a binary function over key:value iterator results: expect (f (op.sum, 2, base.ielems, {3})).to_be (2 + 3) expect (f (op.prod, 2, base.ielems, {3, 4})).to_be (2 * 3 * 4) + - it propagates nil arguments correctly: + function set (t, k, v) t[k] = tostring (v) return t end + expect (f (set, {}, base.npairs, {1, nil, nil, "a", false})). + to_equal {"1", "nil", "nil", "a", "false"} + expect (f (set, {}, base.npairs, {nil, nil, "3"})). + to_equal {"nil", "nil", "3"} - it reduces elements from left to right: expect (f (op.pow, 2, base.ielems, {3, 4})).to_be ((2 ^ 3) ^ 4) - it passes all iterator results to accumulator function: From ee1c7c69d3edcb4984a40fe4638c4029bb8c8c4b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 22 Feb 2015 16:46:49 +0000 Subject: [PATCH 526/703] functional: callable returns falsey for nil argument. * specs/functional_spec.yaml (callable): Specify correct behaviour for uncallable argument. * lib/std/functional.lua (callable): Don't raise an argument error for a nil argument, just return nil like any other uncallable. * NEWS.md (Bugs fixed): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/functional.lua | 2 +- specs/functional_spec.yaml | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index b91db43..348841d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,9 @@ `functional.filter` and `functional.map` propagate nil valued arguments correctly. + - `functional.callable` no longer raises an argument error when passed + a nil valued argument. + ### Incompatible changes - `functional.collect` uses `std.npairs` as a default iterator rather diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 78c28ce..b20a8bc 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -297,7 +297,7 @@ local M = { -- @return `true` if *x* can be called, otherwise `false` -- @usage -- if callable (functable) then functable (args) end - callable = X ("callable (any)", callable), + callable = X ("callable (?any)", callable), --- A rudimentary case statement. -- Match *with* against keys in *branches* table. diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 8a64958..619c251 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -87,6 +87,10 @@ specify std.functional: } do expect (f (v)).to_be (pcall (v, {}) and M.nop or nil) end + - it returns 'nil' for uncallable arguments: + expect (f ()).to_be (nil) + expect (f {}).to_be (nil) + expect (f "").to_be (nil) - describe case: - before: From 5ab1b7490f03b034fcbb77cbb9208fcae222c7cd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 20:10:25 +0000 Subject: [PATCH 527/703] debug: export portable getfenv and setfenv implementations. * specs/debug_spec.yaml (getfenv, setfenv): Add examples of behaviour of these functions. * lib/std/debug.lua (getfenv): Fix a bug that prevented unwrapping of functables on Lua 5.1 and LuaJIT. (getfenv, setfenv): Export as public APIs. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/debug.lua | 49 +++++++++++++++++++++++++++---------------- specs/debug_spec.yaml | 37 ++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/NEWS.md b/NEWS.md index 348841d..62c25b6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,9 @@ fn (nil, nil, 3) --> nil nil 3 ``` + - New `debug.getfenv` and `debug.setfenv` that work with Lua 5.2 and + 5.3. + ### Bug fixes - `std.getmetamethod` no longer rejects non-table subjects when diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 6d3346d..8162ae2 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -71,11 +71,6 @@ local function DEPRECATED (version, name, extramsg, fn) end ---- Extend `debug.setfenv` to unwrap functables correctly. --- @tparam function|functable fn target function --- @tparam table env new function environment --- @treturn function *fn* - local _setfenv = debug.setfenv local function setfenv (fn, env) @@ -105,24 +100,31 @@ local function setfenv (fn, env) end ---- Extend `debug.getfenv` to unwrap functables correctly. --- @tparam int|function|functable fn target function, or stack level --- @treturn table environment of *fn* -local getfenv = rawget (_G, "getfenv") or function (fn) +local _getfenv = rawget (_G, "getfenv") + +local getfenv = function (fn) -- Unwrap functable: if type (fn) == "table" then fn = fn.call or (getmetatable (fn) or {}).__call - elseif type (fn) == "number" then - fn = debug.getinfo (fn + 1, "f").func end - local name, env - local up = 0 - repeat - up = up + 1 - name, env = debug.getupvalue (fn, up) - until name == '_ENV' or name == nil - return env + if _getfenv then + return _getfenv (fn) + + else + fn = fn or 1 + if type (fn) == "number" then + fn = debug.getinfo (fn + 1, "f").func + end + + local name, env + local up = 0 + repeat + up = up + 1 + name, env = debug.getupvalue (fn, up) + until name == '_ENV' or name == nil + return env + end end @@ -718,6 +720,17 @@ M = { -- end) argscheck = argscheck, + --- Extend `debug.getfenv` to unwrap functables correctly. + -- @tparam int|function|functable fn target function, or stack level + -- @treturn table environment of *fn* + getfenv = getfenv, + + --- Extend `debug.setfenv` to unwrap functables correctly. + -- @tparam function|functable fn target function + -- @tparam table env new function environment + -- @treturn function *fn* + setfenv = setfenv, + --- Print a debugging message to `io.stderr`. -- Display arguments passed through `std.tostring` and separated by tab -- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 3ab069e..b2992bb 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,8 +4,8 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "argscheck", "say", "toomanyargmsg", "trace", - "_setdebug" } + "argscheck", "getfenv", "setfenv", "say", "toomanyargmsg", + "trace", "_setdebug" } M = require (this_module) @@ -933,6 +933,39 @@ specify std.debug: expect ({wrapped ("one", 2)}).to_equal {"one", 2} +- context function environments: + - before: + env = { + tostring = function (x) return '"' .. tostring (x) .. '"' end, + } + fn = function (x) return tostring (x) end + ft = setmetatable ({}, { __call = function (_, ...) return fn (...) end }) + + - describe getfenv: + - before: + f = M.getfenv + - it returns a table: + expect (type (f (fn))).to_be "table" + - it gets a function execution environment: + M.setfenv (fn, env) + expect (f (fn)).to_be (env) + - it understands functables: + M.setfenv (ft, env) + expect (f (ft)).to_be (env) + + - describe setfenv: + - before: + f = M.setfenv + - it returns the passed function: + expect (f (fn, env)).to_be (fn) + - it sets a function execution environment: + f (fn, env) + expect (fn (42)).to_be '"42"' + - it understands functables: + f (ft, env) + expect (fn (5)).to_be '"5"' + + - describe say: - before: | function mkwrap (k, v) From 5e7779cb9aaad3426c1c2bce85187635da09d738 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Mar 2015 12:59:30 +0000 Subject: [PATCH 528/703] debug: adjust numeric args for getfenv wrapper for Lua 5.1. * lib/std/debug.lua (getfenv): When calling core getfenv from our wrapper, make sure to add one to numeric args to compensate for the extra stack frame. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 8162ae2..8c81e1b 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -103,16 +103,18 @@ end local _getfenv = rawget (_G, "getfenv") local getfenv = function (fn) + fn = fn or 1 + -- Unwrap functable: if type (fn) == "table" then fn = fn.call or (getmetatable (fn) or {}).__call end if _getfenv then + if type (fn) == "number" then fn = fn + 1 end return _getfenv (fn) else - fn = fn or 1 if type (fn) == "number" then fn = debug.getinfo (fn + 1, "f").func end From 9d4c70ca08b2fc2c3d9159e1070321edc412922e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Mar 2015 13:03:15 +0000 Subject: [PATCH 529/703] slingshot: sync with upstream, for Lua 5.2.4 update. * slingshot: Sync with upstream. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 26 +++++++++++++++++++++----- slingshot | 2 +- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d1e898..0544bf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: c env: global: - _COMPILE="libtool --mode=compile --tag=CC gcc" - - _CFLAGS="-O2 -Wall -DLUA_COMPAT_ALL -DLUA_USE_LINUX" + - _CFLAGS="-O2 -Wall -DLUA_COMPAT_ALL -DLUA_COMPAT_5_2 -DLUA_USE_LINUX" - _INSTALL="libtool --mode=install install -p" - _LINK="libtool --mode=link --tag=CC gcc" - _LIBS="-lm -Wl,-E -ldl -lreadline" @@ -12,6 +12,10 @@ env: - bindir=$prefix/bin - incdir=$prefix/include - libdir=$prefix/lib + + - _inst=$TRAVIS_BUILD_DIR/_inst + - luadir=$_inst/share/lua + - luaexecdir=$_inst/lib/lua matrix: - LUA=lua5.3 - LUA=lua5.2 @@ -32,8 +36,8 @@ before_install: cd lua-5.3.0; fi' - 'if test lua5.2 = "$LUA"; then - curl http://www.lua.org/ftp/lua-5.2.3.tar.gz | tar xz; - cd lua-5.2.3; + curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz; + cd lua-5.2.4; fi' - 'if test lua5.1 = "$LUA"; then curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz; @@ -117,10 +121,22 @@ install: script: + # Reconfigure for in-tree test install. - test -f configure || ./bootstrap --verbose - - test -f Makefile || ./configure --disable-silent-rules LUA="$LUA" + - ./configure --prefix="$_inst" --disable-silent-rules LUA="$LUA" + + # Verify luarocks installation. + - make installcheck || make installcheck V=1 + + # Verify local build. - make - - make check V=1 + - make check || make check V=1 + + # Verify configured installation. + - make install prefix="$_inst" luadir="$luadir" luaexecdir="$luaexecdir" + - LUA_PATH="$luadir/?.lua;$luadir/?/init.lua;;" + LUA_CPATH="$luaexecdir/?.so;;" + make installcheck V=1 # Run sanity checks on CI server, ignoring buggy automakes. diff --git a/slingshot b/slingshot index 2070c2a..cb00818 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 2070c2ac7fbf5344aa13e0ec6e5eb6eb8e75066d +Subproject commit cb008181d0338f5d56432edf97f093b0ec02a018 From 40ec330d31c1909857cbfebe1641dbb3579f02b6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Mar 2015 13:24:31 +0000 Subject: [PATCH 530/703] debug: maintain stack frame depth parity with LuaJIT. * lib/std/debug.lua (getfenv): Be careful not to let the LuaJIT optimizer eliminate a stack frame, and mess up the depth count. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 8c81e1b..996139d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -112,7 +112,10 @@ local getfenv = function (fn) if _getfenv then if type (fn) == "number" then fn = fn + 1 end - return _getfenv (fn) + + -- Stack frame count is critical here, so ensure we don't optimise one + -- away in LuaJIT... + return _getfenv (fn), nil else if type (fn) == "number" then From 7b2ba4b08f475dfa50fb2b42e3b174bfb31d2411 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Mar 2015 18:42:47 +0000 Subject: [PATCH 531/703] slingshot: sync with upstream, for git rockspec fixes. * slingshot: Sync with upstream. * stdlib-git-1.rockspec: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 12 ++++++++---- slingshot | 2 +- stdlib-git-1.rockspec | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0544bf7..b48ff76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,10 +110,14 @@ install: sleep 1; touch configure; fi' - # Build from rockspec. - - export ROCKSPEC=stdlib-41.1.1-1.rockspec - - 'test -f "$ROCKSPEC" || ROCKSPEC=stdlib-git-1.rockspec' - - sudo luarocks make $ROCKSPEC LUA="$LUA" + # Build from rockspec, forcing uninstall of older luarocks installed + # above when testing the git rockspec, both for enforcing backwards + # compatibility by default, and for ease of maintenance. + - if test -f 'stdlib-41.1.1-1.rockspec'; then + sudo luarocks make 'stdlib-41.1.1-1.rockspec' LUA="$LUA"; + else + sudo luarocks make --force 'stdlib-git-1.rockspec' LUA="$LUA"; + fi # Clean up files created by root - sudo git clean -dfx diff --git a/slingshot b/slingshot index cb00818..f369b47 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit cb008181d0338f5d56432edf97f093b0ec02a018 +Subproject commit f369b4710ebfaff0db8e579dc7f6f3e0bb78c462 diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index e5ff2df..6ec4919 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -14,7 +14,7 @@ dependencies = { } external_dependencies = nil build = { - build_command = "./bootstrap && ./configure LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' --prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' --datarootdir='$(PREFIX)' && make clean all", + build_command = "LUA='$(LUA)' ./bootstrap && ./configure LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' --prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' --datarootdir='$(PREFIX)' && make clean all", copy_directories = {}, install_command = "make install luadir='$(LUADIR)' luaexecdir='$(LIBDIR)'", type = "command", From 079c289ed88ce517b3995c1fd1b9a4aa7810465f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 20:32:48 +0000 Subject: [PATCH 532/703] debug: improve argscheck patterns. * lib/std/debug.lua (args_pat, argscheck): Improve patterns. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 996139d..72f2d60 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -473,7 +473,7 @@ if _DEBUG.argcheck then -- Pattern to extract: fname ([types]?[, types]*) - local args_pat = "([%w_][%.%d%w_]*)%s+%(%s*(.*)%s*%)" + local args_pat = "^%s*([%w_][%.%d%w_]*)%s*%(%s*(.*)%s*%)" function argscheck (decl, inner) -- Parse "fname (argtype, argtype, argtype...)". @@ -481,9 +481,9 @@ if _DEBUG.argcheck then if argtypes == "" then argtypes = {} elseif argtypes then - argtypes = split (argtypes, ",%s*") + argtypes = split (argtypes, "%s*,%s*") else - fname = decl:match "([%w_][%.%d%w_]*)" + fname = decl:match "^%s*([%w_][%.%d%w_]*)" end -- Precalculate vtables once to make multiple calls faster. From aa025960c3fe758568fca7bde25da693a2ea09e5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Feb 2015 22:33:28 +0000 Subject: [PATCH 533/703] debug: export a new function to parse type table. Close #91 * lib/std/debug.lua (normalize): Rename from this... (typesplit): ...to this. Move to outer scope, so it will be visible even if _DEBUG.argcheck is false. ((markdots, permute, projectuniq): Move to outer scope. (parsetypes): New function. (parsetypes, typesplit): Export as public APIs. * specs/debug_spec.yaml (extendbase): Add parsetypes and typesplit. Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 199 +++++++++++++++++++++++------------------- specs/debug_spec.yaml | 4 +- 2 files changed, 113 insertions(+), 90 deletions(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 72f2d60..fb497a8 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -35,7 +35,7 @@ local base = require "std.base" local _DEBUG = debug_init._DEBUG local argerror = base.argerror local unpack = base.unpack -local split, tostring = base.split, base.tostring +local copy, split, tostring = base.copy, base.split, base.tostring local insert, last, len, maxn = base.insert, base.last, base.len, base.maxn local ipairs, pairs = base.ipairs, base.pairs @@ -139,11 +139,102 @@ local function toomanymsg (bad, to, name, expect, actual) end +--- Strip trailing ellipsis from final argument if any, storing maximum +-- number of values that can be matched directly in `t.maxvalues`. +-- @tparam table t table to act on +-- @string v element added to *t*, to match against ... suffix +-- @treturn table *t* with ellipsis stripped and maxvalues field set +local function markdots (t, v) + return (v:gsub ("%.%.%.$", function () t.dots = true return "" end)) +end + + +--- Calculate permutations of type lists with and without [optionals]. +-- @tparam table t a list of expected types by argument position +-- @treturn table set of possible type lists +local function permute (t) + if t[#t] then t[#t] = t[#t]:gsub ("%]%.%.%.$", "...]") end + + local p = {{}} + for i, v in ipairs (t) do + local optional = v:match "%[(.+)%]" + + if optional == nil then + -- Append non-optional type-spec to each permutation. + for b = 1, #p do + insert (p[b], markdots (p[b], v)) + end + else + -- Duplicate all existing permutations, and add optional type-spec + -- to the unduplicated permutations. + local o = #p + for b = 1, o do + p[b + o] = copy (p[b]) + insert (p[b], markdots (p[b], optional)) + end + end + end + return p +end + + +local function typesplit (types) + if type (types) == "string" then + types = split (types:gsub ("%s+or%s+", "|"), "%s*|%s*") + end + local r, seen, add_nil = {}, {}, false + for _, v in ipairs (types) do + local m = v:match "^%?(.+)$" + if m then + add_nil, v = true, m + end + if not seen[v] then + r[#r + 1] = v + seen[v] = true + end + end + if add_nil then + r[#r + 1] = "nil" + end + return r +end + + +local function projectuniq (fkey, tt) + -- project + local t = {} + for _, u in ipairs (tt) do + t[#t + 1] = u[fkey] + end + + -- split and remove duplicates + local r, s = {}, {} + for _, e in ipairs (t) do + for _, v in ipairs (typesplit (e)) do + if s[v] == nil then + r[#r + 1], s[v] = v, true + end + end + end + return r +end + + +local function parsetypes (types) + local r, permutations = {}, permute (types) + for i = 1, #permutations[1] do + r[i] = projectuniq (i, permutations) + end + r.dots = permutations[1].dots + return r +end + + local argcheck, argscheck -- forward declarations if _DEBUG.argcheck then - local copy, prototype = base.copy, base.prototype + local prototype = base.prototype local function resulterror (name, i, extramsg, level) level = level or 1 @@ -169,68 +260,6 @@ if _DEBUG.argcheck then end - --- Normalize a list of type names. - -- @tparam table t list of type names, leading "?" as required - -- @treturn table a new list with "?" stripped, "nil" appended if so, - -- and with duplicates stripped. - local function normalize (t) - local r, seen, add_nil = {}, {}, false - for _, v in ipairs (t) do - local m = v:match "^%?(.+)$" - if m then - add_nil, v = true, m - end - if not seen[v] then - r[#r + 1] = v - seen[v] = true - end - end - if add_nil then - r[#r + 1] = "nil" - end - return r - end - - - --- Strip trailing ellipsis from final argument if any, storing maximum - -- number of values that can be matched directly in `t.maxvalues`. - -- @tparam table t table to act on - -- @string v element added to *t*, to match against ... suffix - -- @treturn table *t* with ellipsis stripped and maxvalues field set - local function markdots (t, v) - return (v:gsub ("%.%.%.$", function () t.dots = true return "" end)) - end - - - --- Calculate permutations of type lists with and without [optionals]. - -- @tparam table t a list of expected types by argument position - -- @treturn table set of possible type lists - local function permute (t) - if t[#t] then t[#t] = t[#t]:gsub ("%]%.%.%.$", "...]") end - - local p = {{}} - for i, v in ipairs (t) do - local optional = v:match "%[(.+)%]" - - if optional == nil then - -- Append non-optional type-spec to each permutation. - for b = 1, #p do - insert (p[b], markdots (p[b], v)) - end - else - -- Duplicate all existing permutations, and add optional type-spec - -- to the unduplicated permutations. - local o = #p - for b = 1, o do - p[b + o] = copy (p[b]) - insert (p[b], markdots (p[b], optional)) - end - end - end - return p - end - - --- Return index of the first mismatch between types and values, or `nil`. -- @tparam table typelist a list of expected types -- @tparam table valuelist a table of arguments to compare @@ -362,33 +391,13 @@ if _DEBUG.argcheck then end - local function projectuniq (fkey, tt) - -- project - local t = {} - for _, u in ipairs (tt) do - t[#t + 1] = u[fkey] - end - - -- split and remove duplicates - local r, s = {}, {} - for _, e in ipairs (t) do - for _, v in ipairs (normalize (split (e, "|"))) do - if s[v] == nil then - r[#r + 1], s[v] = v, true - end - end - end - return r - end - - local function empty (t) return not next (t) end -- Pattern to normalize: [types...] to [types]... local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" --- Diagnose mismatches between *valuelist* and type *permutations*. - -- @tparam table valuelist normalized list of actual values to be checked + -- @tparam table valuelist list of actual values to be checked -- @tparam table argt table of precalculated values and handler functiens local function diagnose (valuelist, argt) local permutations = argt.permutations @@ -408,7 +417,7 @@ if _DEBUG.argcheck then -- Report an error for all possible types at bestmismatch index. local i, expected = bestmismatch if t.dots and i > #t then - expected = normalize (split (t[#t], "|")) + expected = typesplit (t[#t]) else expected = projectuniq (i, permutations) end @@ -444,7 +453,7 @@ if _DEBUG.argcheck then function argcheck (name, i, expected, actual, level) level = level or 2 - expected = normalize (split (expected, "|")) + expected = typesplit (expected) -- Check actual has one of the types from expected local ok = false @@ -705,7 +714,7 @@ M = { -- -- insert = argscheck ("table.insert (table, [int], ?any)", table.insert) -- - -- Similarly returt types can be checked with the same list syntax as + -- Similarly return types can be checked with the same list syntax as -- arguments: -- -- len = argscheck ("string.len (string) => int", string.len) @@ -730,6 +739,14 @@ M = { -- @treturn table environment of *fn* getfenv = getfenv, + --- Compact permutation list into a list of valid types at each argument. + -- Eliminate bracketed types by combining all valid types at each position + -- for all permutations of *typelist*. + -- @function parsetypes + -- @tparam list types a normalized list of type names + -- @treturn list valid types for each positional parameter + parsetypes = parsetypes, + --- Extend `debug.setfenv` to unwrap functables correctly. -- @tparam function|functable fn target function -- @tparam table env new function environment @@ -774,6 +791,12 @@ M = { -- local debug = require "std.debug" trace = trace, + --- Split a typespec string into a table of normalized type names. + -- @tparam string|table either `"?bool|:nometa"` or `{"boolean", ":nometa"}` + -- @treturn table a new list with duplicates removed and leading "?"s + -- replaced by a "nil" element + typesplit = typesplit, + -- Private: _setdebug = function (t) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index b2992bb..7cc719f 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,8 +4,8 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "argscheck", "getfenv", "setfenv", "say", "toomanyargmsg", - "trace", "_setdebug" } + "argscheck", "getfenv", "parsetypes", "setfenv", "say", + "toomanyargmsg", "typesplit", "trace", "_setdebug" } M = require (this_module) From 349bb9d998706cfc2a3958a17b44af548047d1a3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Feb 2015 13:10:24 +0000 Subject: [PATCH 534/703] debug: skip `self` typecheck when fname contains a colon. * specs/debug_spec.yaml (argscheck): Specify behaviours for colon delimited function name. * lib/std/debug.lua (argscheck): Drop `self` argument before checking types of remaining arguments when the function name contains a colon. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/debug.lua | 16 +++++++++++++--- specs/debug_spec.yaml | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 62c25b6..65221a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,9 @@ - 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. + ### Bug fixes - `std.getmetamethod` no longer rejects non-table subjects when diff --git a/lib/std/debug.lua b/lib/std/debug.lua index fb497a8..99637ea 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -482,7 +482,7 @@ if _DEBUG.argcheck then -- Pattern to extract: fname ([types]?[, types]*) - local args_pat = "^%s*([%w_][%.%d%w_]*)%s*%(%s*(.*)%s*%)" + local args_pat = "^%s*([%w_][%.%:%d%w_]*)%s*%(%s*(.*)%s*%)" function argscheck (decl, inner) -- Parse "fname (argtype, argtype, argtype...)". @@ -492,7 +492,7 @@ if _DEBUG.argcheck then elseif argtypes then argtypes = split (argtypes, "%s*,%s*") else - fname = decl:match "^%s*([%w_][%.%d%w_]*)" + fname = decl:match "^%s*([%w_][%.%:%d%w_]*)" end -- Precalculate vtables once to make multiple calls faster. @@ -529,8 +529,13 @@ if _DEBUG.argcheck then end return function (...) + local argt = {...} + + -- Don't check type of self if fname has a ':' in it. + if fname:find (":") then table.remove (argt, 1) end + -- Diagnose bad inputs. - diagnose ({...}, input) + diagnose (argt, input) -- Propagate outer environment to inner function. local x = math.max -- ??? getfenv(1) fails if we remove this ??? @@ -709,6 +714,11 @@ M = { -- -- format = argscheck ("string.format (string, ?any...)", string.format) -- + -- A colon in the function name indicates that the argument type list does + -- not have a type for `self`: + -- + -- format = argscheck ("string:format (?any...)", string.format) + -- -- If an argument can be omitted entirely, then put its type specification -- in square brackets: -- diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 7cc719f..c9a5331 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -737,6 +737,21 @@ specify std.debug: expect (wrapped ("foo", 1, 2)).to_be "MAGIC" expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + - context when omitting self type: + - before: + me = { + wrapped = f ("me:inner (string)", mkmagic) + } + _, badarg, badresult = init (M, "", "me:inner") + - it diagnoses missing arguments: + expect (me:wrapped ()).to_raise (badarg (1, "string")) + - it diagnoses wrong argument types: + expect (me:wrapped (false)).to_raise (badarg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (me:wrapped ("foo", false)).to_raise (badarg (2)) + - it accepts correct argument types: + expect (me:wrapped ("foo")).to_be "MAGIC" + - context with too many args: - before: wrapped = f ("inner ([string], int)", mkmagic) From 908aca72371bcaa5320063b6ef30f50fc972f5ea Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Feb 2015 17:22:58 +0000 Subject: [PATCH 535/703] debug: accept "bool" as a typename alias for "boolean". * specs/debug_spec.yaml: Add examples for behaviour when given "bool" in lieu of "boolean". * lib/std/debug.lua (formaterror, checktype): Accept "bool" as an alias for "boolean" in a type specification. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/debug.lua | 4 ++++ specs/debug_spec.yaml | 8 ++++++++ 3 files changed, 15 insertions(+) diff --git a/NEWS.md b/NEWS.md index 65221a3..076e383 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,6 +32,9 @@ - `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. + ### Incompatible changes - `functional.collect` uses `std.npairs` as a default iterator rather diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 99637ea..5d55953 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -310,6 +310,8 @@ if _DEBUG.argcheck then for i, v in ipairs (expectedtypes) do if v == "func" then t[i] = "function" + elseif v == "bool" then + t[i] = "boolean" elseif v == "any" then t[i] = "any value" elseif v == "file" then @@ -346,6 +348,8 @@ if _DEBUG.argcheck then local actualtype = type (actual) if check == actualtype then return true + elseif check == "bool" and actualtype == "boolean" then + return true elseif check == "#table" then if actualtype == "table" and next (actual) then return true diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index c9a5331..41c68d5 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -241,18 +241,21 @@ specify std.debug: - context with primitives: - it diagnoses missing types: + expect (fn ("bool", nil)).to_raise "boolean expected, got no value" expect (fn ("boolean", nil)).to_raise "boolean expected, got no value" expect (fn ("file", nil)).to_raise "FILE* expected, got no value" expect (fn ("number", nil)).to_raise "number expected, got no value" expect (fn ("string", nil)).to_raise "string expected, got no value" expect (fn ("table", nil)).to_raise "table expected, got no value" - it diagnoses mismatched types: + expect (fn ("bool", {0})).to_raise "boolean expected, got table" expect (fn ("boolean", {0})).to_raise "boolean expected, got table" expect (fn ("file", {0})).to_raise "FILE* expected, got table" expect (fn ("number", {0})).to_raise "number expected, got table" expect (fn ("string", {0})).to_raise "string expected, got table" expect (fn ("table", false)).to_raise "table expected, got boolean" - it matches types: + expect (fn ("bool", true)).not_to_raise "any error" expect (fn ("boolean", true)).not_to_raise "any error" expect (fn ("file", io.stderr)).not_to_raise "any error" expect (fn ("number", 1)).not_to_raise "any error" @@ -283,10 +286,15 @@ specify std.debug: expect (fn (":foo", ":foo")).not_to_raise "any error" - context with callable types: - it diagnoses missing types: + expect (fn ("func", nil)).to_raise "function expected, got no value" expect (fn ("function", nil)).to_raise "function expected, got no value" - it diagnoses mismatched types: + expect (fn ("func", {0})).to_raise "function expected, got table" expect (fn ("function", {0})).to_raise "function expected, got table" - it matches types: + expect (fn ("func", function () end)).not_to_raise "any error" + expect (fn ("func", setmetatable ({}, {__call = function () end}))). + not_to_raise "any error" expect (fn ("function", function () end)).not_to_raise "any error" expect (fn ("function", setmetatable ({}, {__call = function () end}))). not_to_raise "any error" From 61ce650c6b1857d76428f5b1f26d93cd5a0f5c07 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Feb 2015 21:05:40 +0000 Subject: [PATCH 536/703] debug: export formaterror as extramsg_mismatch. * specs/debug_spec.yaml (extramsg_mismatch): Add examples for correct behaviours of extramsg_mismatch. * lib/std/debug.lua (formaterror): Rename from this... (extramsg_mismatch): ...to this. Move out of _DEBUG.argcheck guard. (prototype, concat): Move above position of first call in file. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 + lib/std/debug.lua | 169 ++++++++++++++++++++++-------------------- specs/debug_spec.yaml | 31 +++++++- 3 files changed, 122 insertions(+), 81 deletions(-) diff --git a/NEWS.md b/NEWS.md index 076e383..92d7bc2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,6 +20,9 @@ - `debug.argscheck` will skip typecheck for `self` parameter if the function name specification contains a colon. + - New `debug.extramsg_mismatch` to generate `extramsg` argument for + `debug.argerror` on encountering a type mismatch. + ### Bug fixes - `std.getmetamethod` no longer rejects non-table subjects when diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 5d55953..8ed9438 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -32,13 +32,13 @@ local debug_init = require "std.debug_init" local base = require "std.base" -local _DEBUG = debug_init._DEBUG -local argerror = base.argerror -local unpack = base.unpack +local _DEBUG = debug_init._DEBUG +local argerror, prototype, unpack = base.argerror, base.prototype, base.unpack local copy, split, tostring = base.copy, base.split, base.tostring local insert, last, len, maxn = base.insert, base.last, base.len, base.maxn local ipairs, pairs = base.ipairs, base.pairs + local M @@ -230,12 +230,79 @@ local function parsetypes (types) end +--- Concatenate a table of strings using ", " and " or " delimiters. +-- @tparam table alternatives a table of strings +-- @treturn string string of elements from alternatives delimited by ", " +-- and " or " +local function concat (alternatives) + if len (alternatives) > 1 then + local t = copy (alternatives) + local top = table.remove (t) + t[#t] = t[#t] .. " or " .. top + alternatives = t + end + return table.concat (alternatives, ", ") +end + + +local function extramsg_mismatch (expectedtypes, actual, index) + local actualtype = prototype (actual) + + -- Tidy up actual type for display. + if actualtype == "nil" then + actualtype = "no value" + elseif actualtype == "string" and actual:sub (1, 1) == ":" then + actualtype = actual + elseif type (actual) == "table" and next (actual) == nil then + local matchstr = "," .. table.concat (expectedtypes, ",") .. "," + if actualtype == "table" and matchstr == ",#list," then + actualtype = "empty list" + elseif actualtype == "table" or matchstr:match ",#" then + actualtype = "empty " .. actualtype + end + end + + if index then + actualtype = actualtype .. " at index " .. tostring (index) + end + + -- Tidy up expected types for display. + local expectedstr = expectedtypes + if type (expectedtypes) == "table" then + local t = {} + for i, v in ipairs (expectedtypes) do + if v == "func" then + t[i] = "function" + elseif v == "bool" then + t[i] = "boolean" + elseif v == "any" then + t[i] = "any value" + elseif v == "file" then + t[i] = "FILE*" + elseif not index then + t[i] = v:match "(%S+) of %S+" or v + else + t[i] = v + end + end + expectedstr = (concat (t) .. " expected"): + gsub ("#table", "non-empty table"): + gsub ("#list", "non-empty list"): + gsub ("(%S+ of [^,%s]-)s? ", "%1s "): + gsub ("(%S+ of [^,%s]-)s?,", "%1s,"): + gsub ("(s, [^,%s]-)s? ", "%1s "): + gsub ("(s, [^,%s]-)s?,", "%1s,"): + gsub ("(of .-)s? or ([^,%s]-)s? ", "%1s or %2s ") + end + + return expectedstr .. ", got " .. actualtype +end + + local argcheck, argscheck -- forward declarations if _DEBUG.argcheck then - local prototype = base.prototype - local function resulterror (name, i, extramsg, level) level = level or 1 local s = string.format ("bad result #%d from '%s'", i, name) @@ -245,20 +312,6 @@ if _DEBUG.argcheck then error (s, level + 1) end - --- Concatenate a table of strings using ", " and " or " delimiters. - -- @tparam table alternatives a table of strings - -- @treturn string string of elements from alternatives delimited by ", " - -- and " or " - local function concat (alternatives) - if len (alternatives) > 1 then - local t = copy (alternatives) - local top = table.remove (t) - t[#t] = t[#t] .. " or " .. top - alternatives = t - end - return table.concat (alternatives, ", ") - end - --- Return index of the first mismatch between types and values, or `nil`. -- @tparam table typelist a list of expected types @@ -277,62 +330,6 @@ if _DEBUG.argcheck then end - --- Format a type mismatch error. - -- @tparam table expectedtypes a table of matchable types - -- @string actual the actual argument to match with - -- @number[opt] index erroring container element index - -- @treturn string formatted *extramsg* for this mismatch for @{argerror} - local function formaterror (expectedtypes, actual, index) - local actualtype = prototype (actual) - - -- Tidy up actual type for display. - if actualtype == "nil" then - actualtype = "no value" - elseif actualtype == "string" and actual:sub (1, 1) == ":" then - actualtype = actual - elseif type (actual) == "table" and next (actual) == nil then - local matchstr = "," .. table.concat (expectedtypes, ",") .. "," - if actualtype == "table" and matchstr == ",#list," then - actualtype = "empty list" - elseif actualtype == "table" or matchstr:match ",#" then - actualtype = "empty " .. actualtype - end - end - - if index then - actualtype = actualtype .. " at index " .. tostring (index) - end - - -- Tidy up expected types for display. - local expectedstr = expectedtypes - if type (expectedtypes) == "table" then - local t = {} - for i, v in ipairs (expectedtypes) do - if v == "func" then - t[i] = "function" - elseif v == "bool" then - t[i] = "boolean" - elseif v == "any" then - t[i] = "any value" - elseif v == "file" then - t[i] = "FILE*" - elseif not index then - t[i] = v:match "(%S+) of %S+" or v - else - t[i] = v - end - end - expectedstr = concat (t): - gsub ("#table", "non-empty table"): - gsub ("#list", "non-empty list"): - gsub ("(%S+ of %S+)", "%1s"): - gsub ("(%S+ of %S+)ss", "%1s") - end - - return expectedstr .. " expected, got " .. actualtype - end - - --- Compare *check* against type of *actual* -- @string check extended type name expected -- @param actual object being typechecked @@ -436,7 +433,7 @@ if _DEBUG.argcheck then if contents and type (valuelist[i]) == "table" then for k, v in pairs (valuelist[i]) do if not checktype (contents, v) then - argt.badtype (i, formaterror (expected, v, k), 3) + argt.badtype (i, extramsg_mismatch (expected, v, k), 3) end end end @@ -444,7 +441,7 @@ if _DEBUG.argcheck then -- Otherwise the argument type itself was mismatched. if t.dots or #t >= maxn (valuelist) then - argt.badtype (i, formaterror (expected, valuelist[i]), 3) + argt.badtype (i, extramsg_mismatch (expected, valuelist[i]), 3) end end @@ -472,7 +469,7 @@ if _DEBUG.argcheck then if ok and contents and type (actual) == "table" then for k, v in pairs (actual) do if not checktype (contents, v) then - argerror (name, i, formaterror (expected, v, k), level + 1) + argerror (name, i, extramsg_mismatch (expected, v, k), level + 1) end end end @@ -480,7 +477,7 @@ if _DEBUG.argcheck then end if not ok then - argerror (name, i, formaterror (expected, actual), level + 1) + argerror (name, i, extramsg_mismatch (expected, actual), level + 1) end end @@ -748,6 +745,20 @@ M = { -- end) argscheck = argscheck, + --- Format a type mismatch error. + -- @function extramsg_mismatch + -- @string expected a pipe delimited list of matchable types + -- @param actual the actual argument to match with + -- @number[opt] index erroring container element index + -- @treturn string formatted *extramsg* for this mismatch for @{argerror} + -- @usage + -- if fmt ~= nil and type (fmt) ~= "string" then + -- argerror ("format", 1, extramsg ("?string", fmt)) + -- end + extramsg_mismatch = function (expected, actual, index) + return extramsg_mismatch (typesplit (expected), actual, index) + end, + --- Extend `debug.getfenv` to unwrap functables correctly. -- @tparam int|function|functable fn target function, or stack level -- @treturn table environment of *fn* diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 41c68d5..e2d8f3f 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,8 +4,9 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "argscheck", "getfenv", "parsetypes", "setfenv", "say", - "toomanyargmsg", "typesplit", "trace", "_setdebug" } + "argscheck", "extramsg_mismatch", "getfenv", "parsetypes", + "setfenv", "say", "toomanyargmsg", "typesplit", "trace", + "_setdebug" } M = require (this_module) @@ -956,6 +957,32 @@ specify std.debug: expect ({wrapped ("one", 2)}).to_equal {"one", 2} +- describe extramsg_mismatch: + - before: + f = M.extramsg_mismatch + + - it returns the expected types: + expect (f "nil").to_contain "nil expected, " + expect (f "bool").to_contain "boolean expected, " + expect (f "?bool").to_contain "boolean or nil expected, " + expect (f "string|table").to_contain "string or table expected, " + - it returns expected container types: + expect (f ("table of int", nil, 1)).to_contain "table of ints expected, " + expect (f ("table of int|bool", nil, 1)). + to_contain "table of ints or booleans expected, " + expect (f ("table of int|bool|string", nil, 1)). + to_contain "table of ints, booleans or strings expected, " + expect (f ("table of int|bool|string|table", nil, 1)). + to_contain "table of ints, booleans, strings or tables expected, " + - it returns the actual type: + expect (f ("int")).to_contain ", got no value" + expect (f ("int", false)).to_contain ", got boolean" + expect (f ("int", {})).to_contain ", got empty table" + - it returns table field type: + expect (f ("table of int", nil, 1)).to_contain ", got no value at index 1" + expect (f ("table of int", "two", 2)).to_contain ", got string at index 2" + expect (f ("table of int|bool", "five", 3)).to_contain ", got string at index 3" + - context function environments: - before: env = { From f2c60f33c1683f349f280e048157bd1e5153cd4f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 9 Feb 2015 21:43:03 +0000 Subject: [PATCH 537/703] debug: refactor and export debug.resulterror. * specs/debug_spec.yaml (resulterror): Add examples of behaviours for resulterror. * lib/std/base.lua (raise): New function factored out of... (argerror): ...this. Adjust accordingly. * lib/std/debug.lua (resulterror): Rewrite over base.raise. Move out of _DEBUG.argcheck guard. Export as resulterror. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 6 +++++- lib/std/base.lua | 11 ++++++++-- lib/std/debug.lua | 49 +++++++++++++++++++++++++++++++------------ specs/debug_spec.yaml | 36 +++++++++++++++++++++++++++++-- 4 files changed, 84 insertions(+), 18 deletions(-) diff --git a/NEWS.md b/NEWS.md index 92d7bc2..8b11332 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,8 +20,12 @@ - `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` on encountering a type mismatch. + `debug.argerror` or `debug.resulterror` on encountering a type + mismatch. ### Bug fixes diff --git a/lib/std/base.lua b/lib/std/base.lua index 8b98f96..5f2139f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -27,9 +27,9 @@ local dirsep = string.match (package.config, "^(%S+)\n") local loadstring = rawget (_G, "loadstring") or load -local function argerror (name, i, extramsg, level) +local function raise (bad, to, name, i, extramsg, level) level = level or 1 - local s = string.format ("bad argument #%d to '%s'", i, name) + local s = string.format ("bad %s #%d %s '%s'", bad, i, to, name) if extramsg ~= nil then s = s .. " (" .. extramsg .. ")" end @@ -37,6 +37,12 @@ local function argerror (name, i, extramsg, level) end +local function argerror (name, i, extramsg, level) + level = level or 1 + raise ("argument", "to", name, i, extramsg, level + 1) +end + + local function assert (expect, fmt, arg1, ...) local msg = (arg1 ~= nil) and string.format (fmt, arg1, ...) or fmt or "" return expect or error (msg, 2) @@ -436,6 +442,7 @@ return { keysort = keysort, merge = merge, okeys = okeys, + raise = raise, -- std.lua -- assert = assert, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 8ed9438..9166ad7 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -33,7 +33,8 @@ local debug_init = require "std.debug_init" local base = require "std.base" local _DEBUG = debug_init._DEBUG -local argerror, prototype, unpack = base.argerror, base.prototype, base.unpack +local argerror, raise = base.argerror, base.raise +local prototype, unpack = base.prototype, base.unpack local copy, split, tostring = base.copy, base.split, base.tostring local insert, last, len, maxn = base.insert, base.last, base.len, base.maxn local ipairs, pairs = base.ipairs, base.pairs @@ -133,6 +134,12 @@ local getfenv = function (fn) end +local function resulterror (name, i, extramsg, level) + level = level or 1 + raise ("result", "from", name, i, extramsg, level + 1) +end + + local function toomanymsg (bad, to, name, expect, actual) local s = "bad %s #%d %s '%s' (no more than %d %s%s expected, got %d)" return s:format (bad, expect + 1, to, name, expect, bad, expect == 1 and "" or "s", actual) @@ -303,16 +310,6 @@ local argcheck, argscheck -- forward declarations if _DEBUG.argcheck then - local function resulterror (name, i, extramsg, level) - level = level or 1 - local s = string.format ("bad result #%d from '%s'", i, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) - end - - --- Return index of the first mismatch between types and values, or `nil`. -- @tparam table typelist a list of expected types -- @tparam table valuelist a table of arguments to compare @@ -501,7 +498,10 @@ if _DEBUG.argcheck then badcount = function (...) return toomanymsg ("argument", "to", fname, ...) end, - badtype = function (...) argerror (fname, ...) end, + badtype = function (i, extramsg, level) + level = level or 1 + argerror (fname, i, extramsg, level + 1) + end, permutations = permute (argtypes), } @@ -524,7 +524,10 @@ if _DEBUG.argcheck then badcount = function (...) return toomanymsg ("result", "from", fname, ...) end, - badtype = function (...) resulterror (fname, ...) end, + badtype = function (i, extramsg, level) + level = level or 1 + resulterror (fname, i, extramsg, level + 1) + end, permutations = permutations, } end @@ -700,6 +703,8 @@ M = { -- @int i argument number -- @string[opt] extramsg additional text to append to message inside parentheses -- @int[opt=1] level call stack level to blame for the error + -- @see resulterror + -- @see extramsg_mismatch -- @usage -- local function slurp (file) -- local h, err = input_handle (file) @@ -751,6 +756,8 @@ M = { -- @param actual the actual argument to match with -- @number[opt] index erroring container element index -- @treturn string formatted *extramsg* for this mismatch for @{argerror} + -- @see argerror + -- @see resulterror -- @usage -- if fmt ~= nil and type (fmt) ~= "string" then -- argerror ("format", 1, extramsg ("?string", fmt)) @@ -772,6 +779,22 @@ M = { -- @treturn list valid types for each positional parameter parsetypes = parsetypes, + --- Raise a bad result error. + -- Like @{argerror} for bad results. This function does not + -- return. The `level` argument behaves just like the core `error` + -- function. + -- @function argerror + -- @string name function to callout in error message + -- @int i argument number + -- @string[opt] extramsg additional text to append to message inside parentheses + -- @int[opt=1] level call stack level to blame for the error + -- @usage + -- local function slurp (file) + -- local h, err = input_handle (file) + -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end + -- ... + resulterror = resulterror, + --- Extend `debug.setfenv` to unwrap functables correctly. -- @tparam function|functable fn target function -- @tparam table env new function environment diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index e2d8f3f..1fcd851 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -5,8 +5,8 @@ before: | extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", "argscheck", "extramsg_mismatch", "getfenv", "parsetypes", - "setfenv", "say", "toomanyargmsg", "typesplit", "trace", - "_setdebug" } + "resulterror", "setfenv", "say", "toomanyargmsg", + "typesplit", "trace", "_setdebug" } M = require (this_module) @@ -132,6 +132,38 @@ specify std.debug: expect (luaproc (mkscript (2))).to_match_error "^%S+:5:.*deprecated" +- describe resulterror: + - before: | + function mkstack (level) + return string.format ([[ + _DEBUG = true -- line 1 + local debug = require "std.debug" -- line 2 + function ohnoes () -- line 3 + debug.resulterror ("ohnoes", 1, nil, %s) -- line 4 + end -- line 5 + function caller () -- line 6 + local r = ohnoes () -- line 7 + return "not a tail call" -- line 8 + end -- line 9 + caller () -- line 10 + ]], tostring (level)) + end + + f = M.resulterror + + - it blames the call site by default: | + expect (luaproc (mkstack ())).to_contain_error ":4: bad result" + - it honors optional call stack level reporting: | + expect (luaproc (mkstack (1))).to_contain_error ":4: bad result" + expect (luaproc (mkstack (2))).to_contain_error ":7: bad result" + - it reports the calling function name: + expect (f ('expect', 1)).to_raise "'expect'" + - it reports the argument number: | + expect (f ('expect', 12345)).to_raise "#12345" + - it reports extra message in parentheses: + expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" + + - describe argerror: - before: | function mkstack (level) From 7234f8a9703cf95bafbbe07eace4cede400ed95b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 10 Feb 2015 09:53:08 +0000 Subject: [PATCH 538/703] debug: deprecate `toomanyargmsg` for `extramsg_toomany`. * specs/debug_spec.yaml (extramsg_toomany): Add examples for correct behaviours of new api. * lib/std/debug.lua (toomanyargmsg): Deprecate. (extramsg_toomany): New function, satisfies specified behaviours. * lib/std/container.lua: Use extramsg_toomany api. * NEWS.md (New features, Deprecations): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 15 +++++++++++ lib/std/container.lua | 4 +-- lib/std/debug.lua | 59 +++++++++++++++++++++++++------------------ specs/debug_spec.yaml | 23 ++++++++++++++--- 4 files changed, 72 insertions(+), 29 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8b11332..a65072d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -27,6 +27,21 @@ `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 diff --git a/lib/std/container.lua b/lib/std/container.lua index 2b682c3..3136d4e 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -203,7 +203,7 @@ local M = { if _DEBUG.argcheck then - local toomanyargmsg = debug.toomanyargmsg + local argerror, extramsg_toomany = debug.argerror, debug.extramsg_toomany M.__call = function (self, x, ...) local mt = getmetatable (self) @@ -216,7 +216,7 @@ if _DEBUG.argcheck then -- it just refers back to the object being called: `Container {"x"}. argcheck (name, 1, "table", x) if next (argt) then - error (toomanyargmsg (name, 1, 1 + maxn (argt)), 2) + argerror (name, 2, extramsg_toomany ("argument", 1, 1 + maxn (argt)), 2) end end diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 9166ad7..aff3ebc 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -140,9 +140,9 @@ local function resulterror (name, i, extramsg, level) end -local function toomanymsg (bad, to, name, expect, actual) - local s = "bad %s #%d %s '%s' (no more than %d %s%s expected, got %d)" - return s:format (bad, expect + 1, to, name, expect, bad, expect == 1 and "" or "s", actual) +local function extramsg_toomany (bad, expected, actual) + local s = "no more than %d %s%s expected, got %d" + return s:format (expected, bad, expected == 1 and "" or "s", actual) end @@ -444,7 +444,7 @@ if _DEBUG.argcheck then local n, t = maxn (valuelist), t or permutations[1] if t and t.dots == nil and n > #t then - error (argt.badcount (#t, n), 3) + argt.badtype (#t + 1, extramsg_toomany (argt.bad, #t, n), 3) end end @@ -495,9 +495,7 @@ if _DEBUG.argcheck then -- Precalculate vtables once to make multiple calls faster. local input, output = { - badcount = function (...) - return toomanymsg ("argument", "to", fname, ...) - end, + bad = "argument", badtype = function (i, extramsg, level) level = level or 1 argerror (fname, i, extramsg, level + 1) @@ -521,9 +519,7 @@ if _DEBUG.argcheck then table.sort (permutations, function (a, b) return #a > #b end) output = { - badcount = function (...) - return toomanymsg ("result", "from", fname, ...) - end, + bad = "result", badtype = function (i, extramsg, level) level = level or 1 resulterror (fname, i, extramsg, level + 1) @@ -760,12 +756,25 @@ M = { -- @see resulterror -- @usage -- if fmt ~= nil and type (fmt) ~= "string" then - -- argerror ("format", 1, extramsg ("?string", fmt)) + -- argerror ("format", 1, extramsg_mismatch ("?string", fmt)) -- end extramsg_mismatch = function (expected, actual, index) return extramsg_mismatch (typesplit (expected), actual, index) end, + --- Format a too many things error. + -- @string bad the thing there are too many of + -- @int expected maximum number of *bad* things expected + -- @int actual actual number of *bad* things that triggered the error + -- @see argerror + -- @see resulterror + -- @see extramsg_mismatch + -- @usage + -- if maxn (argt) > 7 then + -- argerror ("sevenses", 8, extramsg_toomany ("argument", 7, maxn (argt))) + -- end + extramsg_toomany = extramsg_toomany, + --- Extend `debug.getfenv` to unwrap functables correctly. -- @tparam int|function|functable fn target function, or stack level -- @treturn table environment of *fn* @@ -815,19 +824,6 @@ M = { -- say (2, "_DEBUG table contents:", _DEBUG) say = say, - --- Format a standard "too many arguments" error message. - -- @fixme remove this wart! - -- @function toomanyargmsg - -- @string name function name - -- @number expect maximum number of arguments accepted - -- @number actual number of arguments received - -- @treturn string standard "too many arguments" error message - -- @usage - -- if table.maxn {...} > 1 then - -- io.stderr:write ("module.fname", 7, table.maxn {...}) - -- ... - toomanyargmsg = function (...) return toomanymsg ("argument", "to", ...) end, - --- Trace function calls. -- Use as debug.sethook (trace, "cr"), which is done automatically -- when `_DEBUG.call` is set. @@ -872,6 +868,21 @@ local metatable = { end, } + + +--[[ =========== ]]-- +--[[ Deprecated. ]]-- +--[[ =========== ]]-- + + +M.toomanyargmsg = DEPRECATED ("41.2.0", "debug.toomanyargmsg", + "use 'debug.extramsg_toomany' instead", + function (name, expect, actual) + local s = "bad argument #%d to '%s' (no more than %d argument%s expected, got %d)" + return s:format (expect + 1, name, expect, expect == 1 and "" or "s", actual) + end) + + return setmetatable (M, metatable) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 1fcd851..9a8757e 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -4,9 +4,9 @@ before: | global_table = "_G" extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "argscheck", "extramsg_mismatch", "getfenv", "parsetypes", - "resulterror", "setfenv", "say", "toomanyargmsg", - "typesplit", "trace", "_setdebug" } + "argscheck", "extramsg_mismatch", "extramsg_toomany", + "getfenv", "parsetypes", "resulterror", "setfenv", "say", + "toomanyargmsg", "typesplit", "trace", "_setdebug" } M = require (this_module) @@ -1015,6 +1015,23 @@ specify std.debug: expect (f ("table of int", "two", 2)).to_contain ", got string at index 2" expect (f ("table of int|bool", "five", 3)).to_contain ", got string at index 3" + +- describe extramsg_toomany: + - before: + f = M.extramsg_toomany + + - it returns the expected thing: + expect (f ("mojo", 1, 2)).to_contain "no more than 1 mojo" + - it uses singular thing when 1 is expected: + expect (f ("argument", 1, 2)).to_contain "no more than 1 argument" + - it uses plural thing otherwise: + expect (f ("thing", 0, 3)).to_contain "no more than 0 things" + expect (f ("result", 2, 3)).to_contain "no more than 2 results" + - it returns the actual count: + expect (f ("bad", 0, 1)).to_contain ", got 1" + expect (f ("bad", 99, 999)).to_contain ", got 999" + + - context function environments: - before: env = { From 2f21d23fa8f5d2029e4c48f76da3016b00ea5847 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Mar 2015 20:32:01 +0000 Subject: [PATCH 539/703] doc: don't tell LDoc that resulterror is called argerror! Signed-off-by: Gary V. Vaughan --- lib/std/debug.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index aff3ebc..9657846 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -792,7 +792,6 @@ M = { -- Like @{argerror} for bad results. This function does not -- return. The `level` argument behaves just like the core `error` -- function. - -- @function argerror -- @string name function to callout in error message -- @int i argument number -- @string[opt] extramsg additional text to append to message inside parentheses From 631114951b50e8e5166eacca60e86b46b11b8ffc Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 22 Feb 2015 17:20:39 +0000 Subject: [PATCH 540/703] io: don't leak extra results from implementation details. * lib/std/io.lua (dirname): Return parenthesised results from internal gsub calls to prevent unexpected additional values leaking. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/io.lua | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index a65072d..206194e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -57,6 +57,9 @@ - `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 diff --git a/lib/std/io.lua b/lib/std/io.lua index e086db6..056d7cc 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -180,7 +180,7 @@ M = { -- truncated -- @usage dir = dirname "/base/subdir/filename" dirname = X ("dirname (string)", function (path) - return path:gsub (catfile ("", "[^", "]*$"), "") + return (path:gsub (catfile ("", "[^", "]*$"), "")) end), --- Overwrite core `io` methods with `std` enhanced versions. From da6f7368f19efa1e66a2f6997889d416c7af6620 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 4 Feb 2015 00:06:12 +0000 Subject: [PATCH 541/703] configury: bump release revision to 41.2.0. * configure.ac (AC_INIT): Bump release revision to 41.2.0. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 4 ++-- configure.ac | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b48ff76..8593705 100644 --- a/.travis.yml +++ b/.travis.yml @@ -113,8 +113,8 @@ install: # Build from rockspec, forcing uninstall of older luarocks installed # above when testing the git rockspec, both for enforcing backwards # compatibility by default, and for ease of maintenance. - - if test -f 'stdlib-41.1.1-1.rockspec'; then - sudo luarocks make 'stdlib-41.1.1-1.rockspec' LUA="$LUA"; + - if test -f 'stdlib-41.2.0-1.rockspec'; then + sudo luarocks make 'stdlib-41.2.0-1.rockspec' LUA="$LUA"; else sudo luarocks make --force 'stdlib-git-1.rockspec' LUA="$LUA"; fi diff --git a/configure.ac b/configure.ac index 545f6a8..fd116ac 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [41.1.1], [http://github.com/lua-stdlib/lua-stdlib/issues]) +AC_INIT([stdlib], [41.2.0], [http://github.com/lua-stdlib/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) From 2f3be4a907c089271e2168aa1ac4fcf4f2932d25 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Mar 2015 20:42:51 +0000 Subject: [PATCH 542/703] Release version 41.2.0 * NEWS.md: Record release date. --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 206194e..7bab1c4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # Stdlib NEWS - User visible changes -## Noteworthy changes in release ?.? (????-??-??) [?] +## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] ### New features From 439796d4a465e06d29b02f55a8071eef8d7a7206 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Mar 2015 20:43:43 +0000 Subject: [PATCH 543/703] maint: post-release administrivia. * NEWS: Add header line for next release. * .prev-version: Record previous version. * ./local.mk (old_NEWS_hash): Auto-update. Signed-off-by: Gary V. Vaughan --- .prev-version | 2 +- NEWS.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.prev-version b/.prev-version index f0384dc..6b3b677 100644 --- a/.prev-version +++ b/.prev-version @@ -1 +1 @@ -41.1.1 +41.2.0 diff --git a/NEWS.md b/NEWS.md index 7bab1c4..5c67ec4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # Stdlib NEWS - User visible changes +## Noteworthy changes in release ?.? (????-??-??) [?] + + ## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] ### New features From 4bada7ff5e044a21a921ca9be42d465d67adc644 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 29 Mar 2015 14:06:01 -0700 Subject: [PATCH 544/703] functional: add functional product operation. * specs/functional_spec.yaml (product): Add examples of correct behaviour of new operation. * lib/std/functional.lua (product): Implement correct behaviours. * NEWS: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 6 ++++++ lib/std/functional.lua | 36 ++++++++++++++++++++++++++++++++++++ specs/functional_spec.yaml | 29 +++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5c67ec4..b04d172 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,12 @@ ## Noteworthy changes in release ?.? (????-??-??) [?] +### New features + + - New `functional.product` returns a list of combinations made by + taking one element from each of the argument lists. See LDocs for + an example. + ## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] diff --git a/lib/std/functional.lua b/lib/std/functional.lua index b20a8bc..10a381b 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -254,6 +254,30 @@ local function map_with (mapfn, tt) end +local function _product (x, l) + local r = {} + for v1 in ielems (x) do + for v2 in ielems (l) do + r[#r + 1] = {v1, unpack (v2)} + end + end + return r +end + +local function product (...) + local argt = {...} + if not next (argt) then + return argt + else + -- Accumulate a list of products, starting by popping the last + -- argument and making each member a one element list. + local d = map (lambda '={_1}', ielems, table.remove (argt)) + -- Right associatively fold in remaining argt members. + return foldr (_product, d, argt) + end +end + + local function zip (tt) local r = {} for outerk, inner in pairs (tt) do @@ -498,6 +522,18 @@ local M = { -- if unsupported then vtable["memrmem"] = nop end nop = base.nop, -- ignores all arguments + --- Functional list product. + -- + -- Return a list of each combination possible by taking a single + -- element from each of the argument lists. + -- @function product + -- @param ... operands + -- @return result + -- @usage + -- --> {"000", "001", "010", "011", "100", "101", "110", "111"} + -- map (table.concat, ielems, product ({0,1}, {0, 1}, {0, 1})) + product = X ("product (list...)", product), + --- Fold a binary function into an iterator. -- @function reduce -- @func fn reduce function diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 619c251..a00a554 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -6,8 +6,8 @@ before: exported_apis = { "bind", "callable", "case", "collect", "compose", "cond", "curry", "eval", "filter", "fold", "foldl", - "foldr", "id", "lambda", "map", "map_with", - "memoize", "nop", "op", "reduce", "zip", "zip_with" } + "foldr", "id", "lambda", "map", "map_with", "memoize", + "nop", "op", "product", "reduce", "zip", "zip_with" } M = require (this_module) @@ -686,6 +686,31 @@ specify std.functional: expect (f ({}, {})).to_be (true) +- describe product: + - before: + f = M.product + + - context with bad arguments: + badargs.diagnose (f, "std.functional.product (list*)") + + - it works with an empty table: + expect (f {}).to_equal {} + - it returns a list of elements from a single argument: + expect (f {'a', 'b', 'c'}).to_equal {{'a'}, {'b'}, {'c'}} + - it lists combinations with one element from each argument: + expect (f ({1, 2, 3}, {4, 5, 6})).to_equal { + {1, 4}, {1, 5}, {1, 6}, + {2, 4}, {2, 5}, {2, 6}, + {3, 4}, {3, 5}, {3, 6}, + } + expect (f ({1, 2}, {3, 4}, {5, 6})).to_equal { + {1, 3, 5}, {1, 3, 6}, {1, 4, 5}, {1, 4, 6}, + {2, 3, 5}, {2, 3, 6}, {2, 4, 5}, {2, 4, 6}, + } + expect (M.map (table.concat, base.ielems, f ({0,1},{0,1},{0,1}))). + to_equal {"000", "001", "010", "011", "100", "101", "110", "111"} + + - describe reduce: - before: op = require "std.operator" From ed5c2052efbdc6256a8fd29e5e9265670637bdfd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 5 Apr 2015 12:43:31 -0700 Subject: [PATCH 545/703] functional: make lambda a stringifiable functable. * specs/functional_spec.yaml (lambda): Specify behaviour of compiled lambda objects when passed to `tostring`. * lib/std/functional.lua (lambda): Save the lambda string and compiled function into a functable. * NEWS.md (New features, Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 8 ++++++++ lib/std/functional.lua | 5 ++++- specs/functional_spec.yaml | 21 ++++++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index b04d172..2fa301d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,14 @@ taking one element from each of the argument lists. See LDocs for an example. + - Passing the result of `functional.lambda` to `tostring` returns the + original lambda string. + +### Incompatible changes + + - `functional.lambda` no longer returns a bare function, but a functable + that can be called and stringified. + ## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 10a381b..eecb0f0 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -207,7 +207,10 @@ local lambda = memoize (function (s) return nil, "invalid lambda string '" .. s .. "'" end - return fn + return setmetatable ({}, { + __call = function (self, ...) return fn (...) end, + __tostring = function (self) return s end, + }) end, id) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index a00a554..7f8b7b2 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -349,6 +349,11 @@ specify std.functional: - before: f = M.lambda + callable = function (x) + if M.callable (x) then return true end + return false + end + - context with bad arguments: badargs.diagnose (f, "std.functional.lambda (string)") @@ -360,18 +365,25 @@ specify std.functional: expect (select (2, f "=")).to_be "invalid lambda string '='" end} + - it returns previously compiled lambdas: + fn = f "|a,b|a==b" + expect (fn).to_be (f "|a,b|a==b") + - context with argument format: - - it returns a function: - expect (prototype (f "|x| 1+x")).to_be "function" + - it returns a callable: + expect (callable (f "|x| 1+x")).to_be (true) - it compiles to a working Lua function: fn = f "||42" expect (fn ()).to_be (42) - it propagates argument values: fn = f "|...| {...}" expect (fn (1,2,3)).to_equal {1,2,3} + - it can be stringified: + exp = "|a,b|a==b" + expect (tostring (f (exp))).to_be (exp) - context with expression format: - it returns a function: - expect (prototype (f "_")).to_be "function" + expect (callable (f "_")).to_be (true) - it compiles to a working Lua function: fn = f "=42" expect (fn ()).to_be (42) @@ -381,6 +393,9 @@ specify std.functional: - it sets numeric auto-argument values: fn = f "_1+_2+_3" expect (fn (1, 2, 5)).to_be (8) + - it can be stringified: + exp = "_%2==0" + expect (tostring (f (exp))).to_be (exp) - describe map: From aac7417e9fe5eb8c5c44684c82bbc838275c4a7b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 13 Apr 2015 01:07:39 -0700 Subject: [PATCH 546/703] operator: support new equivalence operator. * specs/operator_spec.yaml (eqv): Specify behaviours of eqv when checking arguments for equivalence. * lib/std/operator.lua (eqv): Implement new operator to satisfy these behaviours. * NEWS (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/operator.lua | 51 +++++++++++++++++++++++++++++++++++++++- specs/operator_spec.yaml | 41 ++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 2fa301d..670ca01 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,9 @@ taking one element from each of the argument lists. See LDocs for an example. + - New `operator.eqv` is similar to `operator.eq`, except that it succeeds + when recursive eable contents are equivalent. + - Passing the result of `functional.lambda` to `tostring` returns the original lambda string. diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 1f77e78..ea27e18 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -6,7 +6,50 @@ local base = require "std.base" -local tostring = base.tostring +local pairs, prototype, tostring = + base.pairs, base.prototype, base.tostring + + +local function eqv (a, b) + -- If they are the same primitive value, or they share a metatable + -- with an __eq metamethod that says they are equivalent, we're done! + if a == b then return true end + + -- Unless we have two tables, what we have cannot be equivalent here. + if type (a) ~= "table" or type (b) ~= "table" then return false end + + local type_a, type_b = prototype (a), prototype (b) + if type_a ~= type_b then return false end + + local keyeqv = {} -- keys requiring recursive equivalence test + for k, v in pairs (a) do + if b[k] == nil then return false end + if v ~= b[k] then + if type (v) ~= "table" then return false end + -- Only require recursive comparisons for mismatched tables at k. + keyeqv[#keyeqv + 1] = k + end + end + + -- Any uncompared keys remaining in b denote a mismatch. + for k in pairs (b) do + if a[k] == nil then return false end + end + + if #keyeqv == 0 then return true end + if #keyeqv > 1 then + for _, k in ipairs (keyeqv) do + assert (a[k] ~= nil and b[k] ~= nil) + if not eqv (a[k], b[k]) then return false end + end + return true + end + + -- Use a tail call for arbitrary depth single table valued key + -- equivalence. + local _, k = next (keyeqv) + return eqv (a[k], b[k]) +end local M = { @@ -119,6 +162,12 @@ local M = { -- functional.bind (functional.map, {std.ielems, neg}) {false, true, 1, 0} neg = function (a) return not a end, + --- Recursive table equivalence. + -- @param a an argument + -- @param b another argument + -- @treturn boolean whether *a* and *b* are recursively equivalent + eqv = eqv, + --- Return the equality of the arguments. -- @param a an argument -- @param b another argument diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index 1b66084..a359eb2 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -131,6 +131,47 @@ specify std.operator: expect (f ()).to_be (true) expect (f (0)).to_be (false) +- describe eqv: + - before: | + f = M.eqv + + __eq = function (a, b) return #a == #b end + X = function (x) return setmetatable (x, {__eq = __eq}) end + + - it returns false if primitive types differ: + expect (f (nil, 1)).to_be (false) + expect (f ("1", 1)).to_be (false) + - it returns true if primitives are equal: + expect (f (nil, nil)).to_be (true) + expect (f (false, false)).to_be (true) + expect (f (10, 10)).to_be (true) + expect (f ("one", "one")).to_be (true) + - it returns true if tables are equivalent: + expect (f ({}, {})).to_be (true) + expect (f ({"one"}, {"one"})).to_be (true) + expect (f ({1,2,3,4,5}, {1,2,3,4,5})).to_be (true) + expect (f ({a=1,b=2,c=3}, {c=3,b=2,a=1})).to_be (true) + - it compares values recursively: + expect (f ({1, {{2, 3}, 4}}, {1, {{2, 3}, 4}})).to_be (true) + expect (f ({a=1, b={c={2, d=3}, 4}}, {a=1, b={c={2, d=3}, 4}})). + to_be (true) + - it does not compare keys recursively: + expect (f ({[{a=1}]=2}, {[{a=1}]=2})).to_be (false) + expect (f ({[{a=1}]={[{2}]="b"}}, {[{a=1}]={[{2}]="b"}})). + to_be (false) + - it returns false if table lengths differ: + expect (f ({1,2,3,4}, {1,2,3,4,5})).to_be (false) + expect (f ({1,2,3,4}, {[0]=0,1,2,3,4})).to_be (false) + expect (f ({[{a=1}]={[{2}]="b"}}, {[{a=1}]={[{2}]="b", 3}})). + to_be (false) + expect (f ({[{a=1}]={[{2}]="b"}}, {[{a=1}]={[{2}]="b"}, 3})). + to_be (false) + - it returns true if __eq metamethod matches: + expect (f (X {}, X {})).to_be (true) + expect (f (X {1}, X {2})).to_be (true) + - it returns false if _eq metamethod mismatches: + expect (f (X {}, X {1})).to_be (false) + - describe eq: - before: f = M.eq From 23c72ae2bcce05128f4facceea34a1c7e3671085 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 17 Apr 2015 20:38:45 -0700 Subject: [PATCH 547/703] maint: remove apis deprecated before stdlib-40.0.0. * lib/std/functional.lua (bind): Remove old syntax support. (curry): Use new syntax for calling bind. * lib/std/list.lua (depair, map_with, transpose, zip_with): Remove deprecated object methods. * lib/std/table.lua (clone_rename): Remove. * specs/functional_spec.yaml (bind), specs/list_spec.yaml (depair, map_with, transpose, zip_with), specs/table_spec.yaml (clone_rename): Adjust accordingly. * NEWS.md (Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 7 +++ lib/std/functional.lua | 15 ++----- lib/std/list.lua | 7 --- lib/std/table.lua | 12 ------ specs/functional_spec.yaml | 14 +----- specs/list_spec.yaml | 87 -------------------------------------- specs/table_spec.yaml | 33 +-------------- 7 files changed, 12 insertions(+), 163 deletions(-) diff --git a/NEWS.md b/NEWS.md index 670ca01..06b7b50 100644 --- a/NEWS.md +++ b/NEWS.md @@ -16,6 +16,13 @@ ### Incompatible changes + - Deprecated multi-argument `functional.bind` has been removed. + + - Deprecated methods `list:depair`, `list:map_with`, `list:transpose` and + `list:zip_with` have been removed. + + - Deprecated function `table.clone_rename` has been removed. + - `functional.lambda` no longer returns a bare function, but a functable that can be called and stringified. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index eecb0f0..e790a7e 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -18,16 +18,7 @@ local callable, copy, len, reduce, unpack = local loadstring = loadstring or load -local function bind (fn, ...) - local bound = {...} - if type (bound[1]) == "table" and bound[2] == nil then - bound = bound[1] - else - io.stderr:write (debug.DEPRECATIONMSG ("39", - "multi-argument 'std.functional.bind'", - "use a table of arguments as the second parameter instead", 2)) - end - +local function bind (fn, bound) return function (...) local argt, i = copy (bound), 1 for _, v in npairs {...} do @@ -80,7 +71,7 @@ local function curry (fn, n) return fn else return function (x) - return curry (bind (fn, x), n - 1) + return curry (bind (fn, {x}), n - 1) end end end @@ -316,7 +307,7 @@ local M = { -- @return function with *argt* arguments already bound -- @usage -- cube = bind (std.operator.pow, {[2] = 3}) - bind = X ("bind (func, ?any...)", bind), + bind = X ("bind (func, table)", bind), --- Identify callable types. -- @function callable diff --git a/lib/std/list.lua b/lib/std/list.lua index ad04c06..aba6154 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -354,13 +354,6 @@ local m = { } -m.depair = DEPRECATED ("38", "'std.list:depair'", depair) -m.map_with = DEPRECATED ("38", "'std.list:map_with'", - function (self, fn) return map_with (fn, self) end) -m.transpose = DEPRECATED ("38", "'std.list:transpose'", transpose) -m.zip_with = DEPRECATED ("38", "'std.list:zip_with'", zip_with) - - M.depair = DEPRECATED ("41", "'std.list.depair'", depair) M.enpair = DEPRECATED ("41", "'std.list.enpair'", enpair) diff --git a/lib/std/table.lua b/lib/std/table.lua index e12d02e..71bf42f 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -472,18 +472,6 @@ monkeys = base.copy ({}, M) -- before deprecations and core merge local DEPRECATED = debug.DEPRECATED -M.clone_rename = DEPRECATED ("39", "'std.table.clone_rename'", - "use the new `map` argument to 'std.table.clone' instead", - function (map, t) - local r = merge_allfields ({}, t) - for i, v in pairs (map) do - r[v] = t[i] - r[i] = nil - end - return r - end) - - M.metamethod = DEPRECATED ("41", "'std.table.metamethod'", "use 'std.getmetamethod' instead", base.getmetamethod) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 7f8b7b2..4769a0e 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -35,16 +35,8 @@ specify std.functional: f = M.bind - - it writes an argument passing deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {nop, M, "bind"})). - to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {nop, M, "bind"})). - not_to_contain_error "was deprecated" - - context with bad arguments: - badargs.diagnose (f, "std.functional.bind (function, ?any*)") + badargs.diagnose (f, "std.functional.bind (function, table)") - it does not affect normal operation if no arguments are bound: expect (f (math.min, {}) (2, 3, 4)).to_be (2) @@ -60,10 +52,6 @@ specify std.functional: - it propagates nil arguments correctly: expect ({f (M.id, {[2]="b", [4]="d"}) (nil, 3, 5, 6, nil)}). to_equal {nil, "b", 3, "d", 5, 6, nil} - - it supports the legacy api: - expect (f (math.min) (2, 3, 4)).to_be (2) - expect (f (math.min, 1, 0) (2, 3, 4)).to_be (0) - expect (f (op.pow, nil, 3) (2)).to_be (8) - describe callable: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 7a90aa0..7cc0b1b 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -318,23 +318,6 @@ specify std.list: - it is the inverse of enpair: expect (f (l)).to_equal (t) - - context as an object method: - - before: - f = l.depair - - - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" - - - it returns a primitive table: - expect (prototype (f (l))).to_be "table" - - it works with an empty List: - expect (f (List {})).to_equal {} - - it is the inverse of enpair: - expect (f (l)).to_equal (t) - - describe elems: - context as a module function: @@ -800,31 +783,6 @@ specify std.list: l = List {} expect (f (fn, l)).to_equal (List {}) - - context as an object method: - - before: - f = l.map_with - - - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, fn})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - - - it returns a List object: - m = f (l, fn) - expect (prototype (m)).to_be "List" - - it creates a new List: - o = l - m = f (l, fn) - expect (l).to_equal (o) - expect (m).not_to_equal (o) - expect (l).to_equal (List {List {1, 2, 3}, List {4, 5}}) - - it maps a function over a List: - expect (f (l, fn)).to_equal (List {3, 2}) - - it works for an empty List: - l = List {} - expect (f (l, fn)).to_equal (List {}) - - describe project: - before: @@ -1165,29 +1123,6 @@ specify std.list: - it transposes rows and columns: expect (f (l)).to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) - - context as an object method: - - before: - f = l.transpose - - - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" - - - it returns a List object: - expect (prototype (f (l))).to_be "List" - - it works for an empty List: - expect (f (List {})).to_equal (List {}) - - it returns the result in a new List object: - expect (f (l)).not_to_be (l) - - it does not perturb the argument List: - m = f (l) - expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - - it transposes rows and columns: - expect (f (l)). - to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) - - describe zip_with: - before: @@ -1215,25 +1150,3 @@ specify std.list: expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) - it combines column entries with a function: expect (f (l, fn)).to_equal (List {135, 24}) - - - context as an object method: - - before: - f = l.zip_with - - - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, fn})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - - - it returns a List object: - expect (prototype (f (l, fn))).to_be "List" - - it works for an empty List: - expect (f (List {}, fn)).to_equal (List {}) - - it returns the result in a new List object: - expect (f (l, fn)):not_to_be (l) - - it does not perturb the argument List: - m = f (l, fn) - expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) - - it combines column entries with a function: - expect (f (l, fn)).to_equal (List {135, 24}) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 6ff2cad..a5872ed 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -9,7 +9,7 @@ before: | "monkey_patch", "new", "okeys", "pack", "project", "remove", "shape", "size", "sort", "unpack", "values" } - deprecations = { "clone_rename", "metamethod", "ripairs", "totable" } + deprecations = { "metamethod", "ripairs", "totable" } M = require "std.table" @@ -78,37 +78,6 @@ specify std.table: expect (f (subject, {k1 = "newkey"}).newkey).to_be (subject.k1) -- describe clone_rename: - - before: - subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } - - fname = "clone_rename" - f = M[fname] - - - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{}, subject})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" - - - it copies the subject: - expect (f ({}, subject)).to_copy (subject) - - it only makes a shallow copy: - expect (f ({}, subject).k2).to_be (subject.k2) - - - context when renaming some keys: - - before: - target = { newkey = subject.k1, k2 = subject.k2, k3 = subject.k3 } - - it renames during cloning: - expect (f ({k1 = "newkey"}, subject)).to_equal (target) - - it does not perturb the value in the renamed key field: - expect (f ({k1 = "newkey"}, subject).newkey).to_be (subject.k1) - - - it diagnoses non-table arguments: - expect (f {}).to_raise ("table expected") - expect (f ({}, "foo")).to_raise ("table expected") - - - describe clone_select: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } From 760cd0977a5636e3bf517a4350c813e387594279 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 16 May 2015 08:34:58 -0500 Subject: [PATCH 548/703] object: don't assume at least 1 object constructor argument. While it makes for slightly faster code in the common case, we can't grab the first _init argument with a named parameter as there's no way to differentiate between an explicit single nil argument and no arguments. * specs/object_spec.yaml (_init): Add a count field for number of arguments passed. (it propagates arguments correctly): Specify behaviour with zero or more contructor arguments. (it propagates nil arguments correctly): Specify behaviour with various nil valued constructor arguments. * lib/std/container.lua (__call): Don't try to capture the first argument as a named parameter. Use (...) to force evaluation to first argument when a table is expected. (M.__call): Likewise. Adjust argument error checks accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 18 +++++++++--------- specs/object_spec.yaml | 11 +++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 3136d4e..25bca34 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -153,7 +153,7 @@ local function mapfields (obj, src, map) end -local function __call (self, x, ...) +local function __call (self, ...) local mt = getmetatable (self) local obj_mt = mt local obj = {} @@ -171,9 +171,9 @@ local function __call (self, x, ...) end if type (mt._init) == "function" then - obj = mt._init (obj, x, ...) + obj = mt._init (obj, ...) else - obj = (self.mapfields or mapfields) (obj, x, mt._init) + obj = (self.mapfields or mapfields) (obj, (...), mt._init) end -- If a metatable was set, then merge our fields and use it. @@ -205,22 +205,22 @@ if _DEBUG.argcheck then local argerror, extramsg_toomany = debug.argerror, debug.extramsg_toomany - M.__call = function (self, x, ...) + M.__call = function (self, ...) local mt = getmetatable (self) -- A function initialised object can be passed arguments of any -- type, so only argcheck non-function initialised objects. if type (mt._init) ~= "function" then - local name, argt = mt._type, {...} + local name, n = mt._type, select ("#", ...) -- Don't count `self` as an argument for error messages, because -- it just refers back to the object being called: `Container {"x"}. - argcheck (name, 1, "table", x) - if next (argt) then - argerror (name, 2, extramsg_toomany ("argument", 1, 1 + maxn (argt)), 2) + argcheck (name, 1, "table", (...)) + if n > 1 then + argerror (name, 2, extramsg_toomany ("argument", 1, n), 2) end end - return __call (self, x, ...) + return __call (self, ...) end else diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index a4d6603..0fca29d 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -162,6 +162,7 @@ specify std.object: f1 = "proto1", f2 = "proto2", _init = function (self, ...) self.args = unpack {...} + self.count = select ("#", ...) return self end, } @@ -169,6 +170,16 @@ specify std.object: instance = Prototype {"param1", "param2"} expect ({instance.f1, instance.f2, instance.args}). to_equal {"proto1", "proto2", {"param1", "param2"}} + - it propagates arguments correctly: + expect (Prototype ().count).to_be (0) + expect (Prototype ("one").count).to_be (1) + expect (Prototype ("one", "two").count).to_be (2) + - it propagates nil arguments correctly: + expect (Prototype (nil).count).to_be (1) + expect (Prototype (false, nil).count).to_be (2) + expect (Prototype (nil, false).count).to_be (2) + expect (Prototype (nil, nil).count).to_be (2) + - describe field access: - before: From 6575e707ed95277d206679dabf5ea6727ea15bc0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 16 May 2015 10:01:19 -0500 Subject: [PATCH 549/703] table: deprecate table.len for richer std.len. * specs/table_spec.yaml (len): Add deprecation message behaviours. * specs/std_spec.yaml (len): Specify original behaviours, plus behaviours when passed a string or an object. * lib/std/table.lua (len): Deprecate. * lib/std.lua.in (len): Improved implementation that works as expected with strings and objects. * NEWS.md (New features, Deprecations): Updated. Signed-off-by: Gary V. Vaughan --- NEWS.md | 8 ++++++++ lib/std.lua.in | 7 +++++++ lib/std/table.lua | 12 +++++------ specs/std_spec.yaml | 46 +++++++++++++++++++++++++++++++++++++++---- specs/table_spec.yaml | 15 +++++++++----- 5 files changed, 72 insertions(+), 16 deletions(-) diff --git a/NEWS.md b/NEWS.md index 06b7b50..cbea2c4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,9 +11,17 @@ - New `operator.eqv` is similar to `operator.eq`, except that it succeeds when recursive eable contents are equivalent. + - New `std.len` replaces deprecated `std.table.len`. + - Passing the result of `functional.lambda` to `tostring` returns the original lambda string. +### Deprecations + + - `std.table.len` has been deprecated in favour of `std.len`, because it + is a portable stand-in for the core `#` operator, and because it also + works on strings, Objects and other traversable types. + ### Incompatible changes - Deprecated multi-argument `functional.bind` has been removed. diff --git a/lib/std.lua.in b/lib/std.lua.in index 925a750..fc4140e 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -185,6 +185,13 @@ M = { -- @usage lookup = std.getmetamethod (require "std.object", "__index") getmetamethod = X ("getmetamethod (?any, string)", base.getmetamethod), + --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. + -- @function len + -- @tparam object|string|table x operand + -- @treturn int length of list part of *t* + -- @usage for i = 1, len (t) do process (t[i]) end + len = X ("len (object|string|table)", base.len), + --- Overwrite core methods and metamethods with `std` enhanced versions. -- -- Write all functions from this module, except `std.barrel` and diff --git a/lib/std/table.lua b/lib/std/table.lua index 71bf42f..93ec8b7 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -300,13 +300,6 @@ M = { -- @usage globals = keys (_G) keys = X ("keys (table)", keys), - --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. - -- @function len - -- @tparam table t a table - -- @treturn int length of list part of *t* - -- @usage for i = 1, len (t) do process (t[i]) end - len = X ("len (table)", base.len), - --- Largest integer key in a table. -- @function maxn -- @tparam table t a table @@ -472,6 +465,11 @@ monkeys = base.copy ({}, M) -- before deprecations and core merge local DEPRECATED = debug.DEPRECATED + +M.len = DEPRECATED ("41.3", "'std.table.len'", + "use 'std.len' instead", X ("len (table)", base.len)) + + M.metamethod = DEPRECATED ("41", "'std.table.metamethod'", "use 'std.getmetamethod' instead", base.getmetamethod) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 32ec591..0033928 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -3,9 +3,9 @@ before: | global_table = "_G" exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", - "ielems", "ipairs", "ireverse", "monkey_patch", "npairs", - "pairs", "require", "ripairs", "rnpairs", "tostring", - "version" } + "ielems", "ipairs", "ireverse", "len", "monkey_patch", + "npairs", "pairs", "require", "ripairs", "rnpairs", + "tostring", "version" } -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -141,7 +141,7 @@ specify std: end - it installs std.table monkey patches: for _, api in ipairs { "clone", "clone_select", "depair", "empty", - "enpair", "flatten", "insert", "invert", "keys", "len", "maxn", + "enpair", "flatten", "insert", "invert", "keys", "maxn", "merge", "merge_select", "monkey_patch", "new", "pack", "project", "shape", "size", "sort", "values" } do @@ -336,6 +336,44 @@ specify std: expect (f {}).to_equal {} +- describe len: + - before: + f = M.len + + - context with bad arguments: + badargs.diagnose (f, "std.len (object|string|table)") + + - context with string argument: + - it returns the length of a string: + expect (f "").to_be (0) + expect (f "abc").to_be (3) + + - context with table argument: + - it returns the length 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 elements after a hole: + expect (f {1, 2, [5]=3}).to_be (2) + - it respects __len metamethod: + t = setmetatable ({1, 2, [5]=3}, {__len = function () return 42 end}) + expect (f (t)).to_be (42) + + - context with object argument: + - before: + Object = require "std.object" {} + subject = Object {"a", "b", "c"} + + - it returns the length of an object: + expect (f (subject)).to_be (3) + - it works with an empty object: + expect (f (Object)).to_be (0) + - it respects __len metamethod: + expect (f (Object {__len = function () return 42 end})).to_be (42) + expect (f (subject {__len = function () return 42 end})).to_be (42) + + - describe monkey_patch: - before: io_mt = {} diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index a5872ed..2d9b6b3 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -5,11 +5,10 @@ before: | extend_base = { "clone", "clone_select", "depair", "empty", "enpair", "flatten", "insert", "invert", "keys", - "len", "maxn", "merge", "merge_select", - "monkey_patch", "new", "okeys", "pack", "project", - "remove", "shape", "size", "sort", "unpack", - "values" } - deprecations = { "metamethod", "ripairs", "totable" } + "maxn", "merge", "merge_select", "monkey_patch", + "new", "okeys", "pack", "project", "remove", + "shape", "size", "sort", "unpack", "values" } + deprecations = { "len", "metamethod", "ripairs", "totable" } M = require "std.table" @@ -269,6 +268,12 @@ specify std.table: - before: f = M.len + - it writes a deprecation warning: + setdebug { deprecate = "nil" } + expect (capture (f, {{}})).to_contain_error "was deprecated" + setdebug { deprecate = false } + expect (capture (f, {{}})).not_to_contain_error "was deprecated" + - context with bad arguments: badargs.diagnose (f, "std.table.len (table)") From 9ec28db8df0abbf4d676fb3c1035b5cbdd6da799 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 17 May 2015 09:23:47 -0500 Subject: [PATCH 550/703] std: npairs now respects __len metamethods. * specs/std_spec.yaml (npairs): Specify behaviour when passing a table with __len metamethod. * lib/std/base.lua (npairs): Prefer result of __len metamethod to result of calling maxn. * NEWS.md (New features, Incompatible changes): Updated. Signed-off-by: Gary V. Vaughan --- NEWS.md | 8 ++++++++ lib/std/base.lua | 3 ++- specs/std_spec.yaml | 6 ++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index cbea2c4..b79e724 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,6 +13,8 @@ - New `std.len` replaces deprecated `std.table.len`. + - `std.npairs` now respects `__len` metamethod, if any. + - Passing the result of `functional.lambda` to `tostring` returns the original lambda string. @@ -34,6 +36,12 @@ - `functional.lambda` no longer returns a bare function, but a functable that can be called and stringified. + - 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. + ## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] diff --git a/lib/std/base.lua b/lib/std/base.lua index 5f2139f..88cc4a9 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -263,7 +263,8 @@ end local function npairs (t) - local i, n = 0, maxn (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 diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 0033928..d1d7d20 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -426,6 +426,12 @@ specify std: t[i] = v end expect (t).to_equal {"foo", 42, nil, nil, "five"} + - it respects __len metamethod: + t = {} + for i, v in f (setmetatable ({[2]=false}, {__len = function (self) return 4 end})) do + t[i] = 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 From 623c1b001cb8e5f349fb0283f19024814b6b7d9d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 17 May 2015 12:39:17 -0500 Subject: [PATCH 551/703] tuple: New interned immutable nil-preserving tuple object. * specs/tuple_spec.yaml: New file. Specify behaviour of a tuple. * specs/specs.mk (specl_SPECS): Add specs/tuple_spec.yaml. * lib/std/tuple.lua: New file. Implement tuple class. * build-aux/config.ld.in (file): Add new lib/std/tuple.lua. * local.mk (dist_luastd_DATA): Add lib/std/tuple.lua. (dist_classes_DATA): Add generated doc/classes/std.tuple.html. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 13 +++++ build-aux/config.ld.in | 1 + lib/std/tuple.lua | 129 +++++++++++++++++++++++++++++++++++++++++ local.mk | 2 + specs/specs.mk | 1 + specs/tuple_spec.yaml | 115 ++++++++++++++++++++++++++++++++++++ 6 files changed, 261 insertions(+) create mode 100644 lib/std/tuple.lua create mode 100644 specs/tuple_spec.yaml diff --git a/NEWS.md b/NEWS.md index b79e724..31cec2e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,19 @@ ### New features + - New `std.tuple` object, for managing interned immutable nil-preserving + tuples: + + ```lua + local std = require "std" + local t3 = std.tuple (nil, false, nil) + local t3_ = std.tuple (nil, false, nil) + if t3 == t3_ then + for i, v in std.npairs (t3) do print (i, v) end + end + --> 1 nil 2 false 3 nil + ``` + - New `functional.product` returns a list of combinations made by taking one element from each of the argument lists. See LDocs for an example. diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 7eff64d..62b9a89 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -25,6 +25,7 @@ file = { "../lib/std/optparse.lua", "../lib/std/set.lua", "../lib/std/strbuf.lua", + "../lib/std/tuple.lua", } new_type ("object", "Objects", false, "Fields") diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua new file mode 100644 index 0000000..8a00d37 --- /dev/null +++ b/lib/std/tuple.lua @@ -0,0 +1,129 @@ +--[[-- + Tuple container prototype. + + An interned immutable nil-preserving tuple object. + + Like Lua strings, tuples with the same elements can be quickly compared with + a straight forward `==` comparison. + + The immutability guarantees only work if you don't change the contents of + tables after adding them to a tuple. Don't do that! + + Prototype Chain + --------------- + + table + `-> Object + `-> Container + `-> Tuple + + @classmod std.tuple + @see std.container +]] + +local Container = require "std.container" {} +local objtype = require "std.base".prototype + + +-- Stringify tuple values, as a memoization key. +-- @tparam Tuple tup tuple to process +-- @treturn string a comma separated ordered list of stringified *tup* elements +local function argstr (tuple) + local s = {} + for i = 1, tuple.n do + local v = tuple[i] + s[i] = (type (v) ~= "string" and "%s" or "%q"):format (v) + end + return table.concat (s, ", ") +end + + +-- Maintain a weak functable of all interned tuples. +-- @static +-- @function intern +-- @param ... tuple elements +-- @treturn table an interned proxied table with ... elements +local intern = setmetatable ({}, { + __mode = "kv", + + __call = function (self, ...) + local t = {n = select ("#", ...), ...} + local k = argstr (t) + if self[k] == nil then + -- Use a proxy table so that __newindex always fires + self[k] = setmetatable ({}, { __index = t }) + end + return self[k] + end, +}) + + + +--[[ ============= ]]-- +--[[ Tuple Object. ]]-- +--[[ ============= ]]-- + + +--- Tuple prototype object. +-- +-- Set also inherits all the fields from @{std.container.Container} +-- @object Tuple +-- @string[opt="Tuple"] _type object name +-- @int n number of tuple elements +-- @see std.container +-- @usage +-- local Tuple = require "std.tuple" +-- function count (...) +-- argtuple = Tuple (...) +-- return argtuple.n +-- end +-- count () --> 0 +-- count (nil) --> 1 +-- count (false) --> 1 +-- count (false, nil, true, nil) --> 4 +return Container { + _type = "Tuple", + + _init = function (obj, ...) + return intern (...) + end, + + __index = getmetatable (intern ()).__index, + + --- Return the length of this tuple. + -- @static + -- @function __len + -- @tparam Tuple tup object to process + -- @treturn int number of elements in *tup* + -- @usage + -- -- Only works on Lua 5.2 or newer: + -- #Tuple (nil, 2, nil) --> 3 + -- -- For compatibility with Lua 5.1, use @{std.len} + -- len (Tuple (nil, 2, nil) + __len = function (self) + return self.n + end, + + --- Prevent mutation of *tup* + -- This metamethod never returns, because Tuples are immutable. + -- @static + -- @function __newindex + -- @tparam Tuple tup object to process + -- @param k tuple key + -- @param v tuple value + __newindex = function (self, k, v) + error ("cannot change immutable tuple object", 2) + end, + + --- Return a string representation of *tup* + -- @static + -- @function __tostring + -- @tparam Tuple tup object to process + -- @treturn string representation of *tup* + -- @usage + -- -- 'Tuple ("nil", nil, false)' + -- print (Tuple ("nil", nil, false)) + __tostring = function (self) + return ("%s (%s)"):format (objtype (self), argstr (self)) + end, +} diff --git a/local.mk b/local.mk index e3fdc97..5f9977b 100644 --- a/local.mk +++ b/local.mk @@ -79,6 +79,7 @@ dist_luastd_DATA = \ lib/std/string.lua \ lib/std/table.lua \ lib/std/tree.lua \ + lib/std/tuple.lua \ $(NOTHING_ELSE) # For bugwards compatibility with LuaRocks 2.1, while ensuring that @@ -133,6 +134,7 @@ dist_classes_DATA += \ $(srcdir)/doc/classes/std.set.html \ $(srcdir)/doc/classes/std.strbuf.html \ $(srcdir)/doc/classes/std.tree.html \ + $(srcdir)/doc/classes/std.tuple.html \ $(NOTHING_ELSE) dist_modules_DATA += \ diff --git a/specs/specs.mk b/specs/specs.mk index 591b61c..f42f651 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -28,6 +28,7 @@ specl_SPECS = \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ + $(srcdir)/specs/tuple_spec.yaml \ $(srcdir)/specs/std_spec.yaml \ $(NOTHING_ELSE) diff --git a/specs/tuple_spec.yaml b/specs/tuple_spec.yaml new file mode 100644 index 0000000..d1080a0 --- /dev/null +++ b/specs/tuple_spec.yaml @@ -0,0 +1,115 @@ +before: + objtype = require "std.object".type + Tuple = require "std.tuple" + + t0, t1, t2 = Tuple (), Tuple "one", Tuple (false, true) + + +specify std.tuple: +- describe require: + - it does not perturb the global namespace: + expect (show_apis {added_to="_G", by="std.tuple"}). + to_equal {} + + +- describe construction: + - it constructs a new tuple: + expect (t0).not_to_be (Tuple) + expect (objtype (t0)).to_be "Tuple" + - it initialises tuple with constructor parameters: + expect (objtype (t2)).to_be "Tuple" + expect (t2[1]).to_be (false) + expect (t2[2]).to_be (true) + - it understands nil valued elements: + t5 = Tuple (nil, nil, 1, nil, nil) + expect (t5[3]).to_be (1) + expect (t5[5]).to_be (nil) + + +- describe length: + - context with n field: + - it returns the number of elements: + expect (t0.n).to_be (0) + expect (t1.n).to_be (1) + expect (t2.n).to_be (2) + - it counts nil valued elements: + expect (Tuple (nil).n).to_be (1) + expect (Tuple (nil, false, nil, nil).n).to_be (4) + + - context with std.len: + - before: + len = require "std".len + - it returns the number of elements: + expect (len (t0)).to_be (0) + expect (len (t1)).to_be (1) + expect (len (t2)).to_be (2) + - it counts nil valued elements: + expect (len (Tuple (nil))).to_be (1) + expect (len (Tuple (nil, false, nil, nil))).to_be (4) + + +- describe indexing: + - it dereferences elements: + expect (t2[1]).to_be (false) + expect (t2[2]).to_be (true) + expect (t2.n).to_be (2) + - it returns nil-valued elements: + t3 = Tuple (nil, false, nil) + expect (t3[1]).to_be (nil) + expect (t3[2]).to_be (false) + expect (t3[3]).to_be (nil) + expect (t3.n).to_be (3) + - it returns nil for out-of-bound indices: + expect (t1[0]).to_be (nil) + expect (t1[1]).not_to_be (nil) + expect (t1[2]).to_be (nil) + expect (t1[-1]).to_be (nil) + expect (t1.foo).to_be (nil) + + +- describe interning: + - it interns all tuples: + expect (Tuple ()).to_be (Tuple ()) + expect (Tuple ("a", 2)).to_be (Tuple ("a", 2)) + - it interns nil valued elements: + expect (Tuple (nil)).to_be (Tuple (nil)) + expect (Tuple (nil, false, nil)).to_be (Tuple (nil, false, nil)) + expect (Tuple (nil)).not_to_be (Tuple (false)) + - it distinguishes nil from no elements: + expect (Tuple (nil)).not_to_be (Tuple ()) + + +- describe immutability: + - it diagnoses mutation attempts: + function fn () t2[1] = 1 end + expect (fn ()).to_raise "cannot change immutable tuple object" + + +- describe traversing: + - before: + npairs = require "std".npairs + pretty = function (t) + local r = {} + for i, v in npairs (t) do r[i] = tostring (v) end + return table.concat (r, ",") + end + + - it iterates over the elements: + expect (pretty (Tuple ("a", "b", "c"))).to_be "a,b,c" + - it works with 0-tuple: + expect (pretty (Tuple ())).to_be "" + - it understands nil elements: + expect (pretty (Tuple (nil))).to_be "nil" + expect (pretty (Tuple (false, nil))).to_be "false,nil" + expect (pretty (Tuple (nil, false))).to_be "nil,false" + expect (pretty (Tuple (nil, nil))).to_be "nil,nil" + + +- describe stringification: + - it starts with the object type: + expect (tostring (Tuple ())).to_match "^Tuple " + - it contains all the elements: + elems = {"a", "b", "c"} + for _, e in ipairs (elems) do + expect (tostring (Tuple (unpack (elems)))).to_match (e) + end From 7ab2017a56cfb0e42dc12ba78079f02b5b9bddc2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 17 May 2015 13:04:27 -0500 Subject: [PATCH 552/703] tuple: format requires explicit strings in Lua 5.1. * lib/std/tuple.lua (argstr): Call tostring on format arguments. Signed-off-by: Gary V. Vaughan --- lib/std/tuple.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 8a00d37..4d40ab7 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -32,7 +32,7 @@ local function argstr (tuple) local s = {} for i = 1, tuple.n do local v = tuple[i] - s[i] = (type (v) ~= "string" and "%s" or "%q"):format (v) + s[i] = (type (v) ~= "string" and "%s" or "%q"):format (tostring (v)) end return table.concat (s, ", ") end From eb1b929a0eb3ff8b2cbb43a3a7b1464f0aadcd74 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 May 2015 08:47:53 -0500 Subject: [PATCH 553/703] container: remove unused base.maxn import. * lib/std/container.lua (maxn): Remove unused base.maxn import. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index 25bca34..72e5d90 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -37,7 +37,7 @@ local base = require "std.base" local debug = require "std.debug" local ipairs, pairs, okeys = base.ipairs, base.pairs, base.okeys -local insert, len, maxn = base.insert, base.len, base.maxn +local insert, len = base.insert, base.len local okeys, prototype, tostring = base.okeys, base.prototype, base.tostring local argcheck = debug.argcheck From cbf39834648c32bb0a0304cae8e6ae62c86ca6c2 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 18 May 2015 09:18:01 -0500 Subject: [PATCH 554/703] std: rnpairs now respects __len metamethod. * specs/std_spec.yaml (rnpairs): Specify behaviour when operating on a table with __len metamethod set. (npairs): Update __len behaviour specification to match. * lib/std/base.lua (rnpairs): Use __len metamethod when available. * lib/std.lua.in (rnpairs): Update LDocs. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 2 +- lib/std.lua.in | 2 +- lib/std/base.lua | 3 ++- specs/std_spec.yaml | 10 ++++++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 31cec2e..fb5003a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -26,7 +26,7 @@ - New `std.len` replaces deprecated `std.table.len`. - - `std.npairs` now respects `__len` metamethod, if any. + - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. - Passing the result of `functional.lambda` to `tostring` returns the original lambda string. diff --git a/lib/std.lua.in b/lib/std.lua.in index fc4140e..d379ed5 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -203,7 +203,7 @@ M = { monkey_patch = X ("monkey_patch (?table)", monkey_patch), --- Ordered iterator for integer keyed values. - -- Like ipairs, but does not stop until the largest integer key. + -- Like ipairs, but does not stop until the __len or maxn of *t*. -- @function npairs -- @tparam table t a table -- @treturn function iterator function diff --git a/lib/std/base.lua b/lib/std/base.lua index 88cc4a9..bfa6517 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -374,7 +374,8 @@ end local function rnpairs (t) - local oob = maxn (t) + 1 + local m = getmetamethod (t, "__len") + local oob = (m and m (t) or maxn (t)) + 1 return function (t, n) n = n - 1 diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index d1d7d20..c07d847 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -428,8 +428,8 @@ specify std: expect (t).to_equal {"foo", 42, nil, nil, "five"} - it respects __len metamethod: t = {} - for i, v in f (setmetatable ({[2]=false}, {__len = function (self) return 4 end})) do - t[i] = tostring (v) + 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: @@ -593,6 +593,12 @@ specify std: 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 From 75a804931153cd7c54cd5b332559ec5470a13c9f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 21 May 2015 13:20:36 -0500 Subject: [PATCH 555/703] table: unpack now works on tuples. * specs/tuple_spec.yaml (unpacking): Specify behaviours for unpacking tuples. * lib/std/tuple.lua (Tuple.__contents): Set an explicit metatable field for the proxy table holding the tuple contents. * lib/std/base.lua (unpack): If there is a __contents metatable field in the table being unpacked, use it. Also, respect __len in preference to maxn(). * lib/std/table.lua (unpack): Update LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 8 +++++++- lib/std/table.lua | 2 +- lib/std/tuple.lua | 13 +++++++++++-- specs/tuple_spec.yaml | 22 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index bfa6517..fb30d87 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -105,7 +105,13 @@ end local _unpack = table.unpack or unpack local function unpack (t, i, j) - return _unpack (t, i or 1, j or maxn (t)) + if j == nil then + -- respect __len, and then maxn if nil j was passed + local m = getmetamethod (t, "__len") + j = m and m (t) or maxn (t) + end + -- use the __contents metatable instead of t when present + return _unpack ( (getmetatable (t) or {}).__contents or t, i or 1, j) end diff --git a/lib/std/table.lua b/lib/std/table.lua index 93ec8b7..764be80 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -434,7 +434,7 @@ M = { -- @usage table.concat (sort (object)) sort = X ("sort (table, ?function)", sort), - --- Enhance core *table.unpack* to always unpack up to `maxn (t)`. + --- 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 diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 4d40ab7..d1a73ba 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -51,7 +51,7 @@ local intern = setmetatable ({}, { local k = argstr (t) if self[k] == nil then -- Use a proxy table so that __newindex always fires - self[k] = setmetatable ({}, { __index = t }) + self[k] = setmetatable ({}, { __contents = t, __index = t }) end return self[k] end, @@ -88,6 +88,15 @@ return Container { return intern (...) end, + -- The actual contents of *tup*. + -- This ensures __newindex will trigger for existing elements too. + -- It also informs `table.unpack` that that the elements to unpack are + -- not in the usual place. + __contents = getmetatable (intern ()).__contents, + + + -- Another reference to the proxy table, so that [] operations work as + -- expected. __index = getmetatable (intern ()).__index, --- Return the length of this tuple. @@ -104,7 +113,7 @@ return Container { return self.n end, - --- Prevent mutation of *tup* + --- Prevent mutation of *tup*. -- This metamethod never returns, because Tuples are immutable. -- @static -- @function __newindex diff --git a/specs/tuple_spec.yaml b/specs/tuple_spec.yaml index d1080a0..0e7d66c 100644 --- a/specs/tuple_spec.yaml +++ b/specs/tuple_spec.yaml @@ -88,6 +88,7 @@ specify std.tuple: - describe traversing: - before: npairs = require "std".npairs + pretty = function (t) local r = {} for i, v in npairs (t) do r[i] = tostring (v) end @@ -105,6 +106,27 @@ specify std.tuple: expect (pretty (Tuple (nil, nil))).to_be "nil,nil" +- describe unpacking: + - before: + unpack = require "std.table".unpack + + collect = function (t) + local r = {unpack (t)} + for i = 1, t.n do r[i] = tostring (r[i]) end + return table.concat (r, ",") + end + + - it returns all elements: + expect (collect (Tuple ("a", "b", "c"))).to_be "a,b,c" + - it works with 0-tuple: + expect (collect (Tuple ())).to_be "" + - it understands nil elements: + expect (collect (Tuple (nil))).to_be "nil" + expect (collect (Tuple (false, nil))).to_be "false,nil" + expect (collect (Tuple (nil, false))).to_be "nil,false" + expect (collect (Tuple (nil, nil))).to_be "nil,nil" + + - describe stringification: - it starts with the object type: expect (tostring (Tuple ())).to_match "^Tuple " From 19ebab1d06fbc072c7036113e76e6cd90fe643d6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 21 May 2015 14:07:44 -0500 Subject: [PATCH 556/703] specs: separate deprecations from documented apis. * specs/list_spec.yaml (deprecations): List deprecated apis. (exported_apis): Remove deprecated apis. (std.list): Merge before comparison. Signed-off-by: Gary V. Vaughan --- specs/list_spec.yaml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 7cc0b1b..0e3a955 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -2,11 +2,12 @@ before: this_module = "std.list" global_table = "_G" - exported_apis = { "append", "compare", "concat", "cons", "depair", - "elems", "enpair", "filter", "flatten", "foldl", - "foldr", "index_key", "index_value", "map", - "map_with", "project", "relems", "rep", "reverse", - "shape", "sub", "tail", "transpose", "zip_with" } + exported_apis = { "append", "compare", "concat", "cons", "rep", "sub", + "tail" } + deprecations = { "depair", "enpair", "elems", "filter", "flatten", "foldl", + "foldr", "index_key", "index_value", "map", "map_with", + "project", "relems", "reverse", "shape", "transpose", + "zip_with" } M = require (this_module) @@ -22,9 +23,12 @@ specify std.list: expect (show_apis {added_to="_G", by="std.list"}). to_equal {} - it exports the documented apis: - t = {} + t, apis = {}, require "std.base".copy (exported_apis) for k in pairs (M) do t[#t + 1] = k end - expect (t).to_contain.a_permutation_of (exported_apis) + for _, v in ipairs (deprecations) do + apis[#apis + 1] = v + end + expect (t).to_contain.a_permutation_of (apis) - context via the std module: - it does not touch the global table: From 377332f2915fd1e0ee7f52ee6863098a0593c449 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 May 2015 12:39:59 -0500 Subject: [PATCH 557/703] object: don't conflate object type and prototype! * specs/object_spec.yaml (prototype): Deprecate. (type): Specify behaviours for object.type and Ob:type. * lib/std/object.lua (prototype): Deprecate. (type): Bless. Adust all callers and references accordingly. * NEWS.md (New features, Deprecations): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 16 ++++++++ lib/std.lua.in | 2 +- lib/std/base.lua | 4 +- lib/std/container.lua | 7 ++-- lib/std/debug.lua | 6 +-- lib/std/list.lua | 7 ++-- lib/std/object.lua | 23 +++++------ lib/std/operator.lua | 6 +-- lib/std/set.lua | 6 +-- lib/std/strbuf.lua | 2 +- lib/std/tree.lua | 10 ++--- lib/std/tuple.lua | 2 +- specs/container_spec.yaml | 7 ++-- specs/list_spec.yaml | 70 ++++++++++++++++----------------- specs/object_spec.yaml | 81 +++++++++++++++++++++++++++++++-------- specs/set_spec.yaml | 27 +++++++------ specs/spec_helper.lua | 4 +- specs/std_spec.yaml | 4 +- specs/table_spec.yaml | 8 ++-- specs/tree_spec.yaml | 11 +++--- 20 files changed, 182 insertions(+), 121 deletions(-) diff --git a/NEWS.md b/NEWS.md index fb5003a..27c134c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,19 @@ ### New features + - We used to have an object module method, `std.object.type`, which + often got imported using: + + ```lua + local prototype = require "std.object".type + ``` + + So we renamed it to `std.object.prototype`, and deprecated the `type` + method; but that was a mistake, because core Lua provides `type`, + and `io.type` (and in recent releases, `math.type`. For orthogonality, + we're going back to using `std.object.type`, which just makes more + sense. Sorry! + - New `std.tuple` object, for managing interned immutable nil-preserving tuples: @@ -33,6 +46,9 @@ ### Deprecations + - `std.object.prototype` has been deprecated in favor of + `std.object.type` for orthogonality with `io.type` and `math.type`. + - `std.table.len` has been deprecated in favour of `std.len`, because it is a portable stand-in for the core `#` operator, and because it also works on strings, Objects and other traversable types. diff --git a/lib/std.lua.in b/lib/std.lua.in index d379ed5..9186c21 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -300,7 +300,7 @@ return setmetatable (M, { -- `name`, otherwise `nil` if nothing was found -- @usage -- local std = require "std" - -- local prototype = std.object.prototype + -- local objtype = std.object.objtype __index = function (self, name) local ok, t = pcall (require, "std." .. name) if ok then diff --git a/lib/std/base.lua b/lib/std/base.lua index fb30d87..ee6b60b 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -304,7 +304,7 @@ local function collect (ifn, ...) end -local function prototype (o) +local function objtype (o) return (getmetatable (o) or {})._type or io.type (o) or type (o) end @@ -482,7 +482,7 @@ return { compare = compare, -- object.lua -- - prototype = prototype, + objtype = objtype, -- package.lua -- dirsep = dirsep, diff --git a/lib/std/container.lua b/lib/std/container.lua index 72e5d90..1f8fe21 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -38,7 +38,7 @@ local debug = require "std.debug" local ipairs, pairs, okeys = base.ipairs, base.pairs, base.okeys local insert, len = base.insert, base.len -local okeys, prototype, tostring = base.okeys, base.prototype, base.tostring +local okeys, objtype, tostring = base.okeys, base.objtype, base.tostring local argcheck = debug.argcheck @@ -232,7 +232,7 @@ end function M.__tostring (self) local n, k_ = 1, nil - local buf = { prototype (self), " {" } -- pre-buffer object open + local buf = { objtype (self), " {" } -- pre-buffer object open for _, k in ipairs (okeys (self)) do -- for ordered public members local v = self[k] @@ -296,7 +296,8 @@ return setmetatable ({ -- it has to be done manually. mapfields = modulefunction (M.mapfields), - prototype = modulefunction (prototype), + prototype = modulefunction (objtype), + type = modulefunction (objtype), }, { _type = "Container", diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 9657846..1d21a9d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -34,7 +34,7 @@ local base = require "std.base" local _DEBUG = debug_init._DEBUG local argerror, raise = base.argerror, base.raise -local prototype, unpack = base.prototype, base.unpack +local objtype, unpack = base.objtype, base.unpack local copy, split, tostring = base.copy, base.split, base.tostring local insert, last, len, maxn = base.insert, base.last, base.len, base.maxn local ipairs, pairs = base.ipairs, base.pairs @@ -253,7 +253,7 @@ end local function extramsg_mismatch (expectedtypes, actual, index) - local actualtype = prototype (actual) + local actualtype = objtype (actual) -- Tidy up actual type for display. if actualtype == "nil" then @@ -364,7 +364,7 @@ if _DEBUG.argcheck then end end - actualtype = prototype (actual) + actualtype = objtype (actual) if check == actualtype then return true elseif check == "list" or check == "#list" then diff --git a/lib/std/list.lua b/lib/std/list.lua index aba6154..144c58b 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -18,10 +18,9 @@ local debug = require "std.debug" local Object = require "std.object" {} local ipairs, pairs = base.ipairs, base.pairs -local len = base.len -local compare = base.compare -local prototype = base.prototype -local unpack = base.unpack +local len = base.len +local compare = base.compare +local unpack = base.unpack local M, List diff --git a/lib/std/object.lua b/lib/std/object.lua index 423507f..aea7cbe 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -32,10 +32,12 @@ local base = require "std.base" local container = require "std.container" +local debug = require "std.debug" local Container = container {} -local getmetamethod, prototype = base.getmetamethod, base.prototype +local getmetamethod, objtype = base.getmetamethod, base.objtype +local DEPRECATED = debug.DEPRECATED --- Root object. @@ -77,8 +79,7 @@ return Container { _type = "Object", -- No need for explicit module functions here, because calls to, e.g. - -- `Object.prototype` will automatically fall back metamethods in - -- `__index`. + -- `Object.type` will automatically fall back metamethods in `__index`. __index = { --- Clone an Object. @@ -127,7 +128,7 @@ return Container { -- file objects, or @{type} otherwise. -- -- @static - -- @function prototype + -- @function type -- @param x anything -- @treturn string type of *x* -- @usage @@ -142,16 +143,16 @@ return Container { -- }, -- } -- local stack = Stack {} - -- assert (stack:prototype () == getmetatable (stack)._type) + -- assert (stack:type () == getmetatable (stack)._type) -- - -- local prototype = Object.prototype - -- assert (prototype (stack) == getmetatable (stack)._type) + -- local objtype = Object.type + -- assert (objtype (stack) == getmetatable (stack)._type) -- -- local h = io.open (os.tmpname (), "w") - -- assert (prototype (h) == io.type (h)) + -- assert (objtype (h) == io.type (h)) -- - -- assert (prototype {} == type {}) - prototype = prototype, + -- assert (type {} == type {}) + type = objtype, --- Return *obj* with references to the fields of *src* merged in. @@ -188,7 +189,7 @@ return Container { -- Backwards compatibility: - type = prototype, + prototype = DEPRECATED ("41.3", "'std.object.prototype'", objtype), }, diff --git a/lib/std/operator.lua b/lib/std/operator.lua index ea27e18..e6e7c07 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -6,8 +6,8 @@ local base = require "std.base" -local pairs, prototype, tostring = - base.pairs, base.prototype, base.tostring +local pairs, objtype, tostring = + base.pairs, base.objtype, base.tostring local function eqv (a, b) @@ -18,7 +18,7 @@ local function eqv (a, b) -- Unless we have two tables, what we have cannot be equivalent here. if type (a) ~= "table" or type (b) ~= "table" then return false end - local type_a, type_b = prototype (a), prototype (b) + local type_a, type_b = objtype (a), objtype (b) if type_a ~= type_b then return false end local keyeqv = {} -- keys requiring recursive equivalence test diff --git a/lib/std/set.lua b/lib/std/set.lua index dacb33a..c3a095d 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -21,7 +21,7 @@ local base = require "std.base" local Container = require "std.container" {} -local ielems, pairs, prototype = base.ielems, base.pairs, base.prototype +local ielems, pairs, type = base.ielems, base.pairs, base.objtype local Set -- forward declaration @@ -141,7 +141,7 @@ end -- @see std.object.__call -- @usage -- local std = require "std" --- std.prototype (std.set) --> "Set" +-- std.type (std.set) --> "Set" -- os.exit (0) Set = Container { _type = "Set", @@ -229,7 +229,7 @@ Set = Container { keys[#keys + 1] = tostring (k) end table.sort (keys) - return prototype (self) .. " {" .. table.concat (keys, ", ") .. "}" + return type (self) .. " {" .. table.concat (keys, ", ") .. "}" end, diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index d23af9a..2a85e15 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -26,7 +26,7 @@ local debug = require "std.debug" local Object = require "std.object" {} -local ielems, insert, prototype = base.ielems, base.insert, base.prototype +local ielems, insert = base.ielems, base.insert local M, StrBuf diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 2feaedc..3a6f821 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -22,8 +22,8 @@ local operator = require "std.operator" local Container = require "std.container" {} -local ielems, ipairs, leaves, pairs, prototype = - base.ielems, base.ipairs, base.leaves, base.pairs, base.prototype +local ielems, ipairs, leaves, pairs, objtype = + base.ielems, base.ipairs, base.leaves, base.pairs, base.objtype local last, len = base.last, base.len local reduce = base.reduce @@ -138,7 +138,7 @@ Tree = Container { -- @usage -- del_other_window = keymap[{"C-x", "4", KEY_DELETE}] __index = function (tr, i) - if prototype (i) == "table" then + if objtype (i) == "table" then return reduce (operator.get, tr, ielems, i) else return rawget (tr, i) @@ -154,9 +154,9 @@ Tree = Container { -- @usage -- function bindkey (keylist, fn) keymap[keylist] = fn end __newindex = function (tr, i, v) - if prototype (i) == "table" then + if objtype (i) == "table" then for n = 1, len (i) - 1 do - if prototype (tr[i[n]]) ~= "Tree" then + if objtype (tr[i[n]]) ~= "Tree" then rawset (tr, i[n], Tree {}) end tr = tr[i[n]] diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index d1a73ba..03b47cf 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -22,7 +22,7 @@ ]] local Container = require "std.container" {} -local objtype = require "std.base".prototype +local objtype = require "std.base".objtype -- Stringify tuple values, as a memoization key. diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 8f1c333..592cc10 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -1,6 +1,5 @@ before: Container = require "std.container" {} - prototype = require "std.object".prototype specify std.container: - context when required: @@ -31,7 +30,7 @@ specify std.container: - it constructs a new container: expect (things).not_to_be (Container) expect (type (things)).to_be "table" - expect (prototype (things)).to_be "Container" + expect (objtype (things)).to_be "Container" - it reuses the container metatable: o, p = things {"o"}, things {"p"} expect (getmetatable (o)).to_be (getmetatable (p)) @@ -40,7 +39,7 @@ specify std.container: expect (o).to_equal (things) - it serves as a prototype for new instances: o = things {} - expect (prototype (o)).to_be "Container" + expect (objtype (o)).to_be "Container" expect (o).to_copy (things) expect (getmetatable (o)).to_be (getmetatable (things)) - it separates '_' prefixed fields: @@ -104,7 +103,7 @@ specify std.container: expect (type (tostring (things))).to_be "string" - it contains the type: expect (tostring (Container {})).to_contain "Container" - expect (tostring (things)).to_contain (prototype (things)) + expect (tostring (things)).to_contain (objtype (things)) - it contains the ordered array part elements: expect (tostring (things)).to_contain "one, two, three" - it contains the ordered dictionary part elements: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 0e3a955..ff12cf6 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -40,7 +40,7 @@ specify std.list: - it constructs a new list: l = List:clone {} expect (l).not_to_be (List) - expect (prototype (l)).to_be "List" + expect (objtype (l)).to_be "List" - it reuses the List metatable: l, m = List:clone {"l"}, List:clone {"m"} expect (getmetatable (l)).to_be (getmetatable (m)) @@ -49,7 +49,7 @@ specify std.list: expect (m).to_equal (l) - it serves as a prototype for new instances: m = l:clone {} - expect (prototype (m)).to_be "List" + expect (objtype (m)).to_be "List" expect (m).to_equal (l) expect (getmetatable (m)).to_be (getmetatable (l)) @@ -58,7 +58,7 @@ specify std.list: - it constructs a new List: l = List {} expect (l).not_to_be (List) - expect (prototype (l)).to_be "List" + expect (objtype (l)).to_be "List" - it reuses the List metatable: l, m = List {"l"}, List {"m"} expect (getmetatable (l)).to_be (getmetatable (m)) @@ -67,7 +67,7 @@ specify std.list: expect (m).to_equal (l) - it serves as a prototype for new instances: m = l {} - expect (prototype (m)).to_be "List" + expect (objtype (m)).to_be "List" expect (m).to_equal (l) expect (getmetatable (m)).to_be (getmetatable (l)) @@ -87,7 +87,7 @@ specify std.list: - context as a module function: - it returns a List object: - expect (prototype (f (l, "quux"))).to_be "List" + expect (objtype (f (l, "quux"))).to_be "List" - it works for an empty List: expect (f (List {}, "quux")).to_equal (List {"quux"}) - it appends an item to a List: @@ -99,7 +99,7 @@ specify std.list: f = l.append - it returns a List object: - expect (prototype (f (l, "quux"))).to_be "List" + expect (objtype (f (l, "quux"))).to_be "List" - it works for an empty List: expect (f (List {}, "quux")).to_equal (List {"quux"}) - it appends an item to a List: @@ -108,7 +108,7 @@ specify std.list: - context as a List metamethod: - it returns a List object: - expect (prototype (l + "quux")).to_be "List" + expect (objtype (l + "quux")).to_be "List" - it works for an empty list: expect (List {} + "quux").to_equal (List {"quux"}) - it appends an item to a list: @@ -221,7 +221,7 @@ specify std.list: - context as a module function: - it returns a List object: - expect (prototype (f (l, l))).to_be "List" + expect (objtype (f (l, l))).to_be "List" - it works for an empty List: expect (f (List {}, {"baz"})).to_equal (List {"baz"}) expect (f (List {}, List {"baz"})).to_equal (List {"baz"}) @@ -240,7 +240,7 @@ specify std.list: f = l.concat - it returns a List object: - expect (prototype (f (l, l))).to_be "List" + expect (objtype (f (l, l))).to_be "List" - it works for an empty List: expect (f (List {}, {"baz"})).to_equal (List {"baz"}) expect (f (List {}, List {"baz"})).to_equal (List {"baz"}) @@ -257,7 +257,7 @@ specify std.list: # Beware that .. operations are right associative - context as a List metamethod: - it returns a List object: - expect (prototype (l .. List {"baz"})).to_be "List" + expect (objtype (l .. List {"baz"})).to_be "List" - it works for an empty List: expect (List {} .. {"baz"}).to_equal (List {"baz"}) expect (List {} .. List {"baz"}).to_equal (List {"baz"}) @@ -281,7 +281,7 @@ specify std.list: - context as a module function: - it returns a List object: - expect (prototype (f (l, "x"))).to_be "List" + expect (objtype (f (l, "x"))).to_be "List" - it prepends an item to a List: expect (f (l, "x")).to_equal (List {"x", "foo", "bar", "baz"}) - it works for empty Lists: @@ -292,7 +292,7 @@ specify std.list: f = l.cons - it returns a List object: - expect (prototype (f (l, "x"))).to_be "List" + expect (objtype (f (l, "x"))).to_be "List" - it prepends an item to a List: expect (f (l, "x")).to_equal (List {"x", "foo", "bar", "baz"}) - it works for empty Lists: @@ -315,7 +315,7 @@ specify std.list: expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a primitive table: - expect (prototype (f (l))).to_be "table" + expect (objtype (f (l))).to_be "table" - it works with an empty List: l = List {} expect (f (l)).to_equal {} @@ -376,7 +376,7 @@ specify std.list: - context as a module function: - it returns a List object: - expect (prototype (f (t))).to_be "List" + expect (objtype (f (t))).to_be "List" - it works for an empty table: expect (f {}).to_equal (List {}) - it turns a table into a List of pairs: @@ -400,7 +400,7 @@ specify std.list: expect (capture (f, {p, l})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (p, l))).to_be "List" + expect (objtype (f (p, l))).to_be "List" - it works for an empty List: expect (f (p, List {})).to_equal (List {}) - it filters a List according to a predicate: @@ -417,7 +417,7 @@ specify std.list: expect (capture (f, {l, p})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l, p))).to_be "List" + expect (objtype (f (l, p))).to_be "List" - it works for an empty List: expect (f (List {}, p)).to_equal (List {}) - it filters a List according to a predicate: @@ -439,7 +439,7 @@ specify std.list: expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l))).to_be "List" + expect (objtype (f (l))).to_be "List" - it works for an empty List: l = List {} expect (f (l)).to_equal (List {}) @@ -458,7 +458,7 @@ specify std.list: expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l))).to_be "List" + expect (objtype (f (l))).to_be "List" - it works for an empty List: l = List {} expect (f (l)).to_equal (List {}) @@ -720,7 +720,7 @@ specify std.list: expect (capture (f, {sq, l})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (sq, l))).to_be "List" + expect (objtype (f (sq, l))).to_be "List" - it works for an empty List: expect (f (sq, List {})).to_equal (List {}) - it creates a new List: @@ -744,7 +744,7 @@ specify std.list: - it returns a List object: m = f (l, sq) - expect (prototype (m)).to_be "List" + expect (objtype (m)).to_be "List" - it works for an empty List: expect (f (List {}, sq)).to_equal (List {}) - it creates a new List: @@ -774,7 +774,7 @@ specify std.list: - it returns a List object: m = f (fn, l) - expect (prototype (m)).to_be "List" + expect (objtype (m)).to_be "List" - it creates a new List: o = l m = f (fn, l) @@ -807,7 +807,7 @@ specify std.list: expect (capture (f, {"third", l})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f ("third", l))).to_be "List" + expect (objtype (f ("third", l))).to_be "List" - it works with an empty List: expect (f ("third", List {})).to_equal (List {}) - it projects a List of fields from a List of tables: @@ -826,7 +826,7 @@ specify std.list: expect (capture (f, {l, "third"})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l, "third"))).to_be "List" + expect (objtype (f (l, "third"))).to_be "List" - it works with an empty List: expect (f (List {}, "third")).to_equal (List {}) - it projects a List of fields from a List of tables: @@ -886,7 +886,7 @@ specify std.list: - context as a module function: - it returns a List object: - expect (prototype (f (l, 3))).to_be "List" + expect (objtype (f (l, 3))).to_be "List" - it works for an empty List: expect (f (List {}, 99)).to_equal (List {}) - it repeats the contents of a List: @@ -898,7 +898,7 @@ specify std.list: f = l.rep - it returns a List object: - expect (prototype (f (l, 3))).to_be "List" + expect (objtype (f (l, 3))).to_be "List" - it works for an empty List: expect (f (List {}, 99)).to_equal (List {}) - it repeats the contents of a List: @@ -921,7 +921,7 @@ specify std.list: expect (capture (f, {{}})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l))).to_be "List" + expect (objtype (f (l))).to_be "List" - it works for an empty List: l = List {} expect (f (l)).to_equal (List {}) @@ -943,7 +943,7 @@ specify std.list: expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l))).to_be "List" + expect (objtype (f (l))).to_be "List" - it works for an empty List: expect (f (List {})).to_equal (List {}) - it makes a new reversed List: @@ -969,7 +969,7 @@ specify std.list: expect (capture (f, {{0}, l})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f ({2, 3}, l))).to_be "List" + expect (objtype (f ({2, 3}, l))).to_be "List" - it works for an empty List: expect (f ({0}, List {})).to_equal (List {}) - it returns the result in a new List object: @@ -999,7 +999,7 @@ specify std.list: expect (capture (f, {l, {0}})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l, {2, 3}))).to_be "List" + expect (objtype (f (l, {2, 3}))).to_be "List" - it works for an empty List: expect (f (List {}, {0})).to_equal (List {}) - it returns the result in a new List object: @@ -1030,7 +1030,7 @@ specify std.list: - context as a module function: - it returns a List object: - expect (prototype (f (l, 1, 1))).to_be "List" + expect (objtype (f (l, 1, 1))).to_be "List" - it makes a List from a subrange of another List: expect (f (l, 2, 5)).to_equal (List {2, 3, 4, 5}) - it truncates the result if 'to' argument is too large: @@ -1051,7 +1051,7 @@ specify std.list: f = l.sub - it returns a List object: - expect (prototype (f (l, 1, 1))).to_be "List" + expect (objtype (f (l, 1, 1))).to_be "List" - it makes a List from a subrange of another List: expect (f (l, 2, 5)).to_equal (List {2, 3, 4, 5}) - it truncates the result if 'to' argument is too large: @@ -1079,7 +1079,7 @@ specify std.list: - context as a module function: - it returns a List object: - expect (prototype (f (l))).to_be "List" + expect (objtype (f (l))).to_be "List" - it makes a new List with the first element removed: expect (f (l)).to_equal (List {2, 3, 4, 5, 6, 7}) - it works for an empty List: @@ -1092,7 +1092,7 @@ specify std.list: f = l.tail - it returns a List object: - expect (prototype (f (l))).to_be "List" + expect (objtype (f (l))).to_be "List" - it makes a new List with the first element removed: expect (f (l)).to_equal (List {2, 3, 4, 5, 6, 7}) - it works for an empty List: @@ -1116,7 +1116,7 @@ specify std.list: expect (capture (f, {l})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l))).to_be "List" + expect (objtype (f (l))).to_be "List" - it works for an empty List: expect (f (List {})).to_equal (List {}) - it returns the result in a new List object: @@ -1144,7 +1144,7 @@ specify std.list: expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" - it returns a List object: - expect (prototype (f (l, fn))).to_be "List" + expect (objtype (f (l, fn))).to_be "List" - it works for an empty List: expect (f (List {}, fn)).to_equal (List {}) - it returns the result in a new List object: diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 0fca29d..57f6934 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -1,7 +1,6 @@ before: Object = require "std.object" obj = Object {"foo", "bar", baz="quux"} - prototype = Object.prototype function copy (t) local r = {} @@ -22,7 +21,7 @@ specify std.object: obj = Object:clone {} expect (obj).not_to_be (Object) expect (type (obj)).to_be "table" - expect (prototype (obj)).to_be "Object" + expect (objtype (obj)).to_be "Object" - it reuses the Object metatable: o = obj:clone {"o"} p = o:clone {"p"} @@ -32,7 +31,7 @@ specify std.object: expect (obj:clone {}).to_copy (obj) - it serves as a prototype for new instances: o = obj:clone {} - expect (prototype (o)).to_be "Object" + expect (objtype (o)).to_be "Object" expect (o).to_copy (obj) expect (getmetatable (o)).to_be (getmetatable (obj)) - it separates '_' prefixed fields: @@ -44,7 +43,15 @@ specify std.object: expect (getmetatable (obj)._baz).to_be "quux" - describe prototype: - - before: o = Object {} + - before: + o = Object {} + prototype = Object.prototype + + - it writes a deprecation warning: + setdebug { deprecate = "nil" } + expect (capture (prototype, {o})).to_contain_error "was deprecated" + setdebug { deprecate = false } + expect (capture (prototype, {o})).not_to_contain_error "was deprecated" - context when called from the object module: - it reports the prototype stored in the object's metatable: @@ -84,11 +91,51 @@ specify std.object: p = Portal {} expect (p:prototype ()).to_be "Demon" expect ((p {}):prototype ()).to_be "Demon" - - context backwards compatibility: - - it reports the prototype stored in the object's metatable: - expect (Object.type (o)).to_be "Object" + + +- describe type: + - before: + o = Object {} + objtype = Object.type + + - context when called from the object module: + - it reports the type stored in the object's metatable: + expect (objtype (o)).to_be "Object" + - it reports the type of a cloned object: + expect (objtype (o {})).to_be "Object" + - it reports the type of a derived object: + Example = Object {_type = "Example"} + expect (objtype (Example)).to_be "Example" + - it reports the type of a cloned derived object: + Portal = Object {_type = "Demon"} + p = Portal {} + expect (objtype (p)).to_be "Demon" + expect (objtype (p {})).to_be "Demon" + - it recognizes a file object: + h = io.open (os.tmpname ()) + expect (objtype (h)).to_be "file" + h:close () + expect (objtype (h)).to_be "closed file" + - it recognizes a primitive object: + expect (objtype (nil)).to_be "nil" + expect (objtype (false)).to_be "boolean" + expect (objtype (0.0)).to_be "number" + expect (objtype "0.0").to_be "string" + expect (objtype (function () end)).to_be "function" + expect (objtype {}).to_be "table" + - context when called as an object method: - it reports the type stored in the object's metatable: expect (o:type ()).to_be "Object" + - it reports the type of a cloned object: + expect ((o {}):type ()).to_be "Object" + - it reports the type of a subclassed object: + Example = Object {_type = "Example"} + expect (Example:type ()).to_be "Example" + - it reports the type of a cloned subclassed object: + Portal = Object {_type = "Demon"} + p = Portal {} + expect (p:type ()).to_be "Demon" + expect ((p {}):type ()).to_be "Demon" - describe instantiation from a prototype: @@ -188,12 +235,12 @@ specify std.object: _init = { "field", "method"}, field = "in prototype", method = function (self, ...) - return prototype (self) .. " class, " .. + return objtype (self) .. " class, " .. table.concat ({...}, ", ") end, } instance = Prototype {"in object", function (self, ...) - return prototype (self) .. " instance, " .. + return objtype (self) .. " instance, " .. table.concat ({...}, ", ") end, } @@ -213,12 +260,12 @@ specify std.object: expect (instance.newfield).to_be "new" - it allows new instance methods to be added: instance.newmethod = function (self) - return prototype (self) .. ", new instance method" + return objtype (self) .. ", new instance method" end expect (instance:newmethod ()).to_be "Prototype, new instance method" - it allows new class methods to be added: Prototype.newmethod = function (self) - return prototype (self) .. ", new class method" + return objtype (self) .. ", new class method" end expect (Prototype.newmethod (instance)). to_be "Prototype, new class method" @@ -226,14 +273,14 @@ specify std.object: - describe object method propagation: - context with no custom instance methods: - # :prototype is a method defined by the root object + # :type is a method defined by the root object - it inherits prototype object methods: instance = Object {} - expect (instance:prototype ()).to_be "Object" + expect (instance:type ()).to_be "Object" - it propagates prototype methods to derived instances: Derived = Object {_type = "Derived"} instance = Derived {} - expect (instance:prototype ()).to_be "Derived" + expect (instance:type ()).to_be "Derived" - context with custom object methods: - before: bag = Object { @@ -247,10 +294,10 @@ specify std.object: } # :prototype is a method defined by the root object - it inherits prototype object methods: - expect (bag:prototype ()).to_be "bag" + expect (bag:type ()).to_be "bag" - it propagates prototype methods to derived instances: instance = bag {} - expect (instance:prototype ()).to_be "bag" + expect (instance:type ()).to_be "bag" - it supports method calls: expect (bag:add "foo").to_be (bag) expect (bag.foo).to_be (1) @@ -298,7 +345,7 @@ specify std.object: expect (type (tostring (obj))).to_be "string" - it contains the type: expect (tostring (Object {})).to_contain "Object" - expect (tostring (obj)).to_contain (prototype (obj)) + expect (tostring (obj)).to_contain (objtype (obj)) - it contains the ordered array part elements: expect (tostring (obj)).to_contain "one, two, three" - it contains the ordered dictionary part elements: diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index ac87faf..a8b103d 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -1,6 +1,5 @@ before: Set = require "std.set" - prototype = require "std.object".prototype s = Set {"foo", "bar", "bar"} specify std.set: @@ -14,13 +13,13 @@ specify std.set: - it constructs a new set: s = Set {} expect (s).not_to_be (Set) - expect (prototype (s)).to_be "Set" + expect (objtype (s)).to_be "Set" - it initialises set with constructor parameters: t = Set {"foo", "bar", "bar"} expect (t).to_equal (s) - it serves as a prototype for new instances: obj = s {} - expect (prototype (obj)).to_be "Set" + expect (objtype (obj)).to_be "Set" expect (obj).to_equal (s) expect (getmetatable (obj)).to_be (getmetatable (s)) @@ -30,7 +29,7 @@ specify std.set: - before: s = Set {"foo", "bar", "baz"} - it returns a set object: - expect (prototype (Set.delete (s, "foo"))).to_be "Set" + expect (objtype (Set.delete (s, "foo"))).to_be "Set" - it is destructive: Set.delete (s, "bar") expect (s).not_to_have_member "bar" @@ -54,7 +53,7 @@ specify std.set: - context when called as a Set module function: - it returns a set object: - expect (prototype (Set.difference (r, s))).to_be "Set" + expect (objtype (Set.difference (r, s))).to_be "Set" - it is non-destructive: Set.difference (r, s) expect (r).to_equal (Set {"foo", "bar", "baz"}) @@ -63,7 +62,7 @@ specify std.set: expect (Set.difference (r, s)).to_equal (Set {"foo"}) - context when called as a set metamethod: - it returns a set object: - expect (prototype (r - s)).to_be "Set" + expect (objtype (r - s)).to_be "Set" - it is non-destructive: q = r - s expect (r).to_equal (Set {"foo", "bar", "baz"}) @@ -93,7 +92,7 @@ specify std.set: - before: s = Set {"foo"} - it returns a set object: - expect (prototype (Set.insert (s, "bar"))).to_be "Set" + expect (objtype (Set.insert (s, "bar"))).to_be "Set" - it is destructive: Set.insert (s, "bar") expect (s).to_have_member "bar" @@ -112,7 +111,7 @@ specify std.set: s = Set {"foo"} - it returns a set object: s["bar"] = true - expect (prototype (s)).to_be "Set" + expect (objtype (s)).to_be "Set" - it is destructive: s["bar"] = true expect (s).to_have_member "bar" @@ -136,7 +135,7 @@ specify std.set: - context when called as a Set module function: - it returns a set object: - expect (prototype (Set.intersection (r, s))).to_be "Set" + expect (objtype (Set.intersection (r, s))).to_be "Set" - it is non-destructive: Set.intersection (r, s) expect (r).to_equal (Set {"foo", "bar", "baz"}) @@ -147,7 +146,7 @@ specify std.set: - context when called as a set metamethod: - it returns a set object: q = r * s - expect (prototype (q)).to_be "Set" + expect (objtype (q)).to_be "Set" - it is non-destructive: q = r * s expect (r).to_equal (Set {"foo", "bar", "baz"}) @@ -234,7 +233,7 @@ specify std.set: - context when called as a Set module function: - it returns a set object: - expect (prototype (Set.symmetric_difference (r, s))). + expect (objtype (Set.symmetric_difference (r, s))). to_be "Set" - it is non-destructive: Set.symmetric_difference (r, s) @@ -245,7 +244,7 @@ specify std.set: to_equal (Set {"foo", "quux"}) - context when called as a set metamethod: - it returns a set object: - expect (prototype (r / s)).to_be "Set" + expect (objtype (r / s)).to_be "Set" - it is non-destructive: q = r / s expect (r).to_equal (Set {"foo", "bar", "baz"}) @@ -261,7 +260,7 @@ specify std.set: - context when called as a Set module function: - it returns a set object: - expect (prototype (Set.union (r, s))).to_be "Set" + expect (objtype (Set.union (r, s))).to_be "Set" - it is non-destructive: Set.union (r, s) expect (r).to_equal (Set {"foo", "bar", "baz"}) @@ -271,7 +270,7 @@ specify std.set: to_equal (Set {"foo", "bar", "baz", "quux"}) - context when called as a set metamethod: - it returns a set object: - expect (prototype (r + s)).to_be "Set" + expect (objtype (r + s)).to_be "Set" - it is non-destructive: q = r + s expect (r).to_equal (Set {"foo", "bar", "baz"}) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 9c60717..5b420bc 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -83,9 +83,9 @@ function init (M, mname, fname) end --- A copy of base.lua:prototype, so that an unloadable base.lua doesn't +-- A copy of base.lua:objtype, so that an unloadable base.lua doesn't -- prevent everything else from working. -function prototype (o) +function objtype (o) return (getmetatable (o) or {})._type or io.type (o) or type (o) end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index c07d847..1c06fc8 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -48,7 +48,7 @@ specify std: lazy = M.set expect (lazy).to_be (require "std.set") - it loads submodule functions on demand: - expect (M.object.prototype (M.set {"Lazy"})). + expect (M.object.type (M.set {"Lazy"})). to_be "Set" - describe assert: @@ -182,7 +182,7 @@ specify std: lazy = M.strbuf expect (lazy).to_be (require "std.strbuf") - it loads submodule functions on demand: - expect (M.object.prototype (M.strbuf {"Lazy"})). + expect (M.object.type (M.strbuf {"Lazy"})). to_be "StrBuf" diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 2d9b6b3..50095a7 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -123,7 +123,7 @@ specify std.table: badargs.diagnose (f, "std.table.depair (list of lists)") - it returns a primitive table: - expect (prototype (f (l))).to_be "table" + expect (objtype (f (l))).to_be "table" - it works with an empty table: expect (f {}).to_equal {} - it is the inverse of enpair: @@ -157,7 +157,7 @@ specify std.table: badargs.diagnose (f, "std.table.enpair (table)") - it returns a table: - expect (prototype (f (t))).to_be "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: @@ -551,7 +551,7 @@ specify std.table: badargs.diagnose (f, "std.table.project (any, list of tables)") - it returns a table: - expect (prototype (f ("third", l))).to_be "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: @@ -632,7 +632,7 @@ specify std.table: badargs.diagnose (f, "std.table.shape (table, table)") - it returns a table: - expect (prototype (f ({2, 3}, l))).to_be "table" + expect (objtype (f ({2, 3}, l))).to_be "table" - it works for an empty table: expect (f ({0}, {})).to_equal ({}) - it returns the result in a new table: diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 025d7fb..2ab0833 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -6,7 +6,6 @@ before: | specify std.tree: - before: - prototype = (require "std.object").prototype t = {foo="foo", fnord={branch={bar="bar", baz="baz"}}, quux="quux"} tr = Tree (t) @@ -25,18 +24,18 @@ specify std.tree: - it constructs a new tree: tr = Tree {} expect (tr).not_to_be (Tree) - expect (prototype (tr)).to_be "Tree" + expect (objtype (tr)).to_be "Tree" - it turns a table argument into a tree: - expect (prototype (Tree (t))).to_be "Tree" + expect (objtype (Tree (t))).to_be "Tree" - it does not turn table argument values into sub-Trees: - expect (prototype (tr["fnord"])).to_be "table" + expect (objtype (tr["fnord"])).to_be "table" - it understands branched nodes: expect (tr).to_equal (Tree (t)) expect (tr[{"fnord"}]).to_equal (t.fnord) expect (tr[{"fnord", "branch", "bar"}]).to_equal (t.fnord.branch.bar) - it serves as a prototype for new instances: obj = tr {} - expect (prototype (obj)).to_be "Tree" + expect (objtype (obj)).to_be "Tree" expect (obj).to_equal (tr) expect (getmetatable (obj)).to_be (getmetatable (tr)) @@ -398,7 +397,7 @@ specify std.tree: - describe __tostring: - it returns a string: - expect (prototype (tostring (tr))).to_be "string" + expect (objtype (tostring (tr))).to_be "string" - it shows the type name: expect (tostring (tr)).to_contain "Tree" - it shows the contents in order: | From cacd0d5fd0742bb57648a12f9ae82267c8662539 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 May 2015 12:58:34 -0500 Subject: [PATCH 558/703] std: add `std.type`. * specs/std_spec.yaml (type): Specify behaviour of std.type. * lib/std.lua.in (type): Re-export base.objtype. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std.lua.in | 7 +++++++ specs/std_spec.yaml | 21 ++++++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 27c134c..7458319 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,9 @@ we're going back to using `std.object.type`, which just makes more sense. Sorry! + - Similarly, for orthogonality with core Lua `type`, we also export the + `std.object.type` function as `std.type`. + - New `std.tuple` object, for managing interned immutable nil-preserving tuples: diff --git a/lib/std.lua.in b/lib/std.lua.in index 9186c21..27336f7 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -275,6 +275,13 @@ M = { -- print (std.tostring {foo="bar","baz"}) tostring = X ("tostring (?any)", base.tostring), + --- Type of an object, or primitive. + -- @function type + -- @param x anything + -- @treturn string type of *x* + -- @see std.object.type + type = X ("type (?any)", base.objtype), + version = "General Lua libraries / @VERSION@", } diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 1c06fc8..6a4f050 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -5,7 +5,7 @@ before: | exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", "ielems", "ipairs", "ireverse", "len", "monkey_patch", "npairs", "pairs", "require", "ripairs", "rnpairs", - "tostring", "version" } + "tostring", "type", "version" } -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -632,3 +632,22 @@ specify std: - it renders keys with invalid symbol names compactly: expect (f { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }). to_be '{?=1,[]=1,_=0,a-key=1,word=0}' + + +- describe type: + - before: f = M.type + + - it recognizes a primitive object: + expect (f (nil)).to_be (type (nil)) + expect (f (false)).to_be (type (false)) + expect (f (0.0)).to_be (type (0.0)) + expect (f "0.0").to_be (type "0.0") + expect (f (function () end)).to_be (type (function () end)) + expect (f {}).to_be (type {}) + - it recognizes a file object: + h = io.open (os.tmpname ()) + expect (f (h)).to_be (io.type (h)) + h:close () + expect (f (h)).to_be (io.type (h)) + - it recognizes _type metatable field: + expect (f (setmetatable ({}, {_type = "objet"}))).to_be "objet" From bf056afaa91c231de37c44f01d34a53520260316 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 25 May 2015 21:07:54 +0100 Subject: [PATCH 559/703] Fix tiny typo --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 7458319..521e97e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -38,7 +38,7 @@ an example. - New `operator.eqv` is similar to `operator.eq`, except that it succeeds - when recursive eable contents are equivalent. + when recursive table contents are equivalent. - New `std.len` replaces deprecated `std.table.len`. From 563bd1b13d79e42f1afdd1aca4784333fbd516a5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 9 Jun 2015 11:26:08 -0500 Subject: [PATCH 560/703] refactor: import std.base as local std. * lib/std.lua.in, lib/std/container.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/object.lua, lib/std/operator.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua: Import std.base as local std. Adjust clients. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 36 ++++++++++++++++++------------------ lib/std/container.lua | 8 ++++---- lib/std/debug.lua | 14 +++++++------- lib/std/functional.lua | 14 +++++++------- lib/std/io.lua | 14 +++++++------- lib/std/list.lua | 26 +++++++++++++------------- lib/std/math.lua | 6 +++--- lib/std/object.lua | 4 ++-- lib/std/operator.lua | 4 ++-- lib/std/optparse.lua | 6 +++--- lib/std/package.lua | 6 +++--- lib/std/set.lua | 6 +++--- lib/std/strbuf.lua | 4 ++-- lib/std/string.lua | 28 ++++++++++++++-------------- lib/std/table.lua | 34 +++++++++++++++++----------------- lib/std/tree.lua | 8 ++++---- 16 files changed, 109 insertions(+), 109 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 27336f7..38500e7 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -23,13 +23,13 @@ ]] -local base = require "std.base" +local std = require "std.base" local M, monkeys local function monkey_patch (namespace) - base.copy (namespace or _G, monkeys) + std.copy (namespace or _G, monkeys) return M end @@ -93,7 +93,7 @@ M = { -- @usage -- std.assert (expected ~= nil, "100% unexpected!") -- std.assert (expected ~= nil, "%s unexpected!", expected) - assert = X ("assert (?any, ?string, [any...])", base.assert), + assert = X ("assert (?any, ?string, [any...])", std.assert), --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). -- @@ -117,14 +117,14 @@ M = { -- @see pairs -- @usage -- for value in std.elems {a = 1, b = 2, c = 5} do process (value) end - elems = X ("elems (table)", base.elems), + elems = X ("elems (table)", std.elems), --- Evaluate a string as Lua code. -- @function eval -- @string s string of Lua code -- @return result of evaluating `s` -- @usage std.eval "math.min (2, 10)" - eval = X ("eval (string)", base.eval), + eval = X ("eval (string)", std.eval), --- An iterator over the integer keyed elements of a sequence. -- If *t* has a `__len` metamethod, iterate up to the index it returns. @@ -137,7 +137,7 @@ M = { -- @see ipairs -- @usage -- for v in std.ielems {"a", "b", "c"} do process (v) end - ielems = X ("ielems (table)", base.ielems), + ielems = X ("ielems (table)", std.ielems), --- An iterator over elements of a sequence, until the first `nil` value. -- @@ -161,7 +161,7 @@ M = { -- for i, v in std.ipairs (args) do -- print (string.format ("%d=%s", i, v)) -- end - ipairs = X ("ipairs (table)", base.ipairs), + ipairs = X ("ipairs (table)", std.ipairs), --- Return a new table with element order reversed. -- Apart from the order of the elments returned, this function follows @@ -175,7 +175,7 @@ M = { -- @usage -- local rielems = std.functional.compose (std.ireverse, std.ielems) -- for e in rielems (l) do process (e) end - ireverse = X ("ireverse (table)", base.ireverse), + ireverse = X ("ireverse (table)", std.ireverse), --- Return named metamethod, if any, otherwise `nil`. -- @function getmetamethod @@ -183,14 +183,14 @@ M = { -- @string n name of metamethod to lookup -- @treturn function|nil metamethod function, or `nil` if no metamethod -- @usage lookup = std.getmetamethod (require "std.object", "__index") - getmetamethod = X ("getmetamethod (?any, string)", base.getmetamethod), + getmetamethod = X ("getmetamethod (?any, string)", std.getmetamethod), --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. -- @function len -- @tparam object|string|table x operand -- @treturn int length of list part of *t* -- @usage for i = 1, len (t) do process (t[i]) end - len = X ("len (object|string|table)", base.len), + len = X ("len (object|string|table)", std.len), --- Overwrite core methods and metamethods with `std` enhanced versions. -- @@ -212,7 +212,7 @@ M = { -- @see rnpairs -- @usage -- for i,v in npairs {"one", nil, "three"} do ... end - npairs = X ("npairs (table)", base.npairs), + npairs = X ("npairs (table)", std.npairs), --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. -- @function pairs @@ -224,7 +224,7 @@ M = { -- @see ipairs -- @usage -- for k, v in pairs {"a", b = "c", foo = 42} do process (k, v) end - pairs = X ("pairs (table)", base.pairs), + pairs = X ("pairs (table)", std.pairs), --- Enhance core `require` to assert version number compatibility. -- By default match against the last substring of (dot-delimited) @@ -238,7 +238,7 @@ M = { -- @usage -- -- posix.version == "posix library for Lua 5.2 / 32" -- posix = require ("posix", "29") - require = X ("require (string, ?string, ?string, ?string)", base.require), + require = X ("require (string, ?string, ?string, ?string)", std.require), --- An iterator like ipairs, but in reverse. -- Apart from the order of the elments returned, this function follows @@ -251,7 +251,7 @@ M = { -- @see ipairs -- @see rnpairs -- @usage for i, v = ripairs (t) do ... end - ripairs = X ("ripairs (table)", base.ripairs), + ripairs = X ("ripairs (table)", std.ripairs), --- An iterator like npairs, but in reverse. -- Apart from the order of the elments returned, this function follows @@ -264,7 +264,7 @@ M = { -- @see ripairs -- @usage -- for i,v in rnpairs {"one", nil, "three"} do ... end - rnpairs = X ("rnpairs (table)", base.rnpairs), + rnpairs = X ("rnpairs (table)", std.rnpairs), --- Enhance core `tostring` to render table contents as a string. -- @function tostring @@ -273,20 +273,20 @@ M = { -- @usage -- -- {1=baz,foo=bar} -- print (std.tostring {foo="bar","baz"}) - tostring = X ("tostring (?any)", base.tostring), + tostring = X ("tostring (?any)", std.tostring), --- Type of an object, or primitive. -- @function type -- @param x anything -- @treturn string type of *x* -- @see std.object.type - type = X ("type (?any)", base.objtype), + type = X ("type (?any)", std.objtype), version = "General Lua libraries / @VERSION@", } -monkeys = base.copy ({}, M) +monkeys = std.copy ({}, M) -- Don't monkey_patch these apis into _G! for _, api in ipairs {"barrel", "monkey_patch", "version"} do diff --git a/lib/std/container.lua b/lib/std/container.lua index 1f8fe21..e3749f6 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -33,12 +33,12 @@ local _DEBUG = require "std.debug_init"._DEBUG -local base = require "std.base" +local std = require "std.base" local debug = require "std.debug" -local ipairs, pairs, okeys = base.ipairs, base.pairs, base.okeys -local insert, len = base.insert, base.len -local okeys, objtype, tostring = base.okeys, base.objtype, base.tostring +local ipairs, pairs, okeys = std.ipairs, std.pairs, std.okeys +local insert, len = std.insert, std.len +local okeys, objtype, tostring = std.okeys, std.objtype, std.tostring local argcheck = debug.argcheck diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 1d21a9d..b1bad1e 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -30,14 +30,14 @@ local debug_init = require "std.debug_init" -local base = require "std.base" +local std = require "std.base" local _DEBUG = debug_init._DEBUG -local argerror, raise = base.argerror, base.raise -local objtype, unpack = base.objtype, base.unpack -local copy, split, tostring = base.copy, base.split, base.tostring -local insert, last, len, maxn = base.insert, base.last, base.len, base.maxn -local ipairs, pairs = base.ipairs, base.pairs +local argerror, raise = std.argerror, std.raise +local objtype, unpack = std.objtype, std.unpack +local copy, split, tostring = std.copy, std.split, std.tostring +local insert, last, len, maxn = std.insert, std.last, std.len, std.maxn +local ipairs, pairs = std.ipairs, std.pairs local M @@ -558,7 +558,7 @@ else -- Turn off argument checking if _DEBUG is false, or a table containing -- a false valued `argcheck` field. - argcheck = base.nop + argcheck = std.nop argscheck = function (decl, inner) return inner end end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index e790a7e..916dab3 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,13 +8,13 @@ ]] -local base = require "std.base" +local std = require "std.base" local debug = require "std.debug" local ielems, ipairs, ireverse, npairs, pairs = - base.ielems, base.ipairs, base.ireverse, base.npairs, base.pairs + std.ielems, std.ipairs, std.ireverse, std.npairs, std.pairs local callable, copy, len, reduce, unpack = - base.callable, base.copy, base.len, base.reduce, base.unpack + std.callable, std.copy, std.len, std.reduce, std.unpack local loadstring = loadstring or load @@ -147,7 +147,7 @@ end local function memoize (fn, normalize) if normalize == nil then - normalize = function (...) return base.tostring {...} end + normalize = function (...) return std.tostring {...} end end return setmetatable ({}, { @@ -347,7 +347,7 @@ local M = { -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} - collect = X ("collect ([func], any...)", base.collect), + collect = X ("collect ([func], any...)", std.collect), --- Compose functions. -- @function compose @@ -514,7 +514,7 @@ local M = { -- @see id -- @usage -- if unsupported then vtable["memrmem"] = nop end - nop = base.nop, -- ignores all arguments + nop = std.nop, -- ignores all arguments --- Functional list product. -- @@ -584,7 +584,7 @@ local DEPRECATED = debug.DEPRECATED M.eval = DEPRECATED ("41", "'std.functional.eval'", - "use 'std.eval' instead", base.eval) + "use 'std.eval' instead", std.eval) local function fold (fn, d, ifn, ...) diff --git a/lib/std/io.lua b/lib/std/io.lua index 056d7cc..eaf37f2 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,13 +11,13 @@ ]] -local base = require "std.base" +local std = require "std.base" local debug = require "std.debug" local argerror = debug.argerror local catfile, dirsep, insert, len, leaves, split = - base.catfile, base.dirsep, base.insert, base.len, base.leaves, base.split -local ipairs, pairs = base.ipairs, base.pairs + std.catfile, std.dirsep, std.insert, std.len, std.leaves, std.split +local ipairs, pairs = std.ipairs, std.pairs local setmetatable = debug.setmetatable @@ -73,7 +73,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.io = base.copy (namespace.io or {}, monkeys) + namespace.io = std.copy (namespace.io or {}, monkeys) if namespace.io.stdin then local mt = getmetatable (namespace.io.stdin) or {} @@ -159,7 +159,7 @@ M = { -- @see catdir -- @see splitdir -- @usage filepath = catfile ("relative", "path", "filename") - catfile = X ("catfile (string...)", base.catfile), + catfile = X ("catfile (string...)", std.catfile), --- Die with error. -- This function uses the same rules to build a message prefix @@ -274,10 +274,10 @@ M = { } -monkeys = base.copy ({}, M) -- before deprecations and core merge +monkeys = std.copy ({}, M) -- before deprecations and core merge -return base.merge (M, io) +return std.merge (M, io) diff --git a/lib/std/list.lua b/lib/std/list.lua index 144c58b..0f71c12 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -12,15 +12,15 @@ ]] -local base = require "std.base" +local std = require "std.base" local debug = require "std.debug" local Object = require "std.object" {} -local ipairs, pairs = base.ipairs, base.pairs -local len = base.len -local compare = base.compare -local unpack = base.unpack +local ipairs, pairs = std.ipairs, std.pairs +local len = std.len +local compare = std.compare +local unpack = std.unpack local M, List @@ -207,7 +207,7 @@ end local function flatten (l) local r = List {} - for v in base.leaves (ipairs, l) do + for v in std.leaves (ipairs, l) do r[#r + 1] = v end return r @@ -220,7 +220,7 @@ local function foldl (fn, d, t) for i = 2, len (d) do tail[#tail + 1] = d[i] end d, t = d[1], tail end - return base.reduce (fn, d, base.ielems, t) + return std.reduce (fn, d, std.ielems, t) end @@ -230,8 +230,8 @@ local function foldr (fn, d, t) for i = 1, last - 1 do u[#u + 1] = d[i] end d, t = d[last], u end - return base.reduce ( - function (x, y) return fn (y, x) end, d, base.ielems, base.ireverse (t)) + return std.reduce ( + function (x, y) return fn (y, x) end, d, std.ielems, std.ireverse (t)) end @@ -281,10 +281,10 @@ local function project (x, l) end -local function relems (l) return base.ielems (base.ireverse (l)) end +local function relems (l) return std.ielems (std.ireverse (l)) end -local function reverse (l) return List (base.ireverse (l)) end +local function reverse (l) return List (std.ireverse (l)) end local function shape (s, l) @@ -359,9 +359,9 @@ M.enpair = DEPRECATED ("41", "'std.list.enpair'", enpair) m.enpair = DEPRECATED ("41", "'std.list:enpair'", enpair) M.elems = DEPRECATED ("41", "'std.list.elems'", - "use 'std.ielems' instead", base.ielems) + "use 'std.ielems' instead", std.ielems) m.elems = DEPRECATED ("41", "'std.list:elems'", - "use 'std.ielems' instead", base.ielems) + "use 'std.ielems' instead", std.ielems) M.filter = DEPRECATED ("41", "'std.list.filter'", "use 'std.functional.filter' instead", filter) diff --git a/lib/std/math.lua b/lib/std/math.lua index 2a87904..7654181 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,7 +11,7 @@ ]] -local base = require "std.base" +local std = require "std.base" local M @@ -30,7 +30,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.math = base.copy (namespace.math or {}, M) + namespace.math = std.copy (namespace.math or {}, M) return M end @@ -78,4 +78,4 @@ M = { } -return base.merge (M, math) +return std.merge (M, math) diff --git a/lib/std/object.lua b/lib/std/object.lua index aea7cbe..e6960a9 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -30,12 +30,12 @@ -- Container is derived from it. Confused? ;-) -local base = require "std.base" +local std = require "std.base" local container = require "std.container" local debug = require "std.debug" local Container = container {} -local getmetamethod, objtype = base.getmetamethod, base.objtype +local getmetamethod, objtype = std.getmetamethod, std.objtype local DEPRECATED = debug.DEPRECATED diff --git a/lib/std/operator.lua b/lib/std/operator.lua index e6e7c07..7d418c4 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -4,10 +4,10 @@ @module std.operator ]] -local base = require "std.base" +local std = require "std.base" local pairs, objtype, tostring = - base.pairs, base.objtype, base.tostring + std.pairs, std.objtype, std.tostring local function eqv (a, b) diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 29ebe93..b589fbc 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -12,12 +12,12 @@ ]=] -local base = require "std.base" +local std = require "std.base" local Object = require "std.object" {} -local ipairs, pairs = base.ipairs, base.pairs -local insert, last, len = base.insert, base.last, base.len +local ipairs, pairs = std.ipairs, std.pairs +local insert, last, len = std.insert, std.last, std.len diff --git a/lib/std/package.lua b/lib/std/package.lua index 562c531..5d456fb 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -11,13 +11,13 @@ ]] -local base = require "std.base" +local std = require "std.base" local debug = require "std.debug" local catfile, escape_pattern, invert = - base.catfile, base.escape_pattern, base.invert + std.catfile, std.escape_pattern, std.invert local ipairs, pairs, split, unpack = - base.ipairs, base.pairs, base.split, base.unpack + std.ipairs, std.pairs, std.split, std.unpack local M diff --git a/lib/std/set.lua b/lib/std/set.lua index c3a095d..c77f95c 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -17,11 +17,11 @@ @see std.container ]] -local base = require "std.base" +local std = require "std.base" local Container = require "std.container" {} -local ielems, pairs, type = base.ielems, base.pairs, base.objtype +local ielems, pairs, type = std.ielems, std.pairs, std.objtype local Set -- forward declaration @@ -38,7 +38,7 @@ local Set -- forward declaration -- whose values are true. -local elems = base.pairs +local elems = std.pairs local function insert (set, e) diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 2a85e15..9b24053 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -21,12 +21,12 @@ @classmod std.strbuf ]] -local base = require "std.base" +local std = require "std.base" local debug = require "std.debug" local Object = require "std.object" {} -local ielems, insert = base.ielems, base.insert +local ielems, insert = std.ielems, std.insert local M, StrBuf diff --git a/lib/std/string.lua b/lib/std/string.lua index ca0e151..8cf20b8 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -10,22 +10,22 @@ @module std.string ]] -local base = require "std.base" +local std = require "std.base" local debug = require "std.debug" local StrBuf = require "std.strbuf" {} -local copy = base.copy -local getmetamethod = base.getmetamethod -local insert, len = base.insert, base.len -local pairs = base.pairs -local render = base.render +local copy = std.copy +local getmetamethod = std.getmetamethod +local insert, len = std.insert, std.len +local pairs = std.pairs +local render = std.render local M -local _tostring = base.tostring +local _tostring = std.tostring local function __concat (s, o) return _tostring (s) .. _tostring (o) @@ -75,7 +75,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.string = base.copy (namespace.string or {}, M) + namespace.string = std.copy (namespace.string or {}, M) local string_metatable = getmetatable "" string_metatable.__concat = M.__concat @@ -298,7 +298,7 @@ M = { -- @string s any string -- @treturn string *s* with active pattern characters escaped -- @usage substr = inputstr:match (escape_pattern (literal)) - escape_pattern = X ("escape_pattern (string)", base.escape_pattern), + escape_pattern = X ("escape_pattern (string)", std.escape_pattern), --- Escape a string to be used as a shell token. -- Quotes spaces, parentheses, brackets, quotes, apostrophes and @@ -436,7 +436,7 @@ M = { -- @string[opt="%s+"] sep separator pattern -- @return list of strings -- @usage words = split "a very short sentence" - split = X ("split (string, ?string)", base.split), + split = X ("split (string, ?string)", std.split), --- Do `string.find`, returning a table of captures. -- @function tfind @@ -482,19 +482,19 @@ local DEPRECATED = debug.DEPRECATED M.assert = DEPRECATED ("41", "'std.string.assert'", - "use 'std.assert' instead", base.assert) + "use 'std.assert' instead", std.assert) M.require_version = DEPRECATED ("41", "'std.string.require_version'", - "use 'std.require' instead", base.require) + "use 'std.require' instead", std.require) M.tostring = DEPRECATED ("41", "'std.string.tostring'", - "use 'std.tostring' instead", base.tostring) + "use 'std.tostring' instead", std.tostring) -return base.merge (M, string) +return std.merge (M, string) diff --git a/lib/std/table.lua b/lib/std/table.lua index 764be80..5a30e8d 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -13,14 +13,14 @@ local core = _G.table -local base = require "std.base" +local std = require "std.base" local debug = require "std.debug" local argerror = debug.argerror -local collect = base.collect -local leaves = base.leaves -local ipairs, pairs = base.ipairs, base.pairs -local len = base.len +local collect = std.collect +local leaves = std.leaves +local ipairs, pairs = std.ipairs, std.pairs +local len = std.len local M, monkeys @@ -161,7 +161,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.table = base.copy (namespace.table or {}, monkeys) + namespace.table = std.copy (namespace.table or {}, monkeys) return M end @@ -280,7 +280,7 @@ M = { -- @usage -- --> {1, "x", 2, 3, "y"} -- insert (insert ({1, 2, 3}, 2, "x"), "y") - insert = X ("insert (table, [int], any)", base.insert), + insert = X ("insert (table, [int], any)", std.insert), --- Invert a table. -- @function invert @@ -289,7 +289,7 @@ M = { -- @usage -- --> {a=1, b=2, c=3} -- invert {"a", "b", "c"} - invert = X ("invert (table)", base.invert), + invert = X ("invert (table)", std.invert), --- Make the list of keys in table. -- @function keys @@ -307,7 +307,7 @@ M = { -- @usage -- --> 42 -- maxn {"a", b="c", 99, [42]="x", "x", [5]=67} - maxn = X ("maxn (table)", base.maxn), + maxn = X ("maxn (table)", std.maxn), --- Destructively merge another table's fields into another. -- @function merge @@ -358,7 +358,7 @@ M = { -- @treturn table ordered list of keys from *t* -- @see keys -- @usage globals = keys (_G) - okeys = X ("okeys (table)", base.okeys), + okeys = X ("okeys (table)", std.okeys), --- Turn a tuple into a list. -- @function pack @@ -441,7 +441,7 @@ M = { -- @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)", base.unpack), + unpack = X ("unpack (table, ?int, ?int)", std.unpack), --- Make the list of values of a table. -- @function values @@ -455,7 +455,7 @@ M = { } -monkeys = base.copy ({}, M) -- before deprecations and core merge +monkeys = std.copy ({}, M) -- before deprecations and core merge --[[ ============= ]]-- @@ -467,21 +467,21 @@ local DEPRECATED = debug.DEPRECATED M.len = DEPRECATED ("41.3", "'std.table.len'", - "use 'std.len' instead", X ("len (table)", base.len)) + "use 'std.len' instead", X ("len (table)", std.len)) M.metamethod = DEPRECATED ("41", "'std.table.metamethod'", - "use 'std.getmetamethod' instead", base.getmetamethod) + "use 'std.getmetamethod' instead", std.getmetamethod) M.ripairs = DEPRECATED ("41", "'std.table.ripairs'", - "use 'std.ripairs' instead", base.ripairs) + "use 'std.ripairs' instead", std.ripairs) M.totable = DEPRECATED ("41", "'std.table.totable'", "use 'std.pairs' instead", function (x) - local m = base.getmetamethod (x, "__totable") + local m = std.getmetamethod (x, "__totable") if m then return m (x) elseif type (x) == "table" then @@ -497,7 +497,7 @@ M.totable = DEPRECATED ("41", "'std.table.totable'", -return base.merge (M, table) +return std.merge (M, table) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 3a6f821..d7deefc 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -17,15 +17,15 @@ @see std.container ]] -local base = require "std.base" +local std = require "std.base" local operator = require "std.operator" local Container = require "std.container" {} local ielems, ipairs, leaves, pairs, objtype = - base.ielems, base.ipairs, base.leaves, base.pairs, base.objtype -local last, len = base.last, base.len -local reduce = base.reduce + std.ielems, std.ipairs, std.leaves, std.pairs, std.objtype +local last, len = std.last, std.len +local reduce = std.reduce local Tree -- forward declaration From c27ee5598abe95b0ad02ef0b309187e7bba04ea7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 9 Jun 2015 13:53:27 -0500 Subject: [PATCH 561/703] refactor: rename base.objtype to std.type internally. * specs/spec_helper.lua (objtype): Correct a reference to base.lua:objtype in a comment. * specs/object_spec.yaml: Show the difference between the object module table, and the root Object more clearly. * lib/std/base.lua (objtype): Rename from this... (type): ...to this. * lib/std.lua.in, lib/std/container.lua, lib/std/debug.lua, lib/std/object.lua, lib/std/operator.lua, lib/std/set.lua, lib/std/tree.lua, lib/std/tuple.lua: Adjust all callers. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 2 +- lib/std/base.lua | 11 +++-- lib/std/container.lua | 8 ++-- lib/std/debug.lua | 6 +-- lib/std/object.lua | 10 ++--- lib/std/operator.lua | 6 +-- lib/std/set.lua | 2 +- lib/std/tree.lua | 40 +++++++++--------- lib/std/tuple.lua | 4 +- specs/object_spec.yaml | 92 +++++++++++++++++++++--------------------- specs/spec_helper.lua | 2 +- 11 files changed, 91 insertions(+), 92 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 38500e7..e12117b 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -280,7 +280,7 @@ M = { -- @param x anything -- @treturn string type of *x* -- @see std.object.type - type = X ("type (?any)", std.objtype), + type = X ("type (?any)", std.type), version = "General Lua libraries / @VERSION@", } diff --git a/lib/std/base.lua b/lib/std/base.lua index ee6b60b..1a96d0a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -304,11 +304,6 @@ local function collect (ifn, ...) end -local function objtype (o) - return (getmetatable (o) or {})._type or io.type (o) or type (o) -end - - local function reduce (fn, d, ifn, ...) local argt = {...} if not callable (ifn) then @@ -466,6 +461,11 @@ return { require = require, tostring = tostring, + type = function (x) + return (getmetatable (x) or {})._type or io.type (x) or type (x) + end, + + -- debug.lua -- argerror = argerror, @@ -482,7 +482,6 @@ return { compare = compare, -- object.lua -- - objtype = objtype, -- package.lua -- dirsep = dirsep, diff --git a/lib/std/container.lua b/lib/std/container.lua index e3749f6..b8fa300 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -38,7 +38,7 @@ local debug = require "std.debug" local ipairs, pairs, okeys = std.ipairs, std.pairs, std.okeys local insert, len = std.insert, std.len -local okeys, objtype, tostring = std.okeys, std.objtype, std.tostring +local okeys, stdtype, tostring = std.okeys, std.type, std.tostring local argcheck = debug.argcheck @@ -232,7 +232,7 @@ end function M.__tostring (self) local n, k_ = 1, nil - local buf = { objtype (self), " {" } -- pre-buffer object open + local buf = { stdtype (self), " {" } -- pre-buffer object open for _, k in ipairs (okeys (self)) do -- for ordered public members local v = self[k] @@ -296,8 +296,8 @@ return setmetatable ({ -- it has to be done manually. mapfields = modulefunction (M.mapfields), - prototype = modulefunction (objtype), - type = modulefunction (objtype), + prototype = modulefunction (stdtype), + type = modulefunction (stdtype), }, { _type = "Container", diff --git a/lib/std/debug.lua b/lib/std/debug.lua index b1bad1e..7822ec3 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -34,7 +34,7 @@ local std = require "std.base" local _DEBUG = debug_init._DEBUG local argerror, raise = std.argerror, std.raise -local objtype, unpack = std.objtype, std.unpack +local stdtype, unpack = std.type, std.unpack local copy, split, tostring = std.copy, std.split, std.tostring local insert, last, len, maxn = std.insert, std.last, std.len, std.maxn local ipairs, pairs = std.ipairs, std.pairs @@ -253,7 +253,7 @@ end local function extramsg_mismatch (expectedtypes, actual, index) - local actualtype = objtype (actual) + local actualtype = stdtype (actual) -- Tidy up actual type for display. if actualtype == "nil" then @@ -364,7 +364,7 @@ if _DEBUG.argcheck then end end - actualtype = objtype (actual) + actualtype = stdtype (actual) if check == actualtype then return true elseif check == "list" or check == "#list" then diff --git a/lib/std/object.lua b/lib/std/object.lua index e6960a9..070516b 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -30,12 +30,12 @@ -- Container is derived from it. Confused? ;-) -local std = require "std.base" local container = require "std.container" local debug = require "std.debug" +local std = require "std.base" local Container = container {} -local getmetamethod, objtype = std.getmetamethod, std.objtype +local getmetamethod = std.getmetamethod local DEPRECATED = debug.DEPRECATED @@ -122,7 +122,7 @@ return Container { -- -- It's conventional to organise similar objects according to a -- string valued *\_type* field, which can then be queried using this - -- function. + -- method. -- -- Additionally, this function returns the results of @{io.type} for -- file objects, or @{type} otherwise. @@ -152,7 +152,7 @@ return Container { -- assert (objtype (h) == io.type (h)) -- -- assert (type {} == type {}) - type = objtype, + type = std.type, --- Return *obj* with references to the fields of *src* merged in. @@ -189,7 +189,7 @@ return Container { -- Backwards compatibility: - prototype = DEPRECATED ("41.3", "'std.object.prototype'", objtype), + prototype = DEPRECATED ("41.3", "'std.object.prototype'", std.type), }, diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 7d418c4..0740c28 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -6,8 +6,8 @@ local std = require "std.base" -local pairs, objtype, tostring = - std.pairs, std.objtype, std.tostring +local pairs, stdtype, tostring = + std.pairs, std.type, std.tostring local function eqv (a, b) @@ -18,7 +18,7 @@ local function eqv (a, b) -- Unless we have two tables, what we have cannot be equivalent here. if type (a) ~= "table" or type (b) ~= "table" then return false end - local type_a, type_b = objtype (a), objtype (b) + local type_a, type_b = stdtype (a), stdtype (b) if type_a ~= type_b then return false end local keyeqv = {} -- keys requiring recursive equivalence test diff --git a/lib/std/set.lua b/lib/std/set.lua index c77f95c..cb0b292 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -21,7 +21,7 @@ local std = require "std.base" local Container = require "std.container" {} -local ielems, pairs, type = std.ielems, std.pairs, std.objtype +local ielems, pairs, type = std.ielems, std.pairs, std.type local Set -- forward declaration diff --git a/lib/std/tree.lua b/lib/std/tree.lua index d7deefc..131c1f3 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -22,8 +22,8 @@ local operator = require "std.operator" local Container = require "std.container" {} -local ielems, ipairs, leaves, pairs, objtype = - std.ielems, std.ipairs, std.leaves, std.pairs, std.objtype +local ielems, ipairs, leaves, pairs, stdtype = + std.ielems, std.ipairs, std.leaves, std.pairs, std.type local last, len = std.last, std.len local reduce = std.reduce @@ -138,12 +138,12 @@ Tree = Container { -- @usage -- del_other_window = keymap[{"C-x", "4", KEY_DELETE}] __index = function (tr, i) - if objtype (i) == "table" then - return reduce (operator.get, tr, ielems, i) - else - return rawget (tr, i) - end - end, + if stdtype (i) == "table" then + return reduce (operator.get, tr, ielems, i) + else + return rawget (tr, i) + end + end, --- Deep insertion. -- @static @@ -154,18 +154,18 @@ Tree = Container { -- @usage -- function bindkey (keylist, fn) keymap[keylist] = fn end __newindex = function (tr, i, v) - if objtype (i) == "table" then - for n = 1, len (i) - 1 do - if objtype (tr[i[n]]) ~= "Tree" then - rawset (tr, i[n], Tree {}) - end - tr = tr[i[n]] - end - rawset (tr, last (i), v) - else - rawset (tr, i, v) - end - end, + if stdtype (i) == "table" then + for n = 1, len (i) - 1 do + if stdtype (tr[i[n]]) ~= "Tree" then + rawset (tr, i[n], Tree {}) + end + tr = tr[i[n]] + end + rawset (tr, last (i), v) + else + rawset (tr, i, v) + end + end, _functions = { --- Make a deep copy of a tree, including any metatables. diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 03b47cf..4209897 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -22,7 +22,7 @@ ]] local Container = require "std.container" {} -local objtype = require "std.base".objtype +local stdtype = require "std.base".type -- Stringify tuple values, as a memoization key. @@ -133,6 +133,6 @@ return Container { -- -- 'Tuple ("nil", nil, false)' -- print (Tuple ("nil", nil, false)) __tostring = function (self) - return ("%s (%s)"):format (objtype (self), argstr (self)) + return ("%s (%s)"):format (stdtype (self), argstr (self)) end, } diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 57f6934..df9bf22 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -1,5 +1,6 @@ before: - Object = require "std.object" + object = require "std.object" + Object = object {} obj = Object {"foo", "bar", baz="quux"} function copy (t) @@ -18,10 +19,10 @@ specify std.object: - describe construction: - context from Object clone method: - it constructs a new object: - obj = Object:clone {} - expect (obj).not_to_be (Object) - expect (type (obj)).to_be "table" - expect (objtype (obj)).to_be "Object" + o = Object:clone {} + expect (o).not_to_be (Object) + expect (type (o)).to_be "table" + expect (objtype (o)).to_be "Object" - it reuses the Object metatable: o = obj:clone {"o"} p = o:clone {"p"} @@ -38,46 +39,46 @@ specify std.object: expect (Object:clone {foo="bar", _baz="quux"}). to_equal (Object:clone {foo="bar"}) - it puts '_' prefixed fields in a new metatable: - obj = Object:clone {foo="bar", _baz="quux"} - expect (getmetatable (obj)).not_to_be (getmetatable (Object)) - expect (getmetatable (obj)._baz).to_be "quux" + o = Object:clone {foo="bar", _baz="quux"} + expect (getmetatable (o)).not_to_be (getmetatable (Object)) + expect (getmetatable (o)._baz).to_be "quux" - describe prototype: - before: o = Object {} - prototype = Object.prototype + fn = object.prototype - it writes a deprecation warning: setdebug { deprecate = "nil" } - expect (capture (prototype, {o})).to_contain_error "was deprecated" + expect (capture (fn, {o})).to_contain_error "was deprecated" setdebug { deprecate = false } - expect (capture (prototype, {o})).not_to_contain_error "was deprecated" + expect (capture (fn, {o})).not_to_contain_error "was deprecated" - context when called from the object module: - it reports the prototype stored in the object's metatable: - expect (prototype (o)).to_be "Object" + expect (fn (o)).to_be "Object" - it reports the type of a cloned object: - expect (prototype (o {})).to_be "Object" + expect (fn (o {})).to_be "Object" - it reports the type of a derived object: Example = Object {_type = "Example"} - expect (prototype (Example)).to_be "Example" + expect (fn (Example)).to_be "Example" - it reports the type of a cloned derived object: Portal = Object {_type = "Demon"} p = Portal {} - expect (prototype (p)).to_be "Demon" - expect (prototype (p {})).to_be "Demon" + expect (fn (p)).to_be "Demon" + expect (fn (p {})).to_be "Demon" - it recognizes a file object: h = io.open (os.tmpname ()) - expect (prototype (h)).to_be "file" + expect (fn (h)).to_be "file" h:close () - expect (prototype (h)).to_be "closed file" + expect (fn (h)).to_be "closed file" - it recognizes a primitive object: - expect (prototype (nil)).to_be "nil" - expect (prototype (false)).to_be "boolean" - expect (prototype (0.0)).to_be "number" - expect (prototype "0.0").to_be "string" - expect (prototype (function () end)).to_be "function" - expect (prototype {}).to_be "table" + expect (fn (nil)).to_be "nil" + expect (fn (false)).to_be "boolean" + expect (fn (0.0)).to_be "number" + expect (fn "0.0").to_be "string" + expect (fn (function () end)).to_be "function" + expect (fn {}).to_be "table" - context when called as an object method: - it reports the type stored in the object's metatable: expect (o:prototype ()).to_be "Object" @@ -96,33 +97,33 @@ specify std.object: - describe type: - before: o = Object {} - objtype = Object.type + fn = object.type - context when called from the object module: - it reports the type stored in the object's metatable: - expect (objtype (o)).to_be "Object" + expect (fn (o)).to_be "Object" - it reports the type of a cloned object: - expect (objtype (o {})).to_be "Object" + expect (fn (o {})).to_be "Object" - it reports the type of a derived object: Example = Object {_type = "Example"} - expect (objtype (Example)).to_be "Example" + expect (fn (Example)).to_be "Example" - it reports the type of a cloned derived object: Portal = Object {_type = "Demon"} p = Portal {} - expect (objtype (p)).to_be "Demon" - expect (objtype (p {})).to_be "Demon" + expect (fn (p)).to_be "Demon" + expect (fn (p {})).to_be "Demon" - it recognizes a file object: h = io.open (os.tmpname ()) - expect (objtype (h)).to_be "file" + expect (fn (h)).to_be "file" h:close () - expect (objtype (h)).to_be "closed file" + expect (fn (h)).to_be "closed file" - it recognizes a primitive object: - expect (objtype (nil)).to_be "nil" - expect (objtype (false)).to_be "boolean" - expect (objtype (0.0)).to_be "number" - expect (objtype "0.0").to_be "string" - expect (objtype (function () end)).to_be "function" - expect (objtype {}).to_be "table" + expect (fn (nil)).to_be "nil" + expect (fn (false)).to_be "boolean" + expect (fn (0.0)).to_be "number" + expect (fn "0.0").to_be "string" + expect (fn (function () end)).to_be "function" + expect (fn {}).to_be "table" - context when called as an object method: - it reports the type stored in the object's metatable: expect (o:type ()).to_be "Object" @@ -292,7 +293,6 @@ specify std.object: end, }, } - # :prototype is a method defined by the root object - it inherits prototype object methods: expect (bag:type ()).to_be "bag" - it propagates prototype methods to derived instances: @@ -340,22 +340,22 @@ specify std.object: - describe __tostring: - before: - obj = Object {_type = "Derived", "one", "two", "three"} + o = Object {_type = "Derived", "one", "two", "three"} - it returns a string: - expect (type (tostring (obj))).to_be "string" + expect (type (tostring (o))).to_be "string" - it contains the type: expect (tostring (Object {})).to_contain "Object" - expect (tostring (obj)).to_contain (objtype (obj)) + expect (tostring (o)).to_contain (objtype (o)) - it contains the ordered array part elements: - expect (tostring (obj)).to_contain "one, two, three" + expect (tostring (o)).to_contain "one, two, three" - it contains the ordered dictionary part elements: expect (tostring (Object {one = true, two = true, three = true})). to_contain "one=true, three=true, two=true" - expect (tostring (obj {one = true, two = true, three = true})). + expect (tostring (o {one = true, two = true, three = true})). to_contain "one=true, three=true, two=true" - it contains a ';' separator only when object has array and dictionary parts: - expect (tostring (obj)).not_to_contain ";" + expect (tostring (o)).not_to_contain ";" expect (tostring (Object {one = true, two = true, three = true})). not_to_contain ";" - expect (tostring (obj {one = true, two = true, three = true})). + expect (tostring (o {one = true, two = true, three = true})). to_contain ";" diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 5b420bc..3e8d17e 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -83,7 +83,7 @@ function init (M, mname, fname) end --- A copy of base.lua:objtype, so that an unloadable base.lua doesn't +-- 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) From 96cd411da5c9a7e123b9b2114770be84a327e376 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 10 Jun 2015 11:12:42 -0500 Subject: [PATCH 562/703] refactor: disambiguate modules and their prototype objects. Close #86 * NEWS.md (New features, Incompatible changes): Update. * lib/std/container.lua (ModuleFunction, modulefunction): Remove. No longer required with disambiguated modules and prototypes. (mapfields): Move from here... * lib/std/base.lua (mapfields): ...to here, and simplify. * lib/std/object.lua (mapfields): Adjust accordingly. * lib/std/base.lua (Module): Helper function for propagating module calls to module.prototype. * lib/std/list.lua, lib/std/object.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/tree.lua, lib/std/tuple.lua (prototype, Module): Use these to disambiguate the Module and object prototypes. * lib/std/functional.lua, lib/std/io.lua, lib/std/package.lua, lib/std/string.lua: Simplify and adjust accordingly. * specs/container_spec.yaml, specs/debug_spec.yaml, specs/list_spec.yaml, specs/object_spec.yaml, specs/set_spec.yaml, specs/strbuf_spec.yaml, specs/tree_spec.yaml, specs/tuple_spec.yaml: Adjust accordingly. Signed-off-by: Gary V. Vaughan --- NEWS.md | 52 +++++- lib/std/base.lua | 44 ++++- lib/std/container.lua | 176 ++++-------------- lib/std/functional.lua | 7 +- lib/std/io.lua | 2 +- lib/std/list.lua | 381 +++++++++++++++++--------------------- lib/std/object.lua | 91 ++++----- lib/std/optparse.lua | 2 +- lib/std/package.lua | 6 +- lib/std/set.lua | 273 ++++++++++++++------------- lib/std/strbuf.lua | 12 +- lib/std/string.lua | 4 +- lib/std/tree.lua | 223 +++++++++++----------- lib/std/tuple.lua | 13 +- specs/container_spec.yaml | 16 +- specs/debug_spec.yaml | 2 +- specs/list_spec.yaml | 38 +--- specs/object_spec.yaml | 10 +- specs/set_spec.yaml | 88 +++++---- specs/strbuf_spec.yaml | 23 ++- specs/tree_spec.yaml | 25 +-- specs/tuple_spec.yaml | 2 +- 22 files changed, 725 insertions(+), 765 deletions(-) diff --git a/NEWS.md b/NEWS.md index 521e97e..d0e6f0c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,15 +11,41 @@ local prototype = require "std.object".type ``` - So we renamed it to `std.object.prototype`, and deprecated the `type` - method; but that was a mistake, because core Lua provides `type`, - and `io.type` (and in recent releases, `math.type`. For orthogonality, - we're going back to using `std.object.type`, which just makes more - sense. Sorry! + So we renamed it to `std.object.prototype` to avoid a name clash with + the `type` symbol, and subsequently deprecated the earlier equivalent + `type` method; but that was a mistake, because core Lua provides `type`, + and and `io.type` (and in recent releases, `math.type`). Now, for + orthogonality with core Lua, we're going back to using `std.object.type`, + because that just makes more sense. Sorry! - Similarly, for orthogonality with core Lua `type`, we also export the `std.object.type` function as `std.type`. + - Objects and Modules are no longer conflated - what you get back from + a `require "std.something"` is now ALWAYS a module: + + ```lua + local object = require "std.object" + assert (object.type (object) == "Module") + ``` + + And the modules that provide objects have a new `prototype` field + that contains the prototye for that kind of object: + + ```lua + local Object = object.prototype + assert (object.type (Object) == "Object") + ``` + + For backwards compatibility, if you call the module with a + constructor table, the previous recommended way to disambiguate + between a module and the object it prototyped, that table is passed + through to that module's object prototype. + + - Now that we have proper separation of concerns between module tables + and object prototype tables, the central `std.object.mapfields` + instantiation function is much cleaner and faster. + - New `std.tuple` object, for managing interned immutable nil-preserving tuples: @@ -65,6 +91,22 @@ - Deprecated function `table.clone_rename` has been removed. + - Now that the `prototype` field is used to reference a module's + object prototype, `std.object.prototype` no longer return the object + type of an argument. Use either `std.type` or the type function from + an instantiated object: + + ```lua + local stdtype = require "std.type" + local Object = require "std.object".prototype + local objtype = Object.type + ``` + + - Objects no longer honor mangling and stripping `_functions` tables + from objects during instantiation, instead move you actual object + into the module `prototype` field, and add the module functions to + the parent table returned whn the module is required. + - `functional.lambda` no longer returns a bare function, but a functable that can be called and stringified. diff --git a/lib/std/base.lua b/lib/std/base.lua index 1a96d0a..5e10eea 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -262,12 +262,53 @@ local function leaves (it, tr) end +local function mapfields (obj, src, map) + local mt = getmetatable (obj) or {} + + -- Map key pairs. + -- Copy all pairs when `map == nil`, but discard unmapped src keys + -- when map is provided (i.e. if `map == {}`, copy nothing). + if map == nil or next (map) then + map = map or {} + local k, v = next (src) + while k do + local key, dst = map[k] or k, obj + local kind = type (key) + if kind == "string" and key:sub (1, 1) == "_" then + mt[key] = v + elseif next (map) and kind == "number" and len (dst) + 1 < key then + -- When map is given, but has fewer entries than src, stop copying + -- fields when map is exhausted. + break + else + dst[key] = v + end + k, v = next (src, k) + end + end + + -- Only set non-empty metatable. + if next (mt) then + setmetatable (obj, mt) + end + return obj +end + + local function merge (dest, src) for k, v in pairs (src) do dest[k] = dest[k] or v end return dest end +local function Module (t) + return setmetatable (t, { + _type = "Module", + __call = function (self, ...) return self.prototype (...) end, + }) +end + + local function npairs (t) local m = getmetamethod (t, "__len") local i, n = 0, m and m(t) or maxn (t) @@ -482,6 +523,8 @@ return { compare = compare, -- object.lua -- + Module = Module, + mapfields = mapfields, -- package.lua -- dirsep = dirsep, @@ -502,5 +545,4 @@ return { -- tree.lua -- leaves = leaves, - } diff --git a/lib/std/container.lua b/lib/std/container.lua index b8fa300..45cc2fb 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -36,10 +36,7 @@ local _DEBUG = require "std.debug_init"._DEBUG local std = require "std.base" local debug = require "std.debug" -local ipairs, pairs, okeys = std.ipairs, std.pairs, std.okeys -local insert, len = std.insert, std.len -local okeys, stdtype, tostring = std.okeys, std.type, std.tostring -local argcheck = debug.argcheck +local ipairs, okeys, tostring = std.ipairs, std.okeys, std.tostring @@ -77,103 +74,28 @@ local function instantiate (proto, t) end -local ModuleFunction = { - __tostring = function (self) return tostring (self.call) end, - __call = function (self, ...) return self.call (...) end, -} - - ---- Mark a function not to be copied into clones. --- --- It responds to `type` with `table`, but otherwise behaves like a --- regular function. Marking uncopied module functions in-situ like this --- (as opposed to doing book keeping in the metatable) means that we --- don't have to create a new metatable with the book keeping removed for --- cloned objects, we can just share our existing metatable directly. --- @func fn a function --- @treturn functable a callable functable for `fn` -local function modulefunction (fn) - if getmetatable (fn) == ModuleFunction then - -- Don't double wrap! - return fn - else - return setmetatable ({_type = "modulefunction", call = fn}, ModuleFunction) - end -end - - - --[[ ================= ]]-- --[[ Container Object. ]]-- --[[ ================= ]]-- -local function mapfields (obj, src, map) - local mt = getmetatable (obj) or {} - - -- Map key pairs. - -- Copy all pairs when `map == nil`, but discard unmapped src keys - -- when map is provided (i.e. if `map == {}`, copy nothing). - if map == nil or next (map) then - map = map or {} - local k, v = next (src) - while k do - local key, dst = map[k] or k, obj - local kind = type (key) - if kind == "string" and key:sub (1, 1) == "_" then - mt[key] = v - elseif next (map) and kind == "number" and len (dst) + 1 < key then - -- When map is given, but has fewer entries than src, stop copying - -- fields when map is exhausted. - break - else - dst[key] = v - end - k, v = next (src, k) - end - end - - -- Quicker to remove this after copying fields than test for it - -- it on every iteration above. - mt._functions = nil - - -- Inject module functions. - local t = src._functions or {} - local k, v = next (t) - while (k) do - obj[k] = modulefunction (v) - k, v = next (t, k) - end - - -- Only set non-empty metatable. - if next (mt) then - setmetatable (obj, mt) - end - return obj -end - - local function __call (self, ...) local mt = getmetatable (self) local obj_mt = mt local obj = {} -- This is the slowest part of cloning for any objects that have - -- a lot of fields to test and copy. If you need to clone a lot of - -- objects from a prototype with several module functions, it's much - -- faster to clone objects from each other than the prototype! + -- a lot of fields to test and copy. local k, v = next (self) while (k) do - if type (v) ~= "table" or v._type ~= "modulefunction" then - obj[k] = v - end + obj[k] = v k, v = next (self, k) end if type (mt._init) == "function" then obj = mt._init (obj, ...) else - obj = (self.mapfields or mapfields) (obj, (...), mt._init) + obj = (self.mapfields or std.mapfields) (obj, (...), mt._init) end -- If a metatable was set, then merge our fields and use it. @@ -192,47 +114,9 @@ local function __call (self, ...) end -local function X (decl, fn) - return debug.argscheck ("std.container." .. decl, fn) -end - -local M = { - mapfields = X ("mapfields (table, table|object, ?table)", mapfields), -} - - -if _DEBUG.argcheck then - - local argerror, extramsg_toomany = debug.argerror, debug.extramsg_toomany - - M.__call = function (self, ...) - local mt = getmetatable (self) - - -- A function initialised object can be passed arguments of any - -- type, so only argcheck non-function initialised objects. - if type (mt._init) ~= "function" then - local name, n = mt._type, select ("#", ...) - -- Don't count `self` as an argument for error messages, because - -- it just refers back to the object being called: `Container {"x"}. - argcheck (name, 1, "table", (...)) - if n > 1 then - argerror (name, 2, extramsg_toomany ("argument", 1, n), 2) - end - end - - return __call (self, ...) - end - -else - - M.__call = __call - -end - - -function M.__tostring (self) +local function __tostring (self) local n, k_ = 1, nil - local buf = { stdtype (self), " {" } -- pre-buffer object open + local buf = { getmetatable (self)._type, " {" } for _, k in ipairs (okeys (self)) do -- for ordered public members local v = self[k] @@ -289,19 +173,41 @@ end -- --> 2 -- print (Graph.nodes (g)) -return setmetatable ({ +local Container = { + _type = "Container", + + __call = __call, + __tostring = __tostring, +} - -- Normally, these are set and wrapped automatically during cloning. - -- But, we have to bootstrap the first object, so in this one instance - -- it has to be done manually. - mapfields = modulefunction (M.mapfields), - prototype = modulefunction (stdtype), - type = modulefunction (stdtype), -}, { - _type = "Container", +if _DEBUG.argcheck then + local argcheck, argerror, extramsg_toomany = + debug.argcheck, debug.argerror, debug.extramsg_toomany - __call = M.__call, - __tostring = M.__tostring, - __pairs = M.__pairs, -}) + Container.__call = function (self, ...) + local mt = getmetatable (self) + + -- A function initialised object can be passed arguments of any + -- type, so only argcheck non-function initialised objects. + if type (mt._init) ~= "function" then + local name, n = mt._type, select ("#", ...) + -- Don't count `self` as an argument for error messages, because + -- it just refers back to the object being called: `Container {"x"}. + argcheck (name, 1, "table", (...)) + if n > 1 then + argerror (name, 2, extramsg_toomany ("argument", 1, n), 2) + end + end + + return __call (self, ...) + end +end + + +return std.Module { + prototype = setmetatable ({}, Container), + + mapfields = debug.argscheck ( + "std.container.mapfields (table, table|object, ?table)", std.mapfields), +} diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 916dab3..588e01f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,8 +8,7 @@ ]] -local std = require "std.base" -local debug = require "std.debug" +local std = require "std.base" local ielems, ipairs, ireverse, npairs, pairs = std.ielems, std.ipairs, std.ireverse, std.npairs, std.pairs @@ -296,7 +295,7 @@ end local function X (decl, fn) - return debug.argscheck ("std.functional." .. decl, fn) + return require "std.debug".argscheck ("std.functional." .. decl, fn) end local M = { @@ -580,7 +579,7 @@ local M = { --[[ ============= ]]-- -local DEPRECATED = debug.DEPRECATED +local DEPRECATED = require "std.debug".DEPRECATED M.eval = DEPRECATED ("41", "'std.functional.eval'", diff --git a/lib/std/io.lua b/lib/std/io.lua index eaf37f2..017dbf0 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -159,7 +159,7 @@ M = { -- @see catdir -- @see splitdir -- @usage filepath = catfile ("relative", "path", "filename") - catfile = X ("catfile (string...)", std.catfile), + catfile = X ("catfile (string...)", catfile), --- Die with error. -- This function uses the same rules to build a message prefix diff --git a/lib/std/list.lua b/lib/std/list.lua index 0f71c12..896386f 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -13,9 +13,8 @@ local std = require "std.base" -local debug = require "std.debug" -local Object = require "std.object" {} +local Object = require "std.object".prototype local ipairs, pairs = std.ipairs, std.pairs local len = std.len @@ -71,109 +70,16 @@ end ---[[ ================= ]]-- ---[[ Public Interface. ]]-- ---[[ ================= ]]-- - - -local function X (decl, fn) - return debug.argscheck ("std.list." .. decl, fn) -end - - -M = { - --- Append an item to a list. - -- @static - -- @function append - -- @tparam List l a list - -- @param x item - -- @treturn List new list with *x* appended - -- @usage - -- longer = append (short, "last") - append = X ("append (List, any)", append), - - --- Compare two lists element-by-element, from left-to-right. - -- @static - -- @function compare - -- @tparam List l a list - -- @tparam List|table m another list, or table - -- @return -1 if *l* is less than *m*, 0 if they are the same, and 1 - -- if *l* is greater than *m* - -- @usage - -- if a_list:compare (another_list) == 0 then print "same" end - compare = X ("compare (List, List|table)", compare), - - --- Concatenate the elements from any number of lists. - -- @static - -- @function concat - -- @tparam List l a list - -- @param ... tuple of lists - -- @treturn List new list with elements from arguments - -- @usage - -- --> {1, 2, 3, {4, 5}, 6, 7} - -- list.concat ({1, 2, 3}, {{4, 5}, 6, 7}) - concat = X ("concat (List, List|table...)", concat), - - --- Prepend an item to a list. - -- @static - -- @function cons - -- @tparam List l a list - -- @param x item - -- @treturn List new list with *x* followed by elements of *l* - -- @usage - -- --> {"x", 1, 2, 3} - -- list.cons ({1, 2, 3}, "x") - cons = X ("cons (List, any)", function (l, x) return List {x, unpack (l)} end), - - --- Repeat a list. - -- @static - -- @function rep - -- @tparam List l a list - -- @int n number of times to repeat - -- @treturn List *n* copies of *l* appended together - -- @usage - -- --> {1, 2, 3, 1, 2, 3, 1, 2, 3} - -- list.rep ({1, 2, 3}, 3) - rep = X ("rep (List, int)", rep), - - --- Return a sub-range of a list. - -- (The equivalent of @{string.sub} on strings; negative list indices - -- count from the end of the list.) - -- @static - -- @function sub - -- @tparam List l a list - -- @int[opt=1] from start of range - -- @int[opt=#l] to end of range - -- @treturn List new list containing elements between *from* and *to* - -- inclusive - -- @usage - -- --> {3, 4, 5} - -- list.sub ({1, 2, 3, 4, 5, 6}, 3, 5) - sub = X ("sub (List, ?int, ?int)", sub), - - --- Return a list with its first element removed. - -- @static - -- @function tail - -- @tparam List l a list - -- @treturn List new list with all but the first element of *l* - -- @usage - -- --> {3, {4, 5}, 6, 7} - -- list.tail {{1, 2}, 3, {4, 5}, 6, 7} - tail = X ("tail (List)", function (l) return sub (l, 2) end), -} - - - --[[ ============= ]]-- --[[ Deprecations. ]]-- --[[ ============= ]]-- --- This entire section can be deleted in due course, with just one --- additional small correction noted in FIXME comments in the List --- object constructor at the end of this file. +-- This entire section can be deleted any time after 2016.01.03, along +-- with all references to these functions further down. -local DEPRECATED = debug.DEPRECATED + +local DEPRECATED = require "std.debug".DEPRECATED local function depair (ls) @@ -342,125 +248,141 @@ local function zip_with (ls, fn) end -local m = { - append = M.append, - compare = M.compare, - concat = M.concat, - cons = M.cons, - rep = M.rep, - sub = M.sub, - tail = M.tail, -} - - -M.depair = DEPRECATED ("41", "'std.list.depair'", depair) - -M.enpair = DEPRECATED ("41", "'std.list.enpair'", enpair) -m.enpair = DEPRECATED ("41", "'std.list:enpair'", enpair) - -M.elems = DEPRECATED ("41", "'std.list.elems'", - "use 'std.ielems' instead", std.ielems) -m.elems = DEPRECATED ("41", "'std.list:elems'", - "use 'std.ielems' instead", std.ielems) - -M.filter = DEPRECATED ("41", "'std.list.filter'", - "use 'std.functional.filter' instead", filter) -m.filter = DEPRECATED ("41", "'std.list:filter'", - "use 'std.functional.filter' instead", - function (self, p) return filter (p, self) end) - - -M.flatten = DEPRECATED ("41", "'std.list.flatten'", - "use 'std.functional.flatten' instead", flatten) -m.flatten = DEPRECATED ("41", "'std.list:flatten'", - "use 'std.functional.flatten' instead", flatten) - - -M.foldl = DEPRECATED ("41", "'std.list.foldl'", - "use 'std.functional.foldl' instead", foldl) -m.foldl = DEPRECATED ("41", "'std.list:foldl'", - "use 'std.functional.foldl' instead", - function (self, fn, e) - if e ~= nil then return foldl (fn, e, self) end - return foldl (fn, self) - end) - -M.foldr = DEPRECATED ("41", "'std.list.foldr'", - "use 'std.functional.foldr' instead", foldr) -m.foldr = DEPRECATED ("41", "'std.list:foldr'", - "use 'std.functional.foldr' instead", - function (self, fn, e) - if e ~= nil then return foldr (fn, e, self) end - return foldr (fn, self) - end) - -M.index_key = DEPRECATED ("41", "'std.list.index_key'", - "compose 'std.functional.filter' and 'std.table.invert' instead", - index_key) -m.index_key = DEPRECATED ("41", "'std.list:index_key'", - function (self, fn) return index_key (fn, self) end) - - -M.index_value = DEPRECATED ("41", "'std.list.index_value'", - "compose 'std.functional.filter' and 'std.table.invert' instead", - index_value) -m.index_value = DEPRECATED ("41", "'std.list:index_value'", - function (self, fn) return index_value (fn, self) end) - - -M.map = DEPRECATED ("41", "'std.list.map'", - "use 'std.functional.map' instead", map) -m.map = DEPRECATED ("41", "'std.list:map'", - "use 'std.functional.map' instead", - function (self, fn) return map (fn, self) end) - - - -M.map_with = DEPRECATED ("41", "'std.list.map_with'", - "use 'std.functional.map_with' instead", map_with) - -M.project = DEPRECATED ("41", "'std.list.project'", - "use 'std.table.project' instead", project) -m.project = DEPRECATED ("41", "'std.list:project'", - "use 'std.table.project' instead", - function (self, x) return project (x, self) end) - -M.relems = DEPRECATED ("41", "'std.list.relems'", - "compose 'std.ielems' and 'std.ireverse' instead", relems) -m.relems = DEPRECATED ("41", "'std.list:relems'", relems) - -M.reverse = DEPRECATED ("41", "'std.list.reverse'", - "compose 'std.list' and 'std.ireverse' instead", reverse) -m.reverse = DEPRECATED ("41", "'std.list:reverse'", - "compose 'std.list' and 'std.ireverse' instead", reverse) - -M.shape = DEPRECATED ("41", "'std.list.shape'", - "use 'std.table.shape' instead", shape) -m.shape = DEPRECATED ("41", "'std.list:shape'", - "use 'std.table.shape' instead", - function (t, l) return shape (l, t) end) - -M.transpose = DEPRECATED ("41", "'std.list.transpose'", - "use 'std.functional.zip' instead", transpose) - -M.zip_with = DEPRECATED ("41", "'std.list.zip_with'", - "use 'std.functional.zip_with' instead", zip_with) - - --[[ ================== ]]-- --[[ Type Declarations. ]]-- --[[ ================== ]]-- +local function X (decl, fn) + return require "std.debug".argscheck ("std.list." .. decl, fn) +end + + --- An Object derived List. -- @object List List = Object { -- Derived object type. _type = "List", - _functions = M, -- FIXME: remove this when DEPRECATIONS have gone - __index = m, -- FIXME: `__index = M` when DEPRECATIONS have gone + + __index = { + --- Append an item to a list. + -- @static + -- @function append + -- @tparam List l a list + -- @param x item + -- @treturn List new list with *x* appended + -- @usage + -- longer = append (short, "last") + append = X ("append (List, any)", append), + + --- Compare two lists element-by-element, from left-to-right. + -- @static + -- @function compare + -- @tparam List l a list + -- @tparam List|table m another list, or table + -- @return -1 if *l* is less than *m*, 0 if they are the same, and 1 + -- if *l* is greater than *m* + -- @usage + -- if a_list:compare (another_list) == 0 then print "same" end + compare = X ("compare (List, List|table)", compare), + + --- Concatenate the elements from any number of lists. + -- @static + -- @function concat + -- @tparam List l a list + -- @param ... tuple of lists + -- @treturn List new list with elements from arguments + -- @usage + -- --> {1, 2, 3, {4, 5}, 6, 7} + -- list.concat ({1, 2, 3}, {{4, 5}, 6, 7}) + concat = X ("concat (List, List|table...)", concat), + + --- Prepend an item to a list. + -- @static + -- @function cons + -- @tparam List l a list + -- @param x item + -- @treturn List new list with *x* followed by elements of *l* + -- @usage + -- --> {"x", 1, 2, 3} + -- list.cons ({1, 2, 3}, "x") + cons = X ("cons (List, any)", function (l, x) return List {x, unpack (l)} end), + + --- Repeat a list. + -- @static + -- @function rep + -- @tparam List l a list + -- @int n number of times to repeat + -- @treturn List *n* copies of *l* appended together + -- @usage + -- --> {1, 2, 3, 1, 2, 3, 1, 2, 3} + -- list.rep ({1, 2, 3}, 3) + rep = X ("rep (List, int)", rep), + + --- Return a sub-range of a list. + -- (The equivalent of @{string.sub} on strings; negative list indices + -- count from the end of the list.) + -- @static + -- @function sub + -- @tparam List l a list + -- @int[opt=1] from start of range + -- @int[opt=#l] to end of range + -- @treturn List new list containing elements between *from* and *to* + -- inclusive + -- @usage + -- --> {3, 4, 5} + -- list.sub ({1, 2, 3, 4, 5, 6}, 3, 5) + sub = X ("sub (List, ?int, ?int)", sub), + + --- Return a list with its first element removed. + -- @static + -- @function tail + -- @tparam List l a list + -- @treturn List new list with all but the first element of *l* + -- @usage + -- --> {3, {4, 5}, 6, 7} + -- list.tail {{1, 2}, 3, {4, 5}, 6, 7} + tail = X ("tail (List)", function (l) return sub (l, 2) end), + + enpair = DEPRECATED ("41", "'std.list:enpair'", enpair), + elems = DEPRECATED ("41", "'std.list:elems'", + "use 'std.ielems' instead", std.ielems), + filter = DEPRECATED ("41", "'std.list:filter'", + "use 'std.functional.filter' instead", + function (self, p) return filter (p, self) end), + flatten = DEPRECATED ("41", "'std.list:flatten'", + "use 'std.functional.flatten' instead", flatten), + foldl = DEPRECATED ("41", "'std.list:foldl'", + "use 'std.functional.foldl' instead", + function (self, fn, e) + if e ~= nil then return foldl (fn, e, self) end + return foldl (fn, self) + end), + foldr = DEPRECATED ("41", "'std.list:foldr'", + "use 'std.functional.foldr' instead", + function (self, fn, e) + if e ~= nil then return foldr (fn, e, self) end + return foldr (fn, self) + end), + index_key = DEPRECATED ("41", "'std.list:index_key'", + function (self, fn) return index_key (fn, self) end), + index_value = DEPRECATED ("41", "'std.list:index_value'", + function (self, fn) return index_value (fn, self) end), + map = DEPRECATED ("41", "'std.list:map'", + "use 'std.functional.map' instead", + function (self, fn) return map (fn, self) end), + project = DEPRECATED ("41", "'std.list:project'", + "use 'std.table.project' instead", + function (self, x) return project (x, self) end), + relems = DEPRECATED ("41", "'std.list:relems'", relems), + reverse = DEPRECATED ("41", "'std.list:reverse'", + "compose 'std.list' and 'std.ireverse' instead", reverse), + shape = DEPRECATED ("41", "'std.list:shape'", + "use 'std.table.shape' instead", + function (t, l) return shape (l, t) end), + }, ------ -- Concatenate lists. @@ -504,4 +426,49 @@ List = Object { } -return List +return std.Module { + prototype = List, + + append = List.append, + compare = List.compare, + concat = List.concat, + cons = List.cons, + rep = List.rep, + sub = List.sub, + tail = List.tail, + + depair = DEPRECATED ("41", "'std.list.depair'", depair), + enpair = DEPRECATED ("41", "'std.list.enpair'", enpair), + elems = DEPRECATED ("41", "'std.list.elems'", + "use 'std.ielems' instead", std.ielems), + filter = DEPRECATED ("41", "'std.list.filter'", + "use 'std.functional.filter' instead", filter), + flatten = DEPRECATED ("41", "'std.list.flatten'", + "use 'std.functional.flatten' instead", flatten), + foldl = DEPRECATED ("41", "'std.list.foldl'", + "use 'std.functional.foldl' instead", foldl), + foldr = DEPRECATED ("41", "'std.list.foldr'", + "use 'std.functional.foldr' instead", foldr), + index_key = DEPRECATED ("41", "'std.list.index_key'", + "compose 'std.functional.filter' and 'std.table.invert' instead", + index_key), + index_value = DEPRECATED ("41", "'std.list.index_value'", + "compose 'std.functional.filter' and 'std.table.invert' instead", + index_value), + map = DEPRECATED ("41", "'std.list.map'", + "use 'std.functional.map' instead", map), + map_with = DEPRECATED ("41", "'std.list.map_with'", + "use 'std.functional.map_with' instead", map_with), + project = DEPRECATED ("41", "'std.list.project'", + "use 'std.table.project' instead", project), + relems = DEPRECATED ("41", "'std.list.relems'", + "compose 'std.ielems' and 'std.ireverse' instead", relems), + reverse = DEPRECATED ("41", "'std.list.reverse'", + "compose 'std.list' and 'std.ireverse' instead", reverse), + shape = DEPRECATED ("41", "'std.list.shape'", + "use 'std.table.shape' instead", shape), + transpose = DEPRECATED ("41", "'std.list.transpose'", + "use 'std.functional.zip' instead", transpose), + zip_with = DEPRECATED ("41", "'std.list.zip_with'", + "use 'std.functional.zip_with' instead", zip_with), +} diff --git a/lib/std/object.lua b/lib/std/object.lua index 070516b..a79dd1d 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -34,10 +34,12 @@ local container = require "std.container" local debug = require "std.debug" local std = require "std.base" -local Container = container {} -local getmetamethod = std.getmetamethod +local Container = container.prototype -local DEPRECATED = debug.DEPRECATED + +local function X (decl, fn) + return debug.argscheck ("std.object." .. decl, fn) +end --- Root object. @@ -75,12 +77,9 @@ local DEPRECATED = debug.DEPRECATED -- } -- local bag = Bag ("function", "arguments", "sent", "to", "_init") -return Container { +local Object = Container { _type = "Object", - -- No need for explicit module functions here, because calls to, e.g. - -- `Object.type` will automatically fall back metamethods in `__index`. - __index = { --- Clone an Object. -- @@ -116,7 +115,39 @@ return Container { -- function o:method_2 (n) return self.field_2 + n end -- print (o:method_2 (2)) --> 4 -- os.exit (0) - clone = getmetamethod (container, "__call"), + clone = std.getmetamethod (Container, "__call"), + + --- Return *obj* with references to the fields of *src* merged in. + -- + -- More importantly, split the fields in *src* between *obj* and its + -- metatable. If any field names begin with "_", attach a metatable + -- to *obj* by cloning the metatable from *src*, and then copy the + -- "private" `_` prefixed fields there. + -- + -- You might want to use this function to instantiate your derived + -- object clones when the *src.\_init* is a function -- when + -- *src.\_init* is a table, the default (inherited unless you overwrite + -- it) clone method calls @{mapfields} automatically. When you're + -- using a function `_init` setting, @{clone} doesn't know what to + -- copy into a new object from the `_init` function's arguments... + -- so you're on your own. Except that calling @{mapfields} inside + -- `_init` is safer than manually splitting `src` into `obj` and + -- its metatable, because you'll pick up any fixes and changes when + -- you upgrade stdlib. + -- @static + -- @function mapfields + -- @tparam table obj destination object + -- @tparam table src fields to copy int clone + -- @tparam[opt={}] table map key renames as `{old_key=new_key, ...}` + -- @treturn table *obj* with non-private fields from *src* merged, + -- and a metatable with private fields (if any) merged, both sets + -- of keys renamed according to *map* + -- @usage + -- myobject.mapfields = function (obj, src, map) + -- object.mapfields (obj, src, map) + -- ... + -- end + mapfields = X ( "mapfields (table, table|object, ?table)", std.mapfields), --- Type of an object, or primitive. -- @@ -152,44 +183,11 @@ return Container { -- assert (objtype (h) == io.type (h)) -- -- assert (type {} == type {}) - type = std.type, - - - --- Return *obj* with references to the fields of *src* merged in. - -- - -- More importantly, split the fields in *src* between *obj* and its - -- metatable. If any field names begin with "_", attach a metatable - -- to *obj* by cloning the metatable from *src*, and then copy the - -- "private" `_` prefixed fields there. - -- - -- You might want to use this function to instantiate your derived - -- object clones when the *src.\_init* is a function -- when - -- *src.\_init* is a table, the default (inherited unless you overwrite - -- it) clone method calls @{mapfields} automatically. When you're - -- using a function `_init` setting, @{clone} doesn't know what to - -- copy into a new object from the `_init` function's arguments... - -- so you're on your own. Except that calling @{mapfields} inside - -- `_init` is safer than manually splitting `src` into `obj` and - -- its metatable, because you'll pick up any fixes and changes when - -- you upgrade stdlib. - -- @static - -- @function mapfields - -- @tparam table obj destination object - -- @tparam table src fields to copy int clone - -- @tparam[opt={}] table map key renames as `{old_key=new_key, ...}` - -- @treturn table *obj* with non-private fields from *src* merged, - -- and a metatable with private fields (if any) merged, both sets - -- of keys renamed according to *map* - -- @usage - -- myobject.mapfields = function (obj, src, map) - -- object.mapfields (obj, src, map) - -- ... - -- end - mapfields = container.mapfields.call, + type = X ("type (?any)", std.type), -- Backwards compatibility: - prototype = DEPRECATED ("41.3", "'std.object.prototype'", std.type), + prototype = debug.DEPRECATED ("41.3", "'std.object.prototype'", std.type), }, @@ -226,3 +224,10 @@ return Container { -- @see tostring -- @usage print (anobject) } + + +return std.Module { + prototype = Object, + + type = Object.type, +} diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index b589fbc..df6a5ba 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -14,7 +14,7 @@ local std = require "std.base" -local Object = require "std.object" {} +local Object = require "std.object".prototype local ipairs, pairs = std.ipairs, std.pairs local insert, last, len = std.insert, std.last, std.len diff --git a/lib/std/package.lua b/lib/std/package.lua index 5d456fb..b498151 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -11,8 +11,7 @@ ]] -local std = require "std.base" -local debug = require "std.debug" +local std = require "std.base" local catfile, escape_pattern, invert = std.catfile, std.escape_pattern, std.invert @@ -131,9 +130,10 @@ end local function X (decl, fn) - return debug.argscheck ("std.package." .. decl, fn) + return require "std.debug".argscheck ("std.package." .. decl, fn) end + M = { --- Look for a path segment match of *patt* in *pathstrings*. -- @function find diff --git a/lib/std/set.lua b/lib/std/set.lua index cb0b292..3b2fa1c 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -17,9 +17,8 @@ @see std.container ]] -local std = require "std.base" - -local Container = require "std.container" {} +local std = require "std.base" +local Container = require "std.container".prototype local ielems, pairs, type = std.ielems, std.pairs, std.type @@ -144,14 +143,14 @@ end -- std.type (std.set) --> "Set" -- os.exit (0) Set = Container { - _type = "Set", + _type = "Set", - _init = function (self, t) - for e in ielems (t) do - insert (self, e) - end - return self - end, + _init = function (self, t) + for e in ielems (t) do + insert (self, e) + end + return self + end, --- Union operator. -- @static @@ -231,133 +230,133 @@ Set = Container { table.sort (keys) return type (self) .. " {" .. table.concat (keys, ", ") .. "}" end, +} - _functions = { - --- Delete an element from a set. - -- @static - -- @function delete - -- @tparam Set set a set - -- @param e element - -- @treturn Set the modified *set* - -- @usage - -- set.delete (available, found) - delete = X ("delete (Set, any)", - function (set, e) return rawset (set, e, nil) end), - - --- Find the difference of two sets. - -- @static - -- @function difference - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set a copy of *set1* with elements of *set2* removed - -- @usage - -- all = set.difference (all, {32, 49, 56}) - difference = X ("difference (Set, Set)", difference), - - --- Iterator for sets. - -- @static - -- @function elems - -- @tparam Set set a set - -- @treturn *set* iterator - -- @todo Make the iterator return only the key - -- @usage - -- for code in set.elems (isprintable) do print (code) end - elems = X ("elems (Set)", elems), - - --- Find whether two sets are equal. - -- @static - -- @function equal - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn boolean `true` if *set1* and *set2* each contain identical - -- elements, `false` otherwise - -- @usage - -- if set.equal (keys, {META, CTRL, "x"}) then process (keys) end - equal = X ( "equal (Set, Set)", equal), - - --- Insert an element into a set. - -- @static - -- @function insert - -- @tparam Set set a set - -- @param e element - -- @treturn Set the modified *set* - -- @usage - -- for byte = 32,126 do - -- set.insert (isprintable, string.char (byte)) - -- end - insert = X ("insert (Set, any)", insert), - - --- Find the intersection of two sets. - -- @static - -- @function intersection - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set a new set with elements in both *set1* and *set2* - -- @usage - -- common = set.intersection (a, b) - intersection = X ("intersection (Set, Set)", intersection), - - --- Say whether an element is in a set. - -- @static - -- @function difference - -- @tparam Set set a set - -- @param e element - -- @return `true` if *e* is in *set*, otherwise `false` - -- otherwise - -- @usage - -- if not set.member (keyset, pressed) then return nil end - member = X ("member (Set, any)", member), - - --- Find whether one set is a proper subset of another. - -- @static - -- @function proper_subset - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn boolean `true` if *set2* contains all elements in *set1* - -- but not only those elements, `false` otherwise - -- @usage - -- if set.proper_subset (a, b) then - -- for e in set.elems (set.difference (b, a)) do - -- set.delete (b, e) - -- end - -- end - -- assert (set.equal (a, b)) - proper_subset = X ("proper_subset (Set, Set)", proper_subset), - - --- Find whether one set is a subset of another. - -- @static - -- @function subset - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn boolean `true` if all elements in *set1* are also in *set2*, - -- `false` otherwise - -- @usage - -- if set.subset (a, b) then a = b end - subset = X ("subset (Set, Set)", subset), - - --- Find the symmetric difference of two sets. - -- @static - -- @function symmetric_difference - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set a new set with elements that are in *set1* or *set2* - -- but not both - -- @usage - -- unique = set.symmetric_difference (a, b) - symmetric_difference = X ("symmetric_difference (Set, Set)", - symmetric_difference), - - --- Find the union of two sets. - -- @static - -- @function union - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set a copy of *set1* with elements in *set2* merged in - -- @usage - -- all = set.union (a, b) - union = X ("union (Set, Set)", union), - }, -} +return std.Module { + prototype = Set, + + --- Delete an element from a set. + -- @static + -- @function delete + -- @tparam Set set a set + -- @param e element + -- @treturn Set the modified *set* + -- @usage + -- set.delete (available, found) + delete = X ("delete (Set, any)", + function (set, e) return rawset (set, e, nil) end), + + --- Find the difference of two sets. + -- @static + -- @function difference + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn Set a copy of *set1* with elements of *set2* removed + -- @usage + -- all = set.difference (all, {32, 49, 56}) + difference = X ("difference (Set, Set)", difference), -return Set + --- Iterator for sets. + -- @static + -- @function elems + -- @tparam Set set a set + -- @treturn *set* iterator + -- @todo Make the iterator return only the key + -- @usage + -- for code in set.elems (isprintable) do print (code) end + elems = X ("elems (Set)", elems), + + --- Find whether two sets are equal. + -- @static + -- @function equal + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn boolean `true` if *set1* and *set2* each contain identical + -- elements, `false` otherwise + -- @usage + -- if set.equal (keys, {META, CTRL, "x"}) then process (keys) end + equal = X ( "equal (Set, Set)", equal), + + --- Insert an element into a set. + -- @static + -- @function insert + -- @tparam Set set a set + -- @param e element + -- @treturn Set the modified *set* + -- @usage + -- for byte = 32,126 do + -- set.insert (isprintable, string.char (byte)) + -- end + insert = X ("insert (Set, any)", insert), + + --- Find the intersection of two sets. + -- @static + -- @function intersection + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn Set a new set with elements in both *set1* and *set2* + -- @usage + -- common = set.intersection (a, b) + intersection = X ("intersection (Set, Set)", intersection), + + --- Say whether an element is in a set. + -- @static + -- @function difference + -- @tparam Set set a set + -- @param e element + -- @return `true` if *e* is in *set*, otherwise `false` + -- otherwise + -- @usage + -- if not set.member (keyset, pressed) then return nil end + member = X ("member (Set, any)", member), + + --- Find whether one set is a proper subset of another. + -- @static + -- @function proper_subset + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn boolean `true` if *set2* contains all elements in *set1* + -- but not only those elements, `false` otherwise + -- @usage + -- if set.proper_subset (a, b) then + -- for e in set.elems (set.difference (b, a)) do + -- set.delete (b, e) + -- end + -- end + -- assert (set.equal (a, b)) + proper_subset = X ("proper_subset (Set, Set)", proper_subset), + + --- Find whether one set is a subset of another. + -- @static + -- @function subset + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn boolean `true` if all elements in *set1* are also in *set2*, + -- `false` otherwise + -- @usage + -- if set.subset (a, b) then a = b end + subset = X ("subset (Set, Set)", subset), + + --- Find the symmetric difference of two sets. + -- @static + -- @function symmetric_difference + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn Set a new set with elements that are in *set1* or *set2* + -- but not both + -- @usage + -- unique = set.symmetric_difference (a, b) + symmetric_difference = X ("symmetric_difference (Set, Set)", + symmetric_difference), + + --- Find the union of two sets. + -- @static + -- @function union + -- @tparam Set set1 a set + -- @tparam Set set2 another set + -- @treturn Set a copy of *set1* with elements in *set2* merged in + -- @usage + -- all = set.union (a, b) + union = X ("union (Set, Set)", union), +} diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 9b24053..9fe4982 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -24,12 +24,10 @@ local std = require "std.base" local debug = require "std.debug" -local Object = require "std.object" {} +local Object = require "std.object".prototype local ielems, insert = std.ielems, std.insert -local M, StrBuf - local function __concat (self, x) return insert (self, x) @@ -53,7 +51,7 @@ local function X (decl, fn) end -M = { +local M = { --- Add a object to a buffer. -- Elements are stringified lazily, so if add a table and then change -- its contents, the contents of the buffer will be affected too. @@ -101,7 +99,7 @@ M.tostring = DEPRECATED ("41.1", "std.strbuf.tostring", -- b = b:concat "seven" -- print (a, b) --> 1234 1234fivesixseven -- os.exit (0) -StrBuf = Object { +local StrBuf = Object { _type = "StrBuf", __index = M, @@ -127,4 +125,6 @@ StrBuf = Object { } -return StrBuf +return std.Module { + prototype = StrBuf, +} diff --git a/lib/std/string.lua b/lib/std/string.lua index 8cf20b8..c2ae211 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -13,7 +13,7 @@ local std = require "std.base" local debug = require "std.debug" -local StrBuf = require "std.strbuf" {} +local StrBuf = require "std.strbuf".prototype local copy = std.copy local getmetamethod = std.getmetamethod @@ -75,7 +75,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.string = std.copy (namespace.string or {}, M) + namespace.string = copy (namespace.string or {}, M) local string_metatable = getmetatable "" string_metatable.__concat = M.__concat diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 131c1f3..a43fa1b 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -20,7 +20,7 @@ local std = require "std.base" local operator = require "std.operator" -local Container = require "std.container" {} +local Container = require "std.container".prototype local ielems, ipairs, leaves, pairs, stdtype = std.ielems, std.ipairs, std.leaves, std.pairs, std.type @@ -166,115 +166,116 @@ Tree = Container { rawset (tr, i, v) end end, - - _functions = { - --- Make a deep copy of a tree, including any metatables. - -- @static - -- @function clone - -- @tparam table t tree or tree-like table - -- @tparam boolean nometa if non-`nil` don't copy metatables - -- @treturn Tree|table a deep copy of *tr* - -- @see std.table.clone - -- @usage - -- tr = {"one", {two=2}, {{"three"}, four=4}} - -- copy = clone (tr) - -- copy[2].two=5 - -- assert (tr[2].two == 2) - clone = X ("clone (table, ?boolean|:nometa)", clone), - - --- Tree iterator which returns just numbered leaves, in order. - -- @static - -- @function ileaves - -- @tparam Tree|table tr tree or tree-like table - -- @treturn function iterator function - -- @treturn Tree|table the tree *tr* - -- @see inodes - -- @see leaves - -- @usage - -- --> t = {"one", "three", "five"} - -- for leaf in ileaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} - -- do - -- t[#t + 1] = leaf - -- end - ileaves = X ("ileaves (table)", function (t) return leaves (ipairs, t) end), - - --- Tree iterator over numbered nodes, in order. - -- - -- The iterator function behaves like @{nodes}, but only traverses the - -- array part of the nodes of *tr*, ignoring any others. - -- @static - -- @function inodes - -- @tparam Tree|table tr tree or tree-like table to iterate over - -- @treturn function iterator function - -- @treturn tree|table the tree, *tr* - -- @see nodes - inodes = X ("inodes (table)", function (t) return _nodes (ipairs, t) end), - - --- Tree iterator which returns just leaves. - -- @static - -- @function leaves - -- @tparam table t tree or tree-like table - -- @treturn function iterator function - -- @treturn table *t* - -- @see ileaves - -- @see nodes - -- @usage - -- for leaf in leaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} - -- do - -- t[#t + 1] = leaf - -- end - -- --> t = {2, 4, "five", "foo", "one", "three"} - -- table.sort (t, lambda "=tostring(_1) < tostring(_2)") - leaves = X ("leaves (table)", function (t) return leaves (pairs, t) end), - - --- Destructively deep-merge one tree into another. - -- @static - -- @function merge - -- @tparam table t destination tree - -- @tparam table u table with nodes to merge - -- @treturn table *t* with nodes from *u* merged in - -- @see std.table.merge - -- @usage - -- merge (dest, {{exists=1}, {{not = {present = { inside = "dest" }}}}}) - merge = X ("merge (table, table)", merge), - - --- Tree iterator over all nodes. - -- - -- The returned iterator function performs a depth-first traversal of - -- `tr`, and at each node it returns `{node-type, tree-path, tree-node}` - -- where `node-type` is `branch`, `join` or `leaf`; `tree-path` is a - -- list of keys used to reach this node, and `tree-node` is the current - -- node. - -- - -- Note that the `tree-path` reuses the same table on each iteration, so - -- you must `table.clone` a copy if you want to take a snap-shot of the - -- current state of the `tree-path` list before the next iteration - -- changes it. - -- @static - -- @function nodes - -- @tparam Tree|table tr tree or tree-like table to iterate over - -- @treturn function iterator function - -- @treturn Tree|table the tree, *tr* - -- @see inodes - -- @usage - -- -- tree = +-- node1 - -- -- | +-- leaf1 - -- -- | '-- leaf2 - -- -- '-- leaf 3 - -- tree = Tree { Tree { "leaf1", "leaf2"}, "leaf3" } - -- for node_type, path, node in nodes (tree) do - -- print (node_type, path, node) - -- end - -- --> "branch" {} {{"leaf1", "leaf2"}, "leaf3"} - -- --> "branch" {1} {"leaf1", "leaf"2") - -- --> "leaf" {1,1} "leaf1" - -- --> "leaf" {1,2} "leaf2" - -- --> "join" {1} {"leaf1", "leaf2"} - -- --> "leaf" {2} "leaf3" - -- --> "join" {} {{"leaf1", "leaf2"}, "leaf3"} - -- os.exit (0) - nodes = X ("nodes (table)", function (t) return _nodes (pairs, t) end), - }, } -return Tree + +return std.Module { + prototype = Tree, + + --- Make a deep copy of a tree, including any metatables. + -- @static + -- @function clone + -- @tparam table t tree or tree-like table + -- @tparam boolean nometa if non-`nil` don't copy metatables + -- @treturn Tree|table a deep copy of *tr* + -- @see std.table.clone + -- @usage + -- tr = {"one", {two=2}, {{"three"}, four=4}} + -- copy = clone (tr) + -- copy[2].two=5 + -- assert (tr[2].two == 2) + clone = X ("clone (table, ?boolean|:nometa)", clone), + + --- Tree iterator which returns just numbered leaves, in order. + -- @static + -- @function ileaves + -- @tparam Tree|table tr tree or tree-like table + -- @treturn function iterator function + -- @treturn Tree|table the tree *tr* + -- @see inodes + -- @see leaves + -- @usage + -- --> t = {"one", "three", "five"} + -- for leaf in ileaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} + -- do + -- t[#t + 1] = leaf + -- end + ileaves = X ("ileaves (table)", function (t) return leaves (ipairs, t) end), + + --- Tree iterator over numbered nodes, in order. + -- + -- The iterator function behaves like @{nodes}, but only traverses the + -- array part of the nodes of *tr*, ignoring any others. + -- @static + -- @function inodes + -- @tparam Tree|table tr tree or tree-like table to iterate over + -- @treturn function iterator function + -- @treturn tree|table the tree, *tr* + -- @see nodes + inodes = X ("inodes (table)", function (t) return _nodes (ipairs, t) end), + + --- Tree iterator which returns just leaves. + -- @static + -- @function leaves + -- @tparam table t tree or tree-like table + -- @treturn function iterator function + -- @treturn table *t* + -- @see ileaves + -- @see nodes + -- @usage + -- for leaf in leaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} + -- do + -- t[#t + 1] = leaf + -- end + -- --> t = {2, 4, "five", "foo", "one", "three"} + -- table.sort (t, lambda "=tostring(_1) < tostring(_2)") + leaves = X ("leaves (table)", function (t) return leaves (pairs, t) end), + + --- Destructively deep-merge one tree into another. + -- @static + -- @function merge + -- @tparam table t destination tree + -- @tparam table u table with nodes to merge + -- @treturn table *t* with nodes from *u* merged in + -- @see std.table.merge + -- @usage + -- merge (dest, {{exists=1}, {{not = {present = { inside = "dest" }}}}}) + merge = X ("merge (table, table)", merge), + + --- Tree iterator over all nodes. + -- + -- The returned iterator function performs a depth-first traversal of + -- `tr`, and at each node it returns `{node-type, tree-path, tree-node}` + -- where `node-type` is `branch`, `join` or `leaf`; `tree-path` is a + -- list of keys used to reach this node, and `tree-node` is the current + -- node. + -- + -- Note that the `tree-path` reuses the same table on each iteration, so + -- you must `table.clone` a copy if you want to take a snap-shot of the + -- current state of the `tree-path` list before the next iteration + -- changes it. + -- @static + -- @function nodes + -- @tparam Tree|table tr tree or tree-like table to iterate over + -- @treturn function iterator function + -- @treturn Tree|table the tree, *tr* + -- @see inodes + -- @usage + -- -- tree = +-- node1 + -- -- | +-- leaf1 + -- -- | '-- leaf2 + -- -- '-- leaf 3 + -- tree = Tree { Tree { "leaf1", "leaf2"}, "leaf3" } + -- for node_type, path, node in nodes (tree) do + -- print (node_type, path, node) + -- end + -- --> "branch" {} {{"leaf1", "leaf2"}, "leaf3"} + -- --> "branch" {1} {"leaf1", "leaf"2") + -- --> "leaf" {1,1} "leaf1" + -- --> "leaf" {1,2} "leaf2" + -- --> "join" {1} {"leaf1", "leaf2"} + -- --> "leaf" {2} "leaf3" + -- --> "join" {} {{"leaf1", "leaf2"}, "leaf3"} + -- os.exit (0) + nodes = X ("nodes (table)", function (t) return _nodes (pairs, t) end), +} diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 4209897..f4ec77f 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -21,8 +21,10 @@ @see std.container ]] -local Container = require "std.container" {} -local stdtype = require "std.base".type +local Container = require "std.container".prototype +local std = require "std.base" + +local stdtype = std.type -- Stringify tuple values, as a memoization key. @@ -81,7 +83,7 @@ local intern = setmetatable ({}, { -- count (nil) --> 1 -- count (false) --> 1 -- count (false, nil, true, nil) --> 4 -return Container { +local Tuple = Container { _type = "Tuple", _init = function (obj, ...) @@ -136,3 +138,8 @@ return Container { return ("%s (%s)"):format (stdtype (self), argstr (self)) end, } + + +return std.Module { + prototype = Tuple, +} diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 592cc10..f861ac8 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -1,5 +1,5 @@ before: - Container = require "std.container" {} + Container = require "std.container".prototype specify std.container: - context when required: @@ -51,17 +51,15 @@ specify std.container: expect (getmetatable (things)._baz).to_be "quux" - context with module functions: - before: - reduce = require "std.functional".reduce - elems = require "std".elems - functions = { + Bag = require "std.base".Module { + prototype = Container { _type = "Bag" }, count = function (bag) - return reduce (function (r, k) return r + k end, 0, elems, bag) + local n = 0 + for _, m in pairs (bag) do n = n + m end + return n end, } - Bag = Container { - _type = "Bag", _functions = functions, count = functions.count, - } - - it does not propagate _functions: + - it does not propagate module functions: things = Bag {} expect (things.count).to_be (nil) - it does not provide object methods: | diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 9a8757e..4f15af7 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -214,7 +214,7 @@ specify std.debug: - describe argcheck: - before: | - Object = require 'std.object' + Object = require "std.object".prototype List = Object { _type = "List" } Foo = Object { _type = "Foo" } diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index ff12cf6..4e72523 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1,39 +1,19 @@ before: - this_module = "std.list" - global_table = "_G" - - exported_apis = { "append", "compare", "concat", "cons", "rep", "sub", - "tail" } - deprecations = { "depair", "enpair", "elems", "filter", "flatten", "foldl", - "foldr", "index_key", "index_value", "map", "map_with", - "project", "relems", "reverse", "shape", "transpose", - "zip_with" } - - M = require (this_module) - - List = M {} - l = List {"foo", "bar", "baz"} - + this_module = "std.list" + M = require (this_module) + List = M.prototype + l = List {"foo", "bar", "baz"} specify std.list: - context when required: - context by name: - - it does not touch the global table: - expect (show_apis {added_to="_G", by="std.list"}). - to_equal {} - - it exports the documented apis: - t, apis = {}, require "std.base".copy (exported_apis) - for k in pairs (M) do t[#t + 1] = k end - for _, v in ipairs (deprecations) do - apis[#apis + 1] = v - end - expect (t).to_contain.a_permutation_of (apis) - + - it does not perturb the global namespace: + expect (show_apis {added_to="_G", by="std.list"}).to_equal {} - 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 perturb the global namespace: + expect (show_apis {added_to="_G", by="std"}).to_equal {} + - describe construction: - context from List clone method: diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index df9bf22..6f87aec 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -1,7 +1,7 @@ before: - object = require "std.object" - Object = object {} - obj = Object {"foo", "bar", baz="quux"} + object = require "std.object" + Object = object.prototype + obj = Object {"foo", "bar", baz="quux"} function copy (t) local r = {} @@ -43,10 +43,12 @@ specify std.object: expect (getmetatable (o)).not_to_be (getmetatable (Object)) expect (getmetatable (o)._baz).to_be "quux" +# std.object.prototype is now something entirely different to +# Object.prototype as specified here. - describe prototype: - before: o = Object {} - fn = object.prototype + fn = Object.prototype - it writes a deprecation warning: setdebug { deprecate = "nil" } diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index a8b103d..b9227dd 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -1,6 +1,7 @@ before: - Set = require "std.set" - s = Set {"foo", "bar", "bar"} + set = require "std.set" + Set = set.prototype + s = Set {"foo", "bar", "bar"} specify std.set: - describe require: @@ -27,39 +28,41 @@ specify std.set: - describe delete: - context when called as a Set module function: - before: + fn = set.delete s = Set {"foo", "bar", "baz"} - it returns a set object: - expect (objtype (Set.delete (s, "foo"))).to_be "Set" + expect (objtype (fn (s, "foo"))).to_be "Set" - it is destructive: - Set.delete (s, "bar") + fn (s, "bar") expect (s).not_to_have_member "bar" - it returns the modified set: - expect (Set.delete (s, "baz")).not_to_have_member "baz" + expect (fn (s, "baz")).not_to_have_member "baz" - it ignores removal of non-members: | clone = s {} - expect (Set.delete (s, "quux")).to_equal (clone) + expect (fn (s, "quux")).to_equal (clone) - it deletes a member from the set: expect (s).to_have_member "bar" - Set.delete (s, "bar") + fn (s, "bar") expect (s).not_to_have_member "bar" - it works with an empty set: - expect (Set.delete (Set {}, "quux")).to_equal (Set {}) + expect (fn (Set {}, "quux")).to_equal (Set {}) - describe difference: - before: + fn = set.difference r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz", "quux"} - context when called as a Set module function: - it returns a set object: - expect (objtype (Set.difference (r, s))).to_be "Set" + expect (objtype (fn (r, s))).to_be "Set" - it is non-destructive: - Set.difference (r, s) + fn (r, s) expect (r).to_equal (Set {"foo", "bar", "baz"}) expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members of the first that are not in the second: - expect (Set.difference (r, s)).to_equal (Set {"foo"}) + expect (fn (r, s)).to_equal (Set {"foo"}) - context when called as a set metamethod: - it returns a set object: expect (objtype (r - s)).to_be "Set" @@ -73,39 +76,41 @@ specify std.set: - describe elems: - before: + fn = set.elems s = Set {"foo", "bar", "baz"} - context when called as a Set module function: - it is an iterator over set members: t = {} - for e in Set.elems (s) do table.insert (t, e) end + for e in fn (s) do table.insert (t, e) end table.sort (t) expect (t).to_equal {"bar", "baz", "foo"} - it works for an empty set: t = {} - for e in Set.elems (Set {}) do table.insert (t, e) end + for e in fn (Set {}) do table.insert (t, e) end expect (t).to_equal {} - describe insert: - context when called as a Set module function: - before: + fn = set.insert s = Set {"foo"} - it returns a set object: - expect (objtype (Set.insert (s, "bar"))).to_be "Set" + expect (objtype (fn (s, "bar"))).to_be "Set" - it is destructive: - Set.insert (s, "bar") + fn (s, "bar") expect (s).to_have_member "bar" - it returns the modified set: - expect (Set.insert (s, "baz")).to_have_member "baz" + expect (fn (s, "baz")).to_have_member "baz" - it ignores insertion of existing members: - expect (Set.insert (s, "foo")).to_equal (Set {"foo"}) + expect (fn (s, "foo")).to_equal (Set {"foo"}) - it inserts a new member into the set: expect (s).not_to_have_member "bar" - Set.insert (s, "bar") + fn (s, "bar") expect (s).to_have_member "bar" - it works with an empty set: - expect (Set.insert (Set {}, "foo")).to_equal (s) + expect (fn (Set {}, "foo")).to_equal (s) - context when called as a set metamethod: - before: s = Set {"foo"} @@ -130,18 +135,19 @@ specify std.set: - describe intersection: - before: + fn = set.intersection r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz", "quux"} - context when called as a Set module function: - it returns a set object: - expect (objtype (Set.intersection (r, s))).to_be "Set" + expect (objtype (fn (r, s))).to_be "Set" - it is non-destructive: - Set.intersection (r, s) + fn (r, s) expect (r).to_equal (Set {"foo", "bar", "baz"}) expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members common to both arguments: - expect (Set.intersection (r, s)). + expect (fn (r, s)). to_equal (Set {"bar", "baz"}) - context when called as a set metamethod: - it returns a set object: @@ -156,16 +162,18 @@ specify std.set: - describe member: - - before: s = Set {"foo", "bar"} + - before: + fn = set.member + s = Set {"foo", "bar"} - context when called as a Set module function: - it succeeds when set contains the given member: - expect (Set.member (s, "foo")).to_be (true) + expect (fn (s, "foo")).to_be (true) - it fails when set does not contain the given member: - expect (Set.member (s, "baz")).not_to_be (true) + expect (fn (s, "baz")).not_to_be (true) - it works with the empty set: s = Set {} - expect (Set.member (s, "foo")).not_to_be (true) + expect (fn (s, "foo")).not_to_be (true) - context when called as a set metamethod: - it succeeds when set contains the given member: expect (s["foo"]).to_be (true) @@ -178,18 +186,19 @@ specify std.set: - describe proper_subset: - before: + fn = set.proper_subset r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz"} - context when called as a Set module function: - it succeeds when set contains all elements of another: - expect (Set.proper_subset (s, r)).to_be (true) + expect (fn (s, r)).to_be (true) - it fails when two sets are equal: r = s {} - expect (Set.proper_subset (s, r)).to_be (false) + expect (fn (s, r)).to_be (false) - it fails when set does not contain all elements of another: s = s + Set {"quux"} - expect (Set.proper_subset (r, s)).to_be (false) + expect (fn (r, s)).to_be (false) - context when called as a set metamethod: - it succeeds when set contains all elements of another: expect (s < r).to_be (true) @@ -203,18 +212,19 @@ specify std.set: - describe subset: - before: + fn = set.subset r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz"} - context when called as a Set module function: - it succeeds when set contains all elements of another: - expect (Set.subset (s, r)).to_be (true) + expect (fn (s, r)).to_be (true) - it succeeds when two sets are equal: r = s {} - expect (Set.subset (s, r)).to_be (true) + expect (fn (s, r)).to_be (true) - it fails when set does not contain all elements of another: s = s + Set {"quux"} - expect (Set.subset (r, s)).to_be (false) + expect (fn (r, s)).to_be (false) - context when called as a set metamethod: - it succeeds when set contains all elements of another: expect (s <= r).to_be (true) @@ -228,19 +238,20 @@ specify std.set: - describe symmetric_difference: - before: + fn = set.symmetric_difference r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz", "quux"} - context when called as a Set module function: - it returns a set object: - expect (objtype (Set.symmetric_difference (r, s))). + expect (objtype (fn (r, s))). to_be "Set" - it is non-destructive: - Set.symmetric_difference (r, s) + fn (r, s) expect (r).to_equal (Set {"foo", "bar", "baz"}) expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: - expect (Set.symmetric_difference (r, s)). + expect (fn (r, s)). to_equal (Set {"foo", "quux"}) - context when called as a set metamethod: - it returns a set object: @@ -255,18 +266,19 @@ specify std.set: - describe union: - before: + fn = set.union r = Set {"foo", "bar", "baz"} s = Set {"bar", "baz", "quux"} - context when called as a Set module function: - it returns a set object: - expect (objtype (Set.union (r, s))).to_be "Set" + expect (objtype (fn (r, s))).to_be "Set" - it is non-destructive: - Set.union (r, s) + fn (r, s) expect (r).to_equal (Set {"foo", "bar", "baz"}) expect (s).to_equal (Set {"bar", "baz", "quux"}) - it returns a set containing members in only one argument set: - expect (Set.union (r, s)). + expect (fn (r, s)). to_equal (Set {"foo", "bar", "baz", "quux"}) - context when called as a set metamethod: - it returns a set object: diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index 88bbc07..0d9e1ed 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -1,6 +1,5 @@ before: - object = require "std.object" - StrBuf = require "std.strbuf" + StrBuf = require "std.strbuf".prototype b = StrBuf {"foo", "bar"} specify std.strbuf: @@ -15,7 +14,7 @@ specify std.strbuf: - it constructs a new strbuf: b = StrBuf:clone {} expect (b).not_to_be (StrBuf) - expect (object.type (b)).to_be "StrBuf" + expect (objtype (b)).to_be "StrBuf" - it reuses the StrBuf metatable: a, b = StrBuf:clone {"a"}, StrBuf:clone {"b"} expect (getmetatable (a)).to_be (getmetatable (b)) @@ -24,7 +23,7 @@ specify std.strbuf: expect (a).to_equal (b) - it serves as a prototype for new instances: obj = b:clone {} - expect (object.type (obj)).to_be "StrBuf" + expect (objtype (obj)).to_be "StrBuf" expect (obj).to_equal (b) expect (getmetatable (obj)).to_be (getmetatable (b)) @@ -33,7 +32,7 @@ specify std.strbuf: - it constructs a new strbuf: b = StrBuf {} expect (b).not_to_be (StrBuf) - expect (object.type (b)).to_be "StrBuf" + expect (objtype (b)).to_be "StrBuf" - it reuses the StrBuf metatable: a, b = StrBuf {"a"}, StrBuf {"b"} expect (getmetatable (a)).to_be (getmetatable (b)) @@ -42,7 +41,7 @@ specify std.strbuf: expect (a).to_equal (b) - it serves as a prototype for new instances: obj = b {} - expect (object.type (obj)).to_be "StrBuf" + expect (objtype (obj)).to_be "StrBuf" expect (obj).to_equal (b) expect (getmetatable (obj)).to_be (getmetatable (b)) @@ -83,29 +82,29 @@ specify std.strbuf: - context as a module function: - it appends a string: a = StrBuf.concat (a, "baz") - expect (object.type (a)).to_be "StrBuf" + expect (objtype (a)).to_be "StrBuf" expect (tostring (a)).to_be "foobarbaz" - it appends a StrBuf: a = StrBuf.concat (a, b) - expect (object.type (a)).to_be "StrBuf" + expect (objtype (a)).to_be "StrBuf" expect (tostring (a)).to_be "foobarbazquux" - context as an object method: - it appends a string: a = a:concat "baz" - expect (object.type (a)).to_be "StrBuf" + expect (objtype (a)).to_be "StrBuf" expect (tostring(a)).to_be "foobarbaz" - it appends a StrBuf: a = a:concat (b) - expect (object.type (a)).to_be "StrBuf" + expect (objtype (a)).to_be "StrBuf" expect (tostring (a)).to_be "foobarbazquux" - context as a metamethod: - it appends a string: a = a .. "baz" - expect (object.type (a)).to_be "StrBuf" + expect (objtype (a)).to_be "StrBuf" expect (tostring (a)).to_be "foobarbaz" - it appends a StrBuf: a = a .. b - expect (object.type (a)).to_be "StrBuf" + expect (objtype (a)).to_be "StrBuf" expect (tostring (a)).to_be "foobarbazquux" - it stringifies lazily: a = StrBuf {1} diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 2ab0833..b910bd5 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -2,7 +2,8 @@ before: | global_table = "_G" this_module = "std.tree" - Tree = require "std.tree" + tree = require "std.tree" + Tree = tree.prototype specify std.tree: - before: @@ -43,7 +44,7 @@ specify std.tree: - describe clone: - before: subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } - f = Tree.clone + f = tree.clone - it does not just return the subject: expect (f (subject)).not_to_be (subject) - it does copy the subject: @@ -62,7 +63,7 @@ specify std.tree: - describe ileaves: - before: - f = Tree.ileaves + f = tree.ileaves l = {} - it iterates over array part of a table argument: for v in f {"first", "second", "3rd"} do l[1+#l]=v end @@ -97,13 +98,13 @@ specify std.tree: - describe inodes: - before: | - f = Tree.inodes + f = tree.inodes local tostring = (require "std.string").tostring function traverse (subject) l = {} for ty, p, n in f (subject) do - l[1+#l]={ty, Tree.clone (p), n} + l[1+#l]={ty, tree.clone (p), n} end return l end @@ -183,7 +184,7 @@ specify std.tree: - describe leaves: - before: - f = Tree.leaves + f = tree.leaves l = {} - it iterates over elements of a table argument: for v in f {"first", "second", "3rd"} do l[1+#l]=v end @@ -220,14 +221,14 @@ specify std.tree: - describe merge: - before: | - f = Tree.merge + f = tree.merge -- Additional merge keys which are moderately unusual t1 = Tree { k1 = "v1", k2 = "if", k3 = Tree {"?"} } t2 = Tree { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = "v2" } - target = Tree.clone (t1) - for ty, p, n in Tree.nodes (t2) do + target = tree.clone (t1) + for ty, p, n in tree.nodes (t2) do if ty == "leaf" then target[p] = n end end - it does not create a whole new table: @@ -239,7 +240,7 @@ specify std.tree: - it merges keys from t2 into t1: | expect (f (t1, t2)).to_equal (target) - it gives precedence to values from t2: - original = Tree.clone (t1) + original = tree.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) @@ -250,11 +251,11 @@ specify std.tree: - describe nodes: - before: - f = Tree.nodes + f = tree.nodes function traverse (subject) l = {} - for ty, p, n in f (subject) do l[1+#l]={ty, Tree.clone (p), n} end + for ty, p, n in f (subject) do l[1+#l]={ty, tree.clone (p), n} end return l end - it iterates over the elements of a table argument: | diff --git a/specs/tuple_spec.yaml b/specs/tuple_spec.yaml index 0e7d66c..c420489 100644 --- a/specs/tuple_spec.yaml +++ b/specs/tuple_spec.yaml @@ -1,6 +1,6 @@ before: objtype = require "std.object".type - Tuple = require "std.tuple" + Tuple = require "std.tuple".prototype t0, t1, t2 = Tuple (), Tuple "one", Tuple (false, true) From 8fd1fb4fce799e575d7a46e2be504f706dfb47ea Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 11 Jun 2015 13:09:55 -0500 Subject: [PATCH 563/703] refactor: organize base.lua export table by main import module. * lib/std/base.lua: Make subtables in the export table so that internal imports mirror external imports. Adjust all clients accordingly. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 4 +- lib/std/base.lua | 133 +++++++++++++++++++++----------------- lib/std/container.lua | 10 +-- lib/std/debug.lua | 15 +++-- lib/std/functional.lua | 13 ++-- lib/std/io.lua | 18 +++--- lib/std/list.lua | 15 ++--- lib/std/math.lua | 4 +- lib/std/object.lua | 4 +- lib/std/optparse.lua | 5 +- lib/std/package.lua | 8 +-- lib/std/set.lua | 2 +- lib/std/strbuf.lua | 4 +- lib/std/string.lua | 16 ++--- lib/std/table.lua | 20 +++--- lib/std/tree.lua | 11 ++-- lib/std/tuple.lua | 2 +- specs/container_spec.yaml | 2 +- specs/string_spec.yaml | 2 +- specs/table_spec.yaml | 2 +- 20 files changed, 157 insertions(+), 133 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index e12117b..a0048ca 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -29,7 +29,7 @@ local M, monkeys local function monkey_patch (namespace) - std.copy (namespace or _G, monkeys) + std.base.copy (namespace or _G, monkeys) return M end @@ -286,7 +286,7 @@ M = { } -monkeys = std.copy ({}, M) +monkeys = std.base.copy ({}, M) -- Don't monkey_patch these apis into _G! for _, api in ipairs {"barrel", "monkey_patch", "version"} do diff --git a/lib/std/base.lua b/lib/std/base.lua index 5e10eea..082ec61 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -480,69 +480,84 @@ local function tostring (x) end - +-- 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 { - copy = copy, - keysort = keysort, - merge = merge, - okeys = okeys, - raise = raise, - - -- std.lua -- - assert = assert, - eval = eval, - elems = elems, - ielems = ielems, - ipairs = ipairs, - ireverse = ireverse, - npairs = npairs, - pairs = pairs, - ripairs = ripairs, - rnpairs = rnpairs, - require = require, - tostring = tostring, + assert = assert, + eval = eval, + elems = elems, + getmetamethod = getmetamethod, + ielems = ielems, + ipairs = ipairs, + ireverse = ireverse, + len = len, + npairs = npairs, + pairs = pairs, + ripairs = ripairs, + rnpairs = rnpairs, + require = require, + tostring = tostring, type = function (x) return (getmetatable (x) or {})._type or io.type (x) or type (x) end, - - -- debug.lua -- - argerror = argerror, - - -- functional.lua -- - callable = callable, - collect = collect, - nop = function () end, - reduce = reduce, - - -- io.lua -- - catfile = catfile, - - -- list.lua -- - compare = compare, - - -- object.lua -- - Module = Module, - mapfields = mapfields, - - -- package.lua -- - dirsep = dirsep, - - -- string.lua -- - escape_pattern = escape_pattern, - render = render, - split = split, - - -- table.lua -- - getmetamethod = getmetamethod, - insert = insert, - invert = invert, - last = last, - len = len, - maxn = maxn, - unpack = unpack, - - -- tree.lua -- - leaves = leaves, + base = { + copy = copy, + keysort = keysort, + last = last, + merge = merge, + raise = raise, + }, + + debug = { + argerror = argerror, + }, + + functional = { + callable = callable, + collect = collect, + nop = function () end, + reduce = reduce, + }, + + io = { + catfile = catfile, + }, + + list = { + compare = compare, + }, + + object = { + Module = Module, + mapfields = mapfields, + }, + + package = { + dirsep = dirsep, + }, + + string = { + escape_pattern = escape_pattern, + render = render, + split = split, + }, + + table = { + insert = insert, + invert = invert, + maxn = maxn, + okeys = okeys, + unpack = unpack, + }, + + tree = { + leaves = leaves, + }, } diff --git a/lib/std/container.lua b/lib/std/container.lua index 45cc2fb..a7ee823 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -36,7 +36,9 @@ local _DEBUG = require "std.debug_init"._DEBUG local std = require "std.base" local debug = require "std.debug" -local ipairs, okeys, tostring = std.ipairs, std.okeys, std.tostring +local ipairs, tostring = std.ipairs, std.tostring +local mapfields = std.object.mapfields +local okeys = std.table.okeys @@ -95,7 +97,7 @@ local function __call (self, ...) if type (mt._init) == "function" then obj = mt._init (obj, ...) else - obj = (self.mapfields or std.mapfields) (obj, (...), mt._init) + obj = (self.mapfields or mapfields) (obj, (...), mt._init) end -- If a metatable was set, then merge our fields and use it. @@ -205,9 +207,9 @@ if _DEBUG.argcheck then end -return std.Module { +return std.object.Module { prototype = setmetatable ({}, Container), mapfields = debug.argscheck ( - "std.container.mapfields (table, table|object, ?table)", std.mapfields), + "std.container.mapfields (table, table|object, ?table)", mapfields), } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 7822ec3..566a2df 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -33,11 +33,14 @@ local debug_init = require "std.debug_init" local std = require "std.base" local _DEBUG = debug_init._DEBUG -local argerror, raise = std.argerror, std.raise -local stdtype, unpack = std.type, std.unpack -local copy, split, tostring = std.copy, std.split, std.tostring -local insert, last, len, maxn = std.insert, std.last, std.len, std.maxn -local ipairs, pairs = std.ipairs, std.pairs + +local ipairs, len, pairs, stdtype, tostring = + std.ipairs, std.len, std.pairs, std.type, std.tostring +local copy, last, raise = std.base.copy, std.base.last, std.base.raise +local argerror = std.debug.argerror +local split = std.string.split +local insert, maxn, unpack = + std.table.insert, std.table.maxn, std.table.unpack local M @@ -558,7 +561,7 @@ else -- Turn off argument checking if _DEBUG is false, or a table containing -- a false valued `argcheck` field. - argcheck = std.nop + argcheck = std.functional.nop argscheck = function (decl, inner) return inner end end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 588e01f..37eafc2 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -10,10 +10,11 @@ local std = require "std.base" -local ielems, ipairs, ireverse, npairs, pairs = - std.ielems, std.ipairs, std.ireverse, std.npairs, std.pairs -local callable, copy, len, reduce, unpack = - std.callable, std.copy, std.len, std.reduce, std.unpack +local ielems, ipairs, ireverse, len, npairs, pairs = + std.ielems, std.ipairs, std.ireverse, std.len, std.npairs, std.pairs +local copy = std.base.copy +local callable, reduce = std.functional.callable, std.functional.reduce +local unpack = std.table.unpack local loadstring = loadstring or load @@ -346,7 +347,7 @@ local M = { -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} - collect = X ("collect ([func], any...)", std.collect), + collect = X ("collect ([func], any...)", std.functional.collect), --- Compose functions. -- @function compose @@ -513,7 +514,7 @@ local M = { -- @see id -- @usage -- if unsupported then vtable["memrmem"] = nop end - nop = std.nop, -- ignores all arguments + nop = std.functional.nop, -- ignores all arguments --- Functional list product. -- diff --git a/lib/std/io.lua b/lib/std/io.lua index 017dbf0..4c9d0b3 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -14,11 +14,13 @@ local std = require "std.base" local debug = require "std.debug" -local argerror = debug.argerror -local catfile, dirsep, insert, len, leaves, split = - std.catfile, std.dirsep, std.insert, std.len, std.leaves, std.split -local ipairs, pairs = std.ipairs, std.pairs -local setmetatable = debug.setmetatable +local argerror, setmetatable = debug.argerror, debug.setmetatable +local ipairs, len, pairs = std.ipairs, std.len, std.pairs +local catfile = std.io.catfile +local dirsep = std.package.dirsep +local split = std.string.split +local insert = std.table.insert +local leaves = std.tree.leaves @@ -73,7 +75,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.io = std.copy (namespace.io or {}, monkeys) + namespace.io = std.base.copy (namespace.io or {}, monkeys) if namespace.io.stdin then local mt = getmetatable (namespace.io.stdin) or {} @@ -274,10 +276,10 @@ M = { } -monkeys = std.copy ({}, M) -- before deprecations and core merge +monkeys = std.base.copy ({}, M) -- before deprecations and core merge -return std.merge (M, io) +return std.base.merge (M, io) diff --git a/lib/std/list.lua b/lib/std/list.lua index 896386f..f358eb8 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -16,10 +16,9 @@ local std = require "std.base" local Object = require "std.object".prototype -local ipairs, pairs = std.ipairs, std.pairs -local len = std.len -local compare = std.compare -local unpack = std.unpack +local ipairs, len, pairs = std.ipairs, std.len, std.pairs +local compare = std.list.compare +local unpack = std.table.unpack local M, List @@ -113,7 +112,7 @@ end local function flatten (l) local r = List {} - for v in std.leaves (ipairs, l) do + for v in std.tree.leaves (ipairs, l) do r[#r + 1] = v end return r @@ -126,7 +125,7 @@ local function foldl (fn, d, t) for i = 2, len (d) do tail[#tail + 1] = d[i] end d, t = d[1], tail end - return std.reduce (fn, d, std.ielems, t) + return std.functional.reduce (fn, d, std.ielems, t) end @@ -136,7 +135,7 @@ local function foldr (fn, d, t) for i = 1, last - 1 do u[#u + 1] = d[i] end d, t = d[last], u end - return std.reduce ( + return std.functional.reduce ( function (x, y) return fn (y, x) end, d, std.ielems, std.ireverse (t)) end @@ -426,7 +425,7 @@ List = Object { } -return std.Module { +return std.object.Module { prototype = List, append = List.append, diff --git a/lib/std/math.lua b/lib/std/math.lua index 7654181..82938aa 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -30,7 +30,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.math = std.copy (namespace.math or {}, M) + namespace.math = std.base.copy (namespace.math or {}, M) return M end @@ -78,4 +78,4 @@ M = { } -return std.merge (M, math) +return std.base.merge (M, math) diff --git a/lib/std/object.lua b/lib/std/object.lua index a79dd1d..94d435d 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -147,7 +147,7 @@ local Object = Container { -- object.mapfields (obj, src, map) -- ... -- end - mapfields = X ( "mapfields (table, table|object, ?table)", std.mapfields), + mapfields = X ("mapfields (table, table|object, ?table)", std.object.mapfields), --- Type of an object, or primitive. -- @@ -226,7 +226,7 @@ local Object = Container { } -return std.Module { +return std.object.Module { prototype = Object, type = Object.type, diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index df6a5ba..e03c6c3 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -16,8 +16,9 @@ local std = require "std.base" local Object = require "std.object".prototype -local ipairs, pairs = std.ipairs, std.pairs -local insert, last, len = std.insert, std.last, std.len +local ipairs, len, pairs = std.ipairs, std.len, std.pairs +local last = std.base.last +local insert = std.table.insert diff --git a/lib/std/package.lua b/lib/std/package.lua index b498151..4281797 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -13,10 +13,10 @@ local std = require "std.base" -local catfile, escape_pattern, invert = - std.catfile, std.escape_pattern, std.invert -local ipairs, pairs, split, unpack = - std.ipairs, std.pairs, std.split, std.unpack +local ipairs, pairs = std.ipairs, std.pairs +local catfile = std.io.catfile +local escape_pattern, split = std.string.escape_pattern, std.string.split +local invert, unpack = std.table.invert, std.table.unpack local M diff --git a/lib/std/set.lua b/lib/std/set.lua index 3b2fa1c..777a59f 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -233,7 +233,7 @@ Set = Container { } -return std.Module { +return std.object.Module { prototype = Set, --- Delete an element from a set. diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 9fe4982..0db4b05 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -26,7 +26,7 @@ local debug = require "std.debug" local Object = require "std.object".prototype -local ielems, insert = std.ielems, std.insert +local ielems, insert = std.ielems, std.table.insert local function __concat (self, x) @@ -125,6 +125,6 @@ local StrBuf = Object { } -return std.Module { +return std.object.Module { prototype = StrBuf, } diff --git a/lib/std/string.lua b/lib/std/string.lua index c2ae211..27dc031 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -15,11 +15,11 @@ local debug = require "std.debug" local StrBuf = require "std.strbuf".prototype -local copy = std.copy -local getmetamethod = std.getmetamethod -local insert, len = std.insert, std.len -local pairs = std.pairs -local render = std.render +local getmetamethod, len, pairs = + std.getmetamethod, std.len, std.pairs +local copy = std.base.copy +local render = std.string.render +local insert = std.table.insert local M @@ -298,7 +298,7 @@ M = { -- @string s any string -- @treturn string *s* with active pattern characters escaped -- @usage substr = inputstr:match (escape_pattern (literal)) - escape_pattern = X ("escape_pattern (string)", std.escape_pattern), + escape_pattern = X ("escape_pattern (string)", std.string.escape_pattern), --- Escape a string to be used as a shell token. -- Quotes spaces, parentheses, brackets, quotes, apostrophes and @@ -436,7 +436,7 @@ M = { -- @string[opt="%s+"] sep separator pattern -- @return list of strings -- @usage words = split "a very short sentence" - split = X ("split (string, ?string)", std.split), + split = X ("split (string, ?string)", std.string.split), --- Do `string.find`, returning a table of captures. -- @function tfind @@ -494,7 +494,7 @@ M.tostring = DEPRECATED ("41", "'std.string.tostring'", -return std.merge (M, string) +return std.base.merge (M, string) diff --git a/lib/std/table.lua b/lib/std/table.lua index 5a30e8d..6c9c9c7 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -17,9 +17,9 @@ local std = require "std.base" local debug = require "std.debug" local argerror = debug.argerror -local collect = std.collect -local leaves = std.leaves local ipairs, pairs = std.ipairs, std.pairs +local collect = std.functional.collect +local leaves = std.tree.leaves local len = std.len @@ -161,7 +161,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.table = std.copy (namespace.table or {}, monkeys) + namespace.table = std.base.copy (namespace.table or {}, monkeys) return M end @@ -280,7 +280,7 @@ M = { -- @usage -- --> {1, "x", 2, 3, "y"} -- insert (insert ({1, 2, 3}, 2, "x"), "y") - insert = X ("insert (table, [int], any)", std.insert), + insert = X ("insert (table, [int], any)", std.table.insert), --- Invert a table. -- @function invert @@ -289,7 +289,7 @@ M = { -- @usage -- --> {a=1, b=2, c=3} -- invert {"a", "b", "c"} - invert = X ("invert (table)", std.invert), + invert = X ("invert (table)", std.table.invert), --- Make the list of keys in table. -- @function keys @@ -307,7 +307,7 @@ M = { -- @usage -- --> 42 -- maxn {"a", b="c", 99, [42]="x", "x", [5]=67} - maxn = X ("maxn (table)", std.maxn), + maxn = X ("maxn (table)", std.table.maxn), --- Destructively merge another table's fields into another. -- @function merge @@ -358,7 +358,7 @@ M = { -- @treturn table ordered list of keys from *t* -- @see keys -- @usage globals = keys (_G) - okeys = X ("okeys (table)", std.okeys), + okeys = X ("okeys (table)", std.table.okeys), --- Turn a tuple into a list. -- @function pack @@ -441,7 +441,7 @@ M = { -- @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)", std.unpack), + unpack = X ("unpack (table, ?int, ?int)", std.table.unpack), --- Make the list of values of a table. -- @function values @@ -455,7 +455,7 @@ M = { } -monkeys = std.copy ({}, M) -- before deprecations and core merge +monkeys = std.base.copy ({}, M) -- before deprecations and core merge --[[ ============= ]]-- @@ -497,7 +497,7 @@ M.totable = DEPRECATED ("41", "'std.table.totable'", -return std.merge (M, table) +return std.base.merge (M, table) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index a43fa1b..8165d23 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -22,10 +22,11 @@ local operator = require "std.operator" local Container = require "std.container".prototype -local ielems, ipairs, leaves, pairs, stdtype = - std.ielems, std.ipairs, std.leaves, std.pairs, std.type -local last, len = std.last, std.len -local reduce = std.reduce +local ielems, ipairs, len, pairs, stdtype = + std.ielems, std.ipairs, std.len, std.pairs, std.type +local last = std.base.last +local reduce = std.functional.reduce +local leaves = std.tree.leaves local Tree -- forward declaration @@ -169,7 +170,7 @@ Tree = Container { } -return std.Module { +return std.object.Module { prototype = Tree, --- Make a deep copy of a tree, including any metatables. diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index f4ec77f..ba7fb7a 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -140,6 +140,6 @@ local Tuple = Container { } -return std.Module { +return std.object.Module { prototype = Tuple, } diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index f861ac8..917cd9b 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -51,7 +51,7 @@ specify std.container: expect (getmetatable (things)._baz).to_be "quux" - context with module functions: - before: - Bag = require "std.base".Module { + Bag = require "std.base".object.Module { prototype = Container { _type = "Bag" }, count = function (bag) local n = 0 diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 00fb422..aa53ed7 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -28,7 +28,7 @@ specify std.string: expect (show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core string table: - apis = require "std.base".copy (extend_base) + apis = require "std.base".base.copy (extend_base) for _, v in ipairs (deprecations) do apis[#apis + 1] = v end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 50095a7..4ec2a5a 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -23,7 +23,7 @@ specify std.table: expect (show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core table table: - apis = require "std.base".copy (extend_base) + apis = require "std.base".base.copy (extend_base) for _, v in ipairs (deprecations) do apis[#apis + 1] = v end From b5f231e1a8633e41c1a28ab19f63725753201a64 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 14 Jun 2015 20:32:18 +0100 Subject: [PATCH 564/703] refactor: restore alphabetical ordering of std functions. * lib/std.lua.in (getmetamethod): Move back into alphabetical order. (__index): Update @usage example. * lib/std/base.lua (eval, require): Move back into alphabetical order. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 18 +++++++++--------- lib/std/base.lua | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index a0048ca..5a312d6 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -126,6 +126,14 @@ M = { -- @usage std.eval "math.min (2, 10)" eval = X ("eval (string)", std.eval), + --- Return named metamethod, if any, otherwise `nil`. + -- @function getmetamethod + -- @param x item to act on + -- @string n name of metamethod to lookup + -- @treturn function|nil metamethod function, or `nil` if no metamethod + -- @usage lookup = std.getmetamethod (require "std.object", "__index") + getmetamethod = X ("getmetamethod (?any, string)", std.getmetamethod), + --- An iterator over the integer keyed elements of a sequence. -- If *t* has a `__len` metamethod, iterate up to the index it returns. -- @function ielems @@ -177,14 +185,6 @@ M = { -- for e in rielems (l) do process (e) end ireverse = X ("ireverse (table)", std.ireverse), - --- Return named metamethod, if any, otherwise `nil`. - -- @function getmetamethod - -- @param x item to act on - -- @string n name of metamethod to lookup - -- @treturn function|nil metamethod function, or `nil` if no metamethod - -- @usage lookup = std.getmetamethod (require "std.object", "__index") - getmetamethod = X ("getmetamethod (?any, string)", std.getmetamethod), - --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. -- @function len -- @tparam object|string|table x operand @@ -307,7 +307,7 @@ return setmetatable (M, { -- `name`, otherwise `nil` if nothing was found -- @usage -- local std = require "std" - -- local objtype = std.object.objtype + -- local Object = std.object.prototype __index = function (self, name) local ok, t = pcall (require, "std." .. name) if ok then diff --git a/lib/std/base.lua b/lib/std/base.lua index 082ec61..a88bf24 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -489,8 +489,8 @@ end -- when importing the functions into stdlib implementation modules. return { assert = assert, - eval = eval, elems = elems, + eval = eval, getmetamethod = getmetamethod, ielems = ielems, ipairs = ipairs, @@ -498,9 +498,9 @@ return { len = len, npairs = npairs, pairs = pairs, + require = require, ripairs = ripairs, rnpairs = rnpairs, - require = require, tostring = tostring, type = function (x) From c2e6dc20c0d81bcc1d66e837512a86bb2202170c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 15 Jun 2015 22:41:11 +0100 Subject: [PATCH 565/703] string: don't use deprecated StrBuf:tostring in string.wrap. * lib/std/string.lua (wrap): Don't use deprecated StrBuf:tostring. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++++ lib/std/string.lua | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index d0e6f0c..513119f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -82,6 +82,10 @@ is a portable stand-in for the core `#` operator, and because it also works on strings, Objects and other traversable types. +### Bug fixes + + - `std.string.wrap` doesn't throw a StrBuf deprecation warning any more. + ### Incompatible changes - Deprecated multi-argument `functional.bind` has been removed. diff --git a/lib/std/string.lua b/lib/std/string.lua index 27dc031..1824c73 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -143,7 +143,7 @@ local function wrap (s, w, ind, ind1) lstart = ind end end - return r:tostring () + return tostring (r) end From a16eedb3ef50d014c7e200368bdede02fa4ba44d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 16 Jun 2015 11:09:13 +0100 Subject: [PATCH 566/703] refactor: move std.len to std.operator.len. In addition to being useful for more than just tables, the len operation is great for functional programming. * specs/std_spec.yaml (len): Move from here... * specs/operator_spec.yaml (len): ...to here. * lib/std.lua.in (len): Move from here... * lib/std/operator (len): ...to here. Adjust all callers. * NEWS.md (Deprecations): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 7 +++---- lib/std.lua.in | 7 ------- lib/std/base.lua | 5 ++++- lib/std/debug.lua | 5 +++-- lib/std/functional.lua | 5 +++-- lib/std/io.lua | 3 ++- lib/std/list.lua | 3 ++- lib/std/operator.lua | 7 +++++++ lib/std/optparse.lua | 3 ++- lib/std/string.lua | 4 ++-- lib/std/table.lua | 4 ++-- lib/std/tree.lua | 5 +++-- lib/std/tuple.lua | 2 +- specs/operator_spec.yaml | 36 ++++++++++++++++++++++++++++++++++++ specs/std_spec.yaml | 40 +--------------------------------------- specs/tuple_spec.yaml | 4 ++-- 16 files changed, 73 insertions(+), 67 deletions(-) diff --git a/NEWS.md b/NEWS.md index 513119f..34bb32c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -66,7 +66,7 @@ - New `operator.eqv` is similar to `operator.eq`, except that it succeeds when recursive table contents are equivalent. - - New `std.len` replaces deprecated `std.table.len`. + - New `std.operator.len` replaces deprecated `std.table.len`. - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. @@ -78,9 +78,8 @@ - `std.object.prototype` has been deprecated in favor of `std.object.type` for orthogonality with `io.type` and `math.type`. - - `std.table.len` has been deprecated in favour of `std.len`, because it - is a portable stand-in for the core `#` operator, and because it also - works on strings, Objects and other traversable types. + - `std.table.len` has been deprecated in favour of `std.operator.len`, + because it is not just for tables! ### Bug fixes diff --git a/lib/std.lua.in b/lib/std.lua.in index 5a312d6..5ad4d0c 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -185,13 +185,6 @@ M = { -- for e in rielems (l) do process (e) end ireverse = X ("ireverse (table)", std.ireverse), - --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. - -- @function len - -- @tparam object|string|table x operand - -- @treturn int length of list part of *t* - -- @usage for i = 1, len (t) do process (t[i]) end - len = X ("len (object|string|table)", std.len), - --- Overwrite core methods and metamethods with `std` enhanced versions. -- -- Write all functions from this module, except `std.barrel` and diff --git a/lib/std/base.lua b/lib/std/base.lua index a88bf24..3f31a32 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -495,7 +495,6 @@ return { ielems = ielems, ipairs = ipairs, ireverse = ireverse, - len = len, npairs = npairs, pairs = pairs, require = require, @@ -539,6 +538,10 @@ return { mapfields = mapfields, }, + operator = { + len = len, + }, + package = { dirsep = dirsep, }, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 566a2df..d002d4d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -34,10 +34,11 @@ local std = require "std.base" local _DEBUG = debug_init._DEBUG -local ipairs, len, pairs, stdtype, tostring = - std.ipairs, std.len, std.pairs, std.type, std.tostring +local ipairs, pairs, stdtype, tostring = + std.ipairs, std.pairs, std.type, std.tostring local copy, last, raise = std.base.copy, std.base.last, std.base.raise local argerror = std.debug.argerror +local len = std.operator.len local split = std.string.split local insert, maxn, unpack = std.table.insert, std.table.maxn, std.table.unpack diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 37eafc2..3a9f45a 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -10,10 +10,11 @@ local std = require "std.base" -local ielems, ipairs, ireverse, len, npairs, pairs = - std.ielems, std.ipairs, std.ireverse, std.len, std.npairs, std.pairs +local ielems, ipairs, ireverse, npairs, pairs = + std.ielems, std.ipairs, std.ireverse, std.npairs, std.pairs local copy = std.base.copy local callable, reduce = std.functional.callable, std.functional.reduce +local len = std.operator.len local unpack = std.table.unpack local loadstring = loadstring or load diff --git a/lib/std/io.lua b/lib/std/io.lua index 4c9d0b3..e055a1f 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -15,8 +15,9 @@ local std = require "std.base" local debug = require "std.debug" local argerror, setmetatable = debug.argerror, debug.setmetatable -local ipairs, len, pairs = std.ipairs, std.len, std.pairs +local ipairs, pairs = std.ipairs, std.pairs local catfile = std.io.catfile +local len = std.operator.len local dirsep = std.package.dirsep local split = std.string.split local insert = std.table.insert diff --git a/lib/std/list.lua b/lib/std/list.lua index f358eb8..6798bc6 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -16,8 +16,9 @@ local std = require "std.base" local Object = require "std.object".prototype -local ipairs, len, pairs = std.ipairs, std.len, std.pairs +local ipairs, pairs = std.ipairs, std.pairs local compare = std.list.compare +local len = std.operator.len local unpack = std.table.unpack local M, List diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 0740c28..545586f 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -62,6 +62,13 @@ local M = { -- functional.foldl (concat, "=> ", {10000, 100, 10}) concat = function (a, b) return tostring (a) .. tostring (b) end, + --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. + -- @function len + -- @tparam object|string|table x operand + -- @treturn int length of list part of *t* + -- @usage for i = 1, len (t) do process (t[i]) end + len = std.operator.len, + --- Dereference a table. -- @tparam table t a table -- @param k a key to lookup in *t* diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index e03c6c3..bd30f5b 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -16,8 +16,9 @@ local std = require "std.base" local Object = require "std.object".prototype -local ipairs, len, pairs = std.ipairs, std.len, std.pairs +local ipairs, pairs = std.ipairs, std.pairs local last = std.base.last +local len = std.operator.len local insert = std.table.insert diff --git a/lib/std/string.lua b/lib/std/string.lua index 1824c73..e1d9907 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -15,9 +15,9 @@ local debug = require "std.debug" local StrBuf = require "std.strbuf".prototype -local getmetamethod, len, pairs = - std.getmetamethod, std.len, std.pairs +local getmetamethod, pairs = std.getmetamethod, std.pairs local copy = std.base.copy +local len = std.operator.len local render = std.string.render local insert = std.table.insert diff --git a/lib/std/table.lua b/lib/std/table.lua index 6c9c9c7..b7e4fa3 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -19,8 +19,8 @@ local debug = require "std.debug" local argerror = debug.argerror local ipairs, pairs = std.ipairs, std.pairs local collect = std.functional.collect +local len = std.operator.len local leaves = std.tree.leaves -local len = std.len local M, monkeys @@ -467,7 +467,7 @@ local DEPRECATED = debug.DEPRECATED M.len = DEPRECATED ("41.3", "'std.table.len'", - "use 'std.len' instead", X ("len (table)", std.len)) + "use 'std.operator.len' instead", X ("len (table)", std.operator.len)) M.metamethod = DEPRECATED ("41", "'std.table.metamethod'", diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 8165d23..f3d83bb 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -22,10 +22,11 @@ local operator = require "std.operator" local Container = require "std.container".prototype -local ielems, ipairs, len, pairs, stdtype = - std.ielems, std.ipairs, std.len, std.pairs, std.type +local ielems, ipairs, pairs, stdtype = + std.ielems, std.ipairs, std.pairs, std.type local last = std.base.last local reduce = std.functional.reduce +local len = std.operator.len local leaves = std.tree.leaves local Tree -- forward declaration diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index ba7fb7a..22f7318 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -109,7 +109,7 @@ local Tuple = Container { -- @usage -- -- Only works on Lua 5.2 or newer: -- #Tuple (nil, 2, nil) --> 3 - -- -- For compatibility with Lua 5.1, use @{std.len} + -- -- For compatibility with Lua 5.1, use @{std.operator.len} -- len (Tuple (nil, 2, nil) __len = function (self) return self.n diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index a359eb2..ac7a1aa 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -27,6 +27,42 @@ specify std.operator: - it concatenates its arguments: expect (f (1, 2)).to_be "12" + +- describe len: + - before: + f = M.len + + - context with string argument: + - it returns the length of a string: + expect (f "").to_be (0) + expect (f "abc").to_be (3) + + - context with table argument: + - it returns the length 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 elements after a hole: + expect (f {1, 2, [5]=3}).to_be (2) + - it respects __len metamethod: + t = setmetatable ({1, 2, [5]=3}, {__len = function () return 42 end}) + expect (f (t)).to_be (42) + + - context with object argument: + - before: + Object = require "std.object" {} + subject = Object {"a", "b", "c"} + + - it returns the length of an object: + expect (f (subject)).to_be (3) + - it works with an empty object: + expect (f (Object)).to_be (0) + - it respects __len metamethod: + expect (f (Object {__len = function () return 42 end})).to_be (42) + expect (f (subject {__len = function () return 42 end})).to_be (42) + + - describe get: - before: f = M.get diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 6a4f050..404dc0a 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -3,7 +3,7 @@ before: | global_table = "_G" exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", - "ielems", "ipairs", "ireverse", "len", "monkey_patch", + "ielems", "ipairs", "ireverse", "monkey_patch", "npairs", "pairs", "require", "ripairs", "rnpairs", "tostring", "type", "version" } @@ -336,44 +336,6 @@ specify std: expect (f {}).to_equal {} -- describe len: - - before: - f = M.len - - - context with bad arguments: - badargs.diagnose (f, "std.len (object|string|table)") - - - context with string argument: - - it returns the length of a string: - expect (f "").to_be (0) - expect (f "abc").to_be (3) - - - context with table argument: - - it returns the length 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 elements after a hole: - expect (f {1, 2, [5]=3}).to_be (2) - - it respects __len metamethod: - t = setmetatable ({1, 2, [5]=3}, {__len = function () return 42 end}) - expect (f (t)).to_be (42) - - - context with object argument: - - before: - Object = require "std.object" {} - subject = Object {"a", "b", "c"} - - - it returns the length of an object: - expect (f (subject)).to_be (3) - - it works with an empty object: - expect (f (Object)).to_be (0) - - it respects __len metamethod: - expect (f (Object {__len = function () return 42 end})).to_be (42) - expect (f (subject {__len = function () return 42 end})).to_be (42) - - - describe monkey_patch: - before: io_mt = {} diff --git a/specs/tuple_spec.yaml b/specs/tuple_spec.yaml index c420489..061f0ea 100644 --- a/specs/tuple_spec.yaml +++ b/specs/tuple_spec.yaml @@ -36,9 +36,9 @@ specify std.tuple: expect (Tuple (nil).n).to_be (1) expect (Tuple (nil, false, nil, nil).n).to_be (4) - - context with std.len: + - context with std.operator.len: - before: - len = require "std".len + len = require "std.operator".len - it returns the number of elements: expect (len (t0)).to_be (0) expect (len (t1)).to_be (1) From 756def078437ede74341b2e80b93a7bd0848f5e0 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Fri, 19 Jun 2015 23:21:24 +0200 Subject: [PATCH 567/703] Update git rockspec --- stdlib-git-1.rockspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index 6ec4919..f13aeb7 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -14,8 +14,8 @@ dependencies = { } external_dependencies = nil build = { - build_command = "LUA='$(LUA)' ./bootstrap && ./configure LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' --prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' --datarootdir='$(PREFIX)' && make clean all", + build_command = "./bootstrap && ./configure LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' --prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' && make clean all", copy_directories = {}, - install_command = "make install luadir='$(LUADIR)' luaexecdir='$(LIBDIR)'", + install_command = "make install luadir='$(LUADIR)'", type = "command", } From c784c68164bf5ba995f18113a77e96a7f5615492 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Sat, 20 Jun 2015 22:07:10 +0200 Subject: [PATCH 568/703] Fix std.require for non-table modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specifically, package.loaded[x] is set to true if the module returns no value and raises no error, and stdlib couldn’t cope. --- lib/std/base.lua | 3 ++- lib/std/debug.lua | 1 + lib/std/string.lua | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 3f31a32..a17284e 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -25,6 +25,7 @@ local dirsep = string.match (package.config, "^(%S+)\n") local loadstring = rawget (_G, "loadstring") or load +local type = type local function raise (bad, to, name, i, extramsg, level) @@ -455,7 +456,7 @@ local _require = require local function require (module, min, too_big, pattern) local m = _require (module) - local v = tostring (m.version or m._VERSION or ""):match (pattern or "([%.%d]+)%D*$") + local v = tostring (type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") if min then assert (vcompare (v, min) >= 0, "require '" .. module .. "' with at least version " .. min .. ", but found version " .. v) diff --git a/lib/std/debug.lua b/lib/std/debug.lua index d002d4d..e12146d 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -42,6 +42,7 @@ local len = std.operator.len local split = std.string.split local insert, maxn, unpack = std.table.insert, std.table.maxn, std.table.unpack +local type = type local M diff --git a/lib/std/string.lua b/lib/std/string.lua index e1d9907..75b32d1 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -20,6 +20,7 @@ local copy = std.base.copy local len = std.operator.len local render = std.string.render local insert = std.table.insert +local type = type -- avoid mutual recursion between debug argument checker and string.__index local M From 31f48411bf3f9518765bba47fe51b4c82db9a286 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 25 Jun 2015 22:20:01 +0100 Subject: [PATCH 569/703] doc: overhaul LDoc organization and content. Close #99 * build-aux/config.ld.in (description): New introduction text. * lib/std.lua.in: Split module indices into groups. * lib/std/container.lua: Be honest that this is the real base prototype. * lib/std/object.lua: Simplify Object documentation accordingly. * lib/std/list.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/tree.lua, lib/std/tuple.lua: Update and enhance. Put an honest inheritence diagram in the header. * lib/std/debug.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/math.lua, lib/std/operator.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua: Update and enhance. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 6 + build-aux/config.ld.in | 78 +++++++++-- lib/std.lua.in | 244 +++++++++++++++++++-------------- lib/std/container.lua | 297 +++++++++++++++++++++++++---------------- lib/std/debug.lua | 86 +++++++----- lib/std/functional.lua | 2 +- lib/std/io.lua | 103 ++++++++------ lib/std/list.lua | 220 +++++++++++++++--------------- lib/std/math.lua | 23 ++-- lib/std/object.lua | 250 ++++++++++------------------------ lib/std/operator.lua | 2 +- lib/std/optparse.lua | 16 ++- lib/std/package.lua | 27 +++- lib/std/set.lua | 221 +++++++++++++++--------------- lib/std/strbuf.lua | 73 +++++----- lib/std/string.lua | 36 +++-- lib/std/table.lua | 231 +++++++++++++++++--------------- lib/std/tree.lua | 80 +++++------ lib/std/tuple.lua | 60 ++++----- 19 files changed, 1110 insertions(+), 945 deletions(-) diff --git a/NEWS.md b/NEWS.md index 34bb32c..bb140d4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,12 @@ ### 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! + - We used to have an object module method, `std.object.type`, which often got imported using: diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 62b9a89..052dd2c 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -1,35 +1,95 @@ -- -*- lua -*- title = "@PACKAGE_STRING@ Reference" project = "@PACKAGE_STRING@" -description = "Standard Lua Libraries" +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}; + +3. A straight forward prototype-based object system, and a selection of + useful objects built on it: @{std.container}, @{std.object}, + @{std.list}, @{std.set}, @{std.strbuf}, @{std.tree} and @{std.tuple}; + +4. A specialized prototype object that reads the help text for a + command-line script, and produces a custom option parser for handling + the options described by that help text: @{std.optparse}; + +5. A foundation for programming in a functional style: @{std.functional} + and @{std.operator}; + +6. A runtime gradual typing system, for typechecking argument and return + types at function boundaries with simple annotations that can be + enabled or disabled for production code, with a Lua API modelled on + the core Lua C language API: also in @{std.debug}; + +7. And an implementation of @{std.strict} to enforce declaration of all + globals prior to use. + +## 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 = "." file = { - -- Modules + -- Core Functions "../lib/std.lua", + + -- Core Libraries "../lib/std/debug.lua", - "../lib/std/functional.lua", "../lib/std/io.lua", "../lib/std/math.lua", - "../lib/std/operator.lua", "../lib/std/package.lua", - "../lib/std/strict.lua", "../lib/std/string.lua", "../lib/std/table.lua", - "../lib/std/tree.lua", - -- Classes + -- Object System "../lib/std/container.lua", "../lib/std/object.lua", "../lib/std/list.lua", - "../lib/std/optparse.lua", "../lib/std/set.lua", "../lib/std/strbuf.lua", + "../lib/std/tree.lua", "../lib/std/tuple.lua", + + -- Functional Style + "../lib/std/functional.lua", + "../lib/std/operator.lua", + + -- Other Modules + "../lib/std/optparse.lua", + "../lib/std/strict.lua", } +new_type ("corefunction", "Core Functions", true) +new_type ("corelibrary", "Core Libraries", true) +new_type ("prototype", "Object System", true) +new_type ("functional", "Functional Style", true) + +function postprocess_html(s) + s = s:gsub("

    %s*Corefunction (.-)

    ", '

    Module %1

    ') + s = s:gsub("

    %s*Corelibrary (.-)

    ", '

    Module %1

    ') + s = s:gsub("

    %s*Prototype (.-)

    ", '

    Module %1

    ') + s = s:gsub("

    %s*Functional (.-)

    ", '

    Module %1

    ') + s = s:gsub('href="core[ _]functions', 'href="core%%20functions') + s = s:gsub('href="core[ _]libraries', 'href="core%%20libraries') + s = s:gsub('href="object[ _]system', 'href="object%%20system') + s = s:gsub('href="functional[ _]style', 'href="functional%%20style') + return s +end + new_type ("object", "Objects", false, "Fields") +new_type ("init", "Initialisation", false, "Parameters") format = "markdown" backtick_references = false -sort = true +sort = false diff --git a/lib/std.lua.in b/lib/std.lua.in index 5ad4d0c..6d2cab1 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -1,10 +1,7 @@ --[[-- - Lua Standard Libraries. + Enhanced Lua core functions, and others. - This module contains a selection of improved Lua core functions, among - others. - - Also, after requiring this module, simply referencing symbols in the + After requiring this module, simply referencing symbols in the submodule hierarchy will load the necessary modules on demand. By default there are no changes to any global symbols, or monkey @@ -12,14 +9,14 @@ still convenient to do that: For example, when using stdlib from the REPL, or in a prototype where you want to throw caution to the wind and compatibility with other modules be damned. In that case, you can give - `stdlib` permission to scribble all over your namespaces by using the + stdlib permission to scribble all over your namespaces by using the various `monkey_patch` calls in the library. @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. - @module std + @corefunction std ]] @@ -71,19 +68,24 @@ end ---- Module table. --- --- In addition to the functions documented on this page, and a `version` --- field, references to other submodule functions will be loaded on --- demand. --- @table std --- @field version release version string +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + local function X (decl, fn) return require "std.debug".argscheck ("std." .. decl, fn) end M = { + --- Release version string. + -- @field version + version = "General Lua libraries / @VERSION@", + + + --- Core Functions + -- @section corefuncs + --- Enhance core `assert` to also allow formatted arguments. -- @function assert -- @param expect expression, expected to be *truthy* @@ -91,22 +93,93 @@ M = { -- @param[opt] ... arguments to format -- @return value of *expect*, if *truthy* -- @usage - -- std.assert (expected ~= nil, "100% unexpected!") - -- std.assert (expected ~= nil, "%s unexpected!", expected) + -- std.assert (expect == nil, "100% unexpected!") + -- std.assert (expect == "expect", "%s the unexpected!", expect) assert = X ("assert (?any, ?string, [any...])", std.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)", std.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)", std.getmetamethod), + + --- Enhance core `tostring` to render table contents as a string. + -- @function tostring + -- @param x object to convert to string + -- @treturn string compact string rendering of *x* + -- @usage + -- -- {1=baz,foo=bar} + -- print (std.tostring {foo="bar","baz"}) + tostring = X ("tostring (?any)", std.tostring), + + --- Type of an object, or primitive. + -- @function type + -- @param x anything + -- @treturn string type of *x* + -- @see std.object.type + type = X ("type (?any)", std.type), + + + --- Module Functions + -- @section modulefuncs + --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). -- - -- Apply **all** `monkey_patch` functions. Additionally, for backwards - -- compatibility only, write a selection of sub-module functions into - -- the given namespace. + -- Apply **all** of stdlib's `monkey_patch` functions to *namespace*. + -- + -- Additionally, for backwards compatibility only, write an historical + -- selection of stdlib submodule functions into the given namespace too + -- (at least until the next major release). -- @function barrel -- @tparam[opt=_G] table namespace where to install global functions -- @treturn table module table -- @usage local std = require "std".barrel () barrel = X ("barrel (?table)", barrel), - --- An iterator over all elements of a sequence. + --- Overwrite core methods and metamethods with `std` enhanced versions. + -- + -- Write all functions from this module, except `std.barrel` and + -- `std.monkey_patch`, into *namespace*. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage local std = require "std".monkey_patch () + monkey_patch = X ("monkey_patch (?table)", monkey_patch), + + --- 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)", std.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 @@ -116,26 +189,19 @@ M = { -- @see ielems -- @see pairs -- @usage - -- for value in std.elems {a = 1, b = 2, c = 5} do process (value) end + -- --> foo + -- --> bar + -- --> baz + -- --> 5 + -- std.functional.map (print, std.ielems, {"foo", "bar", [4]="baz", d=5}) elems = X ("elems (table)", std.elems), - --- Evaluate a string as Lua code. - -- @function eval - -- @string s string of Lua code - -- @return result of evaluating `s` - -- @usage std.eval "math.min (2, 10)" - eval = X ("eval (string)", std.eval), - - --- Return named metamethod, if any, otherwise `nil`. - -- @function getmetamethod - -- @param x item to act on - -- @string n name of metamethod to lookup - -- @treturn function|nil metamethod function, or `nil` if no metamethod - -- @usage lookup = std.getmetamethod (require "std.object", "__index") - getmetamethod = X ("getmetamethod (?any, string)", std.getmetamethod), - - --- An iterator over the integer keyed elements of a sequence. - -- If *t* has a `__len` metamethod, iterate up to the index it returns. + --- 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 @@ -144,15 +210,21 @@ M = { -- @see elems -- @see ipairs -- @usage - -- for v in std.ielems {"a", "b", "c"} do process (v) end + -- --> foo + -- --> bar + -- std.functional.map (print, std.ielems, {"foo", "bar", [4]="baz", d=5}) ielems = X ("ielems (table)", std.ielems), - --- An iterator over elements of a sequence, until the first `nil` value. + --- An iterator over integer keyed pairs of a sequence. -- - -- Like Lua 5.1 and 5.3, but unlike Lua 5.2 (which looks for and uses the - -- `__ipairs` metamethod), this iterator returns successive key-value + -- 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 @@ -162,17 +234,14 @@ M = { -- @see npairs -- @see pairs -- @usage - -- -- length of sequence - -- args = {"first", "second", nil, "last"} - -- --> 1=first - -- --> 2=second - -- for i, v in std.ipairs (args) do - -- print (string.format ("%d=%s", i, v)) - -- end + -- --> 1 foo + -- --> 2 bar + -- std.functional.map (print, std.ipairs, {"foo", "bar", [4]="baz", d=5}) ipairs = X ("ipairs (table)", std.ipairs), - --- Return a new table with element order reversed. - -- Apart from the order of the elments returned, this function follows + --- Return a new sequence with element order reversed. + -- + -- Apart from the order of the elements returned, this function follows -- the same rules as @{ipairs} for determining first and last elements. -- @function ireverse -- @tparam table t a table @@ -182,19 +251,11 @@ M = { -- @see ipairs -- @usage -- local rielems = std.functional.compose (std.ireverse, std.ielems) - -- for e in rielems (l) do process (e) end + -- --> bar + -- --> foo + -- std.functional.map (print, rielems, {"foo", "bar", [4]="baz", d=5}) ireverse = X ("ireverse (table)", std.ireverse), - --- Overwrite core methods and metamethods with `std` enhanced versions. - -- - -- Write all functions from this module, except `std.barrel` and - -- `std.monkey_patch`, into the given namespace. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage local std = require "std".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), - --- Ordered iterator for integer keyed values. -- Like ipairs, but does not stop until the __len or maxn of *t*. -- @function npairs @@ -204,7 +265,11 @@ M = { -- @see ipairs -- @see rnpairs -- @usage - -- for i,v in npairs {"one", nil, "three"} do ... end + -- --> 1 foo + -- --> 2 bar + -- --> 3 nil + -- --> 4 baz + -- std.functional.map (print, std.npairs, {"foo", "bar", [4]="baz", d=5}) npairs = X ("npairs (table)", std.npairs), --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. @@ -216,25 +281,15 @@ M = { -- @see elems -- @see ipairs -- @usage - -- for k, v in pairs {"a", b = "c", foo = 42} do process (k, v) end + -- --> 1 foo + -- --> 2 bar + -- --> 4 baz + -- --> d 5 + -- std.functional.map (print, std.pairs, {"foo", "bar", [4]="baz", d=5}) pairs = X ("pairs (table)", std.pairs), - --- 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)", std.require), - --- An iterator like ipairs, but in reverse. - -- Apart from the order of the elments returned, this function follows + -- 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 @@ -243,11 +298,14 @@ M = { -- @treturn number `#t + 1` -- @see ipairs -- @see rnpairs - -- @usage for i, v = ripairs (t) do ... end + -- @usage + -- --> 2 bar + -- --> 1 foo + -- std.functional.map (print, std.ripairs, {"foo", "bar", [4]="baz", d=5}) ripairs = X ("ripairs (table)", std.ripairs), --- An iterator like npairs, but in reverse. - -- Apart from the order of the elments returned, this function follows + -- 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 @@ -256,26 +314,12 @@ M = { -- @see npairs -- @see ripairs -- @usage - -- for i,v in rnpairs {"one", nil, "three"} do ... end + -- --> 4 baz + -- --> 3 nil + -- --> 2 bar + -- --> 1 foo + -- std.functional.map (print, std.rnpairs, {"foo", "bar", [4]="baz", d=5}) rnpairs = X ("rnpairs (table)", std.rnpairs), - - --- Enhance core `tostring` to render table contents as a string. - -- @function tostring - -- @param x object to convert to string - -- @treturn string compact string rendering of *x* - -- @usage - -- -- {1=baz,foo=bar} - -- print (std.tostring {foo="bar","baz"}) - tostring = X ("tostring (?any)", std.tostring), - - --- Type of an object, or primitive. - -- @function type - -- @param x anything - -- @treturn string type of *x* - -- @see std.object.type - type = X ("type (?any)", std.type), - - version = "General Lua libraries / @VERSION@", } diff --git a/lib/std/container.lua b/lib/std/container.lua index a7ee823..d7edc30 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -1,33 +1,31 @@ --[[-- Container prototype. - A container is a @{std.object} with no methods. It's functionality is - instead defined by its *meta*methods. - - Where an Object uses the `__index` metatable entry to hold object - methods, a Container stores its contents using `__index`, preventing - it from having methods in there too. - - Although there are no actual methods, Containers are free to use - metamethods (`__index`, `__sub`, etc) and, like Objects, can supply - module functions by listing them in `_functions`. Also, since a - @{std.container} is a @{std.object}, it can be passed to the - @{std.object} module functions, or anywhere else a @{std.object} is - expected. - - When making your own prototypes, derive from @{std.container} if you want - to access the contents of your objects with the `[]` operator, or from - @{std.object} if you want to access the functionality of your objects with - named object methods. + This module supplies the root prototype object from which every other + object is descended. There are no classes as such, rather new objects + are created by cloning an existing object, and then changing or adding + to the clone. Further objects can then be made by cloning the changed + object, and so on. + + The functionality of a container based object is entirely defined by its + *meta*methods. However, since we can store *any* object in a container, + we cannot rely on the `__index` metamethod, because it is only a + fallback for when that key is not already in the container itself. Of + course that does not entirely preclude the use of `__index` with + containers, so long as this limitation is observed. + + When making your own prototypes, derive from @{std.container.prototype} + if you want to access the contents of your containers with the `[]` + operator, otherwise from @{std.object.prototype} if you want to access + the functionality of your objects with named object methods. Prototype Chain --------------- table - `-> Object - `-> Container + `-> Container - @classmod std.container + @prototype std.container ]] @@ -47,14 +45,14 @@ local okeys = std.table.okeys --[[ ================= ]]-- --- Instantiate a new object based on *proto*. +--- Instantiate a new object based on *proto*. -- -- This is equivalent to: -- -- table.merge (table.clone (proto), t or {}) -- --- But, not typechecking arguments or checking for metatables, is --- slightly faster. +-- Except that, by not typechecking arguments or checking for metatables, +-- it is slightly faster. -- @tparam table proto base object to copy from -- @tparam[opt={}] table t additional fields to merge in -- @treturn table a new table with fields from proto and t merged in. @@ -81,113 +79,147 @@ end --[[ ================= ]]-- -local function __call (self, ...) - local mt = getmetatable (self) - local obj_mt = mt - local obj = {} - - -- This is the slowest part of cloning for any objects that have - -- a lot of fields to test and copy. - local k, v = next (self) - while (k) do - obj[k] = v - k, v = next (self, k) - end - - if type (mt._init) == "function" then - obj = mt._init (obj, ...) - else - obj = (self.mapfields or mapfields) (obj, (...), mt._init) - end - - -- If a metatable was set, then merge our fields and use it. - if next (getmetatable (obj) or {}) then - obj_mt = instantiate (mt, getmetatable (obj)) - - -- Merge object methods. - if type (obj_mt.__index) == "table" and - type ((mt or {}).__index) == "table" - then - obj_mt.__index = instantiate (mt.__index, obj_mt.__index) - end - end - - return setmetatable (obj, obj_mt) -end - - -local function __tostring (self) - local n, k_ = 1, nil - local buf = { getmetatable (self)._type, " {" } - for _, k in ipairs (okeys (self)) do -- for ordered public members - local v = self[k] +--- Container prototype. +-- @object prototype +-- @string[opt="Container"] _type object name +-- @tfield[opt] table|function _init object initialisation +-- @usage +-- local Container = require "std.container".prototype +-- local Graph = Container { _type = "Graph" } +-- local function nodes (graph) +-- local n = 0 +-- for _ in std.pairs (graph) do n = n + 1 end +-- return n +-- end +-- local g = Graph { "node1", "node2" } +-- assert (nodes (g) == 2) +local prototype = { + _type = "Container", - if k_ ~= nil then -- | buffer separator - if k ~= n and type (k_) == "number" and k_ == n - 1 then - -- `;` separates `v` elements from `k=v` elements - buf[#buf + 1] = "; " - elseif k ~= nil then - -- `,` separator everywhere else - buf[#buf + 1] = ", " - end + --- Metamethods + -- @section metamethods + + --- Return a clone of this container and its metatable. + -- + -- Like any Lua table, a container is essentially a collection of + -- `field_n = value_n` pairs, except that field names beginning with + -- an underscore `_` are usually kept in that container's metatable + -- where they define the behaviour of a container object rather than + -- being part of its actual contents. In general, cloned objects + -- also clone the behaviour of the object they cloned, unless... + -- + -- When calling @{std.container.prototype}, you pass a single table + -- argument with additional fields (and values) to be merged into the + -- clone. Any field names beginning with an underscore `_` are copied + -- to the clone's metatable, and all other fields to the cloned + -- container itself. For instance, you can change the name of the + -- cloned object by setting the `_type` field in the argument table. + -- + -- The `_init` private field is also special: When set to a sequence of + -- field names, unnamed fields in the call argument table are assigned + -- to those field names in subsequent clones, like the example below. + -- + -- Alternatively, you can set the `_init` private field of a cloned + -- container object to a function instead of a sequence, in which case + -- all the arguments passed when *it* is called/cloned (including named + -- and unnamed fields in the initial table argument, if there is one) + -- are passed through to the `_init` function, following the nascent + -- cloned object. See the @{mapfields} usage example below. + -- @function prototype:__call + -- @param ... arguments to prototype's *\_init*, often a single table + -- @treturn prototype clone of this container, with shared or + -- merged metatable as appropriate + -- @usage + -- local Cons = Container {_type="Cons", _init={"car", "cdr"}} + -- local list = Cons {"head", Cons {"tail", nil}} + __call = function (self, ...) + local mt = getmetatable (self) + local obj_mt = mt + local obj = {} + + -- This is the slowest part of cloning for any objects that have + -- a lot of fields to test and copy. + local k, v = next (self) + while (k) do + obj[k] = v + k, v = next (self, k) end - if type (k) == "number" and k == n then -- | buffer key/value pair - -- render initial array-like elements as just `v` - buf[#buf + 1] = tostring (v) - n = n + 1 + if type (mt._init) == "function" then + obj = mt._init (obj, ...) else - -- render remaining elements as `k=v` - buf[#buf + 1] = tostring (k) .. "=" .. tostring (v) + obj = (self.mapfields or mapfields) (obj, (...), mt._init) end - k_ = k -- maintain loop invariant: k_ is previous key - end - buf[#buf + 1] = "}" -- buffer object close + -- If a metatable was set, then merge our fields and use it. + if next (getmetatable (obj) or {}) then + obj_mt = instantiate (mt, getmetatable (obj)) - return table.concat (buf) -- stringify buffer -end + -- Merge object methods. + if type (obj_mt.__index) == "table" and + type ((mt or {}).__index) == "table" + then + obj_mt.__index = instantiate (mt.__index, obj_mt.__index) + end + end + return setmetatable (obj, obj_mt) + end, + + + --- Return a string representation of this object. + -- + -- First the container name, and then between { and } an ordered list + -- of the array elements of the contained values with numeric keys, + -- followed by asciibetically sorted remaining public key-value pairs. + -- + -- This metamethod doesn't recurse explicitly, but relies upon + -- suitable `__tostring` metamethods for non-primitive content objects. + -- @function prototype:__tostring + -- @treturn string stringified object representation + -- @see tostring + -- @usage + -- assert (tostring (list) == 'Cons {car="head", cdr=Cons {car="tail"}}') + __tostring = function (self) + local n, k_ = 1, nil + local buf = { getmetatable (self)._type, " {" } + for _, k in ipairs (okeys (self)) do -- for ordered public members + local v = self[k] + + if k_ ~= nil then -- | buffer separator + if k ~= n and type (k_) == "number" and k_ == n - 1 then + -- `;` separates `v` elements from `k=v` elements + buf[#buf + 1] = "; " + elseif k ~= nil then + -- `,` separator everywhere else + buf[#buf + 1] = ", " + end + end ---- Container prototype. --- --- Container also inherits all the fields and methods from --- @{std.object.Object}. --- @object Container --- @string[opt="Container"] _type object name --- @see std.object --- @see std.object.__call --- @usage --- local std = require "std" --- local Container = std.container {} --- --- local Graph = Container { --- _type = "Graph", --- _functions = { --- nodes = function (graph) --- local n = 0 --- for _ in std.pairs (graph) do n = n + 1 end --- return n --- end, --- }, --- } --- local g = Graph { "node1", "node2" } --- --> 2 --- print (Graph.nodes (g)) + if type (k) == "number" and k == n then -- | buffer key/value pair + -- render initial array-like elements as just `v` + buf[#buf + 1] = tostring (v) + n = n + 1 + else + -- render remaining elements as `k=v` + buf[#buf + 1] = tostring (k) .. "=" .. tostring (v) + end -local Container = { - _type = "Container", + k_ = k -- maintain loop invariant: k_ is previous key + end + buf[#buf + 1] = "}" -- buffer object close - __call = __call, - __tostring = __tostring, + return table.concat (buf) -- stringify buffer + end, } if _DEBUG.argcheck then local argcheck, argerror, extramsg_toomany = debug.argcheck, debug.argerror, debug.extramsg_toomany + local __call = prototype.__call - Container.__call = function (self, ...) + prototype.__call = function (self, ...) local mt = getmetatable (self) -- A function initialised object can be passed arguments of any @@ -195,7 +227,7 @@ if _DEBUG.argcheck then if type (mt._init) ~= "function" then local name, n = mt._type, select ("#", ...) -- Don't count `self` as an argument for error messages, because - -- it just refers back to the object being called: `Container {"x"}. + -- it just refers back to the object being called: `prototype {"x"}. argcheck (name, 1, "table", (...)) if n > 1 then argerror (name, 2, extramsg_toomany ("argument", 1, n), 2) @@ -208,8 +240,41 @@ end return std.object.Module { - prototype = setmetatable ({}, Container), - + prototype = setmetatable ({}, prototype), + + --- Functions + -- @section functions + + --- Return *new* with references to the fields of *src* merged in. + -- + -- This is the function used to instantiate the contents of a newly + -- cloned container, as called by @{__call} above, to split the + -- fields of a @{__call} argument table into private "_" prefixed + -- field namess, -- which are merged into the *new* metatable, and + -- public (everything else) names, which are merged into *new* itself. + -- + -- You might want to use this function from `_init` functions of your + -- own derived containers. + -- @function mapfields + -- @tparam table new partially instantiated clone container + -- @tparam table src @{__call} argument table that triggered cloning + -- @tparam[opt={}] table map key renaming specification in the form + -- `{old_key=new_key, ...}` + -- @treturn table merged public fields from *new* and *src*, with a + -- metatable of private fields (if any), both renamed according to + -- *map* + -- @usage + -- local Bag = Container { + -- _type = "Bag", + -- _init = function (new, ...) + -- if type (...) == "table" then + -- return container.mapfields (new, (...)) + -- end + -- return functional.reduce (operator.set, new, ipairs, {...}) + -- end, + -- } + -- local groceries = Bag ("apple", "banana", "banana") + -- local purse = Bag {_type = "Purse"} ("cards", "cash", "id") mapfields = debug.argscheck ( "std.container.mapfields (table, table|object, ?table)", mapfields), } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index e12146d..8cf6bf4 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -25,13 +25,27 @@ This mitigates almost all of the overhead of argument typechecking in stdlib API functions. - @module std.debug + @corelibrary std.debug ]] local debug_init = require "std.debug_init" local std = require "std.base" +--- Control std.debug function behaviour. +-- To declare debugging state, set _DEBUG either to `false` to disable all +-- runtime debugging; to any "truthy" value (equivalent to enabling everything +-- except *call*, or as documented below. +-- @class table +-- @name _DEBUG +-- @tfield[opt=true] boolean argcheck honor argcheck and argscheck calls +-- @tfield[opt=false] boolean call do call trace debugging +-- @field[opt=nil] deprecate if `false`, deprecated APIs are defined, +-- and do not issue deprecation warnings when used; if `nil` issue a +-- deprecation warning each time a deprecated api is used; any other +-- value causes deprecated APIs not to be defined at all +-- @tfield[opt=1] int level debugging level +-- @usage _DEBUG = { argcheck = false, level = 9 } local _DEBUG = debug_init._DEBUG local ipairs, pairs, stdtype, tostring = @@ -622,6 +636,9 @@ end M = { + --- API Maturity + -- @section maturity + --- Provide a deprecated function definition according to _DEBUG.deprecate. -- You can check whether your covered code uses deprecated functions by -- setting `_DEBUG.deprecate` to `true` before loading any stdlib modules, @@ -647,6 +664,10 @@ M = { -- io.stderr:write (DEPRECATIONMSG ("42", "multi-argument 'module.fname'", 2)) DEPRECATIONMSG = DEPRECATIONMSG, + + --- Gradual Typing + -- @section typing + --- Check the type of an argument against expected types. -- Equivalent to luaL_argcheck in the Lua C API. -- @@ -768,6 +789,7 @@ M = { end, --- Format a too many things error. + -- @function extramsg_toomany -- @string bad the thing there are too many of -- @int expected maximum number of *bad* things expected -- @int actual actual number of *bad* things that triggered the error @@ -780,11 +802,6 @@ M = { -- end extramsg_toomany = extramsg_toomany, - --- Extend `debug.getfenv` to unwrap functables correctly. - -- @tparam int|function|functable fn target function, or stack level - -- @treturn table environment of *fn* - getfenv = getfenv, - --- Compact permutation list into a list of valid types at each argument. -- Eliminate bracketed types by combining all valid types at each position -- for all permutations of *typelist*. @@ -797,23 +814,45 @@ M = { -- Like @{argerror} for bad results. This function does not -- return. The `level` argument behaves just like the core `error` -- function. + -- @function resulterror -- @string name function to callout in error message - -- @int i argument number + -- @int i result number -- @string[opt] extramsg additional text to append to message inside parentheses -- @int[opt=1] level call stack level to blame for the error -- @usage -- local function slurp (file) - -- local h, err = input_handle (file) - -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end -- ... + -- if type (result) ~= "string" then resulterror ("std.io.slurp", 1, err, 2) end resulterror = resulterror, + --- Split a typespec string into a table of normalized type names. + -- @function typesplit + -- @tparam string|table either `"?bool|:nometa"` or `{"boolean", ":nometa"}` + -- @treturn table a new list with duplicates removed and leading "?"s + -- replaced by a "nil" element + typesplit = typesplit, + + + --- 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 `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` @@ -839,12 +878,6 @@ M = { -- local debug = require "std.debug" trace = trace, - --- Split a typespec string into a table of normalized type names. - -- @tparam string|table either `"?bool|:nometa"` or `{"boolean", ":nometa"}` - -- @treturn table a new list with duplicates removed and leading "?"s - -- replaced by a "nil" element - typesplit = typesplit, - -- Private: _setdebug = function (t) @@ -860,8 +893,12 @@ for k, v in pairs (debug) do M[k] = M[k] or v end + +--- Metamethods +-- @section metamethods + --- Equivalent to calling `debug.say (1, ...)` --- @function debug +-- @function __call -- @see say -- @usage -- local debug = require "std.debug" @@ -888,20 +925,3 @@ M.toomanyargmsg = DEPRECATED ("41.2.0", "debug.toomanyargmsg", return setmetatable (M, metatable) - - - ---- Control std.debug function behaviour. --- To declare debugging state, set _DEBUG either to `false` to disable all --- runtime debugging; to any "truthy" value (equivalent to enabling everything --- except *call*, or as documented below. --- @class table --- @name _DEBUG --- @tfield[opt=true] boolean argcheck honor argcheck and argscheck calls --- @tfield[opt=false] boolean call do call trace debugging --- @field[opt=nil] deprecate if `false`, deprecated APIs are defined, --- and do not issue deprecation warnings when used; if `nil` issue a --- deprecation warning each time a deprecated api is used; any other --- value causes deprecated APIs not to be defined at all --- @tfield[opt=1] int level debugging level --- @usage _DEBUG = { argcheck = false, level = 9 } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 3a9f45a..fab1209 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -4,7 +4,7 @@ A selection of higher-order functions to enable a functional style of programming in Lua. - @module std.functional + @functional std.functional ]] diff --git a/lib/std/io.lua b/lib/std/io.lua index e055a1f..2be534a 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -7,7 +7,7 @@ local io = require "std.io" - @module std.io + @corelibrary std.io ]] @@ -145,6 +145,46 @@ 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. @{std.optparse:parse} + -- returns an `opts` table that provides the required `program` + -- field, as long as you assign it back to `_G.opts`. + -- @function warn + -- @string msg format string + -- @param ... additional arguments to plug format string specifiers + -- @see std.optparse:parse + -- @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 @@ -164,18 +204,6 @@ M = { -- @usage filepath = catfile ("relative", "path", "filename") catfile = X ("catfile (string...)", catfile), - --- 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), - --- Remove the last dirsep delimited element from a path. -- @function dirname -- @string path file path @@ -186,6 +214,20 @@ M = { return (path:gsub (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), + + + --- Module Functions + -- @section modulefuncs + --- Overwrite core `io` methods with `std` enhanced versions. -- -- Also adds @{readlines} and @{writelines} metamethods to core file objects. @@ -195,6 +237,10 @@ M = { -- @usage local io = require "std.io".monkey_patch () monkey_patch = X ("monkey_patch (?table)", monkey_patch), + + --- 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 @@ -236,37 +282,6 @@ M = { -- @usage contents = slurp (filename) slurp = X ("slurp (?file|string)", slurp), - --- 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), - - --- 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. @{std.optparse:parse} - -- returns an `opts` table that provides the required `program` - -- field, as long as you assign it back to `_G.opts`. - -- @function warn - -- @string msg format string - -- @param ... additional arguments to plug format string specifiers - -- @see std.optparse:parse - -- @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), - --- Write values adding a newline after each. -- @function writelines -- @tparam[opt=io.output()] file h open writable file handle; diff --git a/lib/std/list.lua b/lib/std/list.lua index 6798bc6..38ff771 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -1,14 +1,19 @@ --[[-- - Tables as lists. + List prototype. + + In addition to the functionality described here, List objects also + have all the methods and metamethods of the @{std.object.prototype} + (except where overridden here), Prototype Chain --------------- table - `-> Object - `-> List + `-> Container + `-> Object + `-> List - @classmod std.list + @prototype std.list ]] @@ -21,7 +26,7 @@ local compare = std.list.compare local len = std.operator.len local unpack = std.table.unpack -local M, List +local M, prototype local function append (l, x) @@ -32,7 +37,7 @@ end local function concat (l, ...) - local r = List {} + local r = prototype {} for _, e in ipairs {l, ...} do for _, v in ipairs (e) do r[#r + 1] = v @@ -43,7 +48,7 @@ end local function rep (l, n) - local r = List {} + local r = prototype {} for i = 1, n do r = concat (r, l) end @@ -52,7 +57,7 @@ end local function sub (l, from, to) - local r = List {} + local r = prototype {} local lenl = len (l) from = from or 1 to = to or lenl @@ -92,16 +97,16 @@ end local function enpair (t) - local ls = List {} + local ls = prototype {} for i, v in pairs (t) do - ls[#ls + 1] = List {i, v} + ls[#ls + 1] = prototype {i, v} end return ls end local function filter (pfn, l) - local r = List {} + local r = prototype {} for _, e in ipairs (l) do if pfn (e) then r[#r + 1] = e @@ -112,7 +117,7 @@ end local function flatten (l) - local r = List {} + local r = prototype {} for v in std.tree.leaves (ipairs, l) do r[#r + 1] = v end @@ -166,7 +171,7 @@ end local function map (fn, l) - local r = List {} + local r = prototype {} for _, e in ipairs (l) do local v = fn (e) if v ~= nil then @@ -190,7 +195,7 @@ end local function relems (l) return std.ielems (std.ireverse (l)) end -local function reverse (l) return List (std.ireverse (l)) end +local function reverse (l) return prototype (std.ireverse (l)) end local function shape (s, l) @@ -216,7 +221,7 @@ local function shape (s, l) if d > len (s) then return l[i], i + 1 else - local r = List {} + local r = prototype {} for j = 1, s[d] do local e e, i = fill (i, d + 1) @@ -230,10 +235,10 @@ end local function transpose (ls) - local rs, lenls, dims = List {}, len (ls), map (len, ls) + local rs, lenls, dims = prototype {}, len (ls), map (len, ls) if len (dims) > 0 then for i = 1, math.max (unpack (dims)) do - rs[i] = List {} + rs[i] = prototype {} for j = 1, lenls do rs[i][j] = ls[j][i] end @@ -259,91 +264,120 @@ local function X (decl, fn) end ---- An Object derived List. --- @object List - -List = Object { - -- Derived object type. +--- List prototype object. +-- @object prototype +-- @string[opt="List"] _type object name +-- @tfield[opt] table|function _init object initialisation +-- @see std.object.prototype +-- @usage +-- local List = require "std.list".prototype +-- assert (std.type (List) == "List") +prototype = Object { _type = "List", + --- Metamethods + -- @section metamethods + + --- Concatenate lists. + -- @function prototype:__concat + -- @tparam prototype|table m another list, or table (hash part is ignored) + -- @see concat + -- @usage + -- new = alist .. {"append", "these", "elements"} + __concat = concat, + + --- Append element to list. + -- @function prototype:__add + -- @param e element to append + -- @see append + -- @usage + -- list = list + "element" + __add = append, + + --- List order operator. + -- @function prototype:__lt + -- @tparam prototype m another list + -- @see compare + -- @usage + -- max = list1 > list2 and list1 or list2 + __lt = function (list1, list2) return compare (list1, list2) < 0 end, + + --- List equality or order operator. + -- @function prototype:__le + -- @tparam prototype m another list + -- @see compare + -- @usage + -- min = list1 <= list2 and list1 or list2 + __le = function (list1, list2) return compare (list1, list2) <= 0 end, + __index = { + --- Methods + -- @section methods + --- Append an item to a list. - -- @static - -- @function append - -- @tparam List l a list + -- @function prototype:append -- @param x item - -- @treturn List new list with *x* appended + -- @treturn prototype new list with *x* appended -- @usage - -- longer = append (short, "last") + -- --> List {"shorter", "longer"} + -- longer = (List {"shorter"}):append "longer" append = X ("append (List, any)", append), --- Compare two lists element-by-element, from left-to-right. - -- @static - -- @function compare - -- @tparam List l a list - -- @tparam List|table m another list, or table + -- @function prototype:compare + -- @tparam prototype|table m another list, or table -- @return -1 if *l* is less than *m*, 0 if they are the same, and 1 -- if *l* is greater than *m* -- @usage - -- if a_list:compare (another_list) == 0 then print "same" end + -- if list1:compare (list2) == 0 then print "same" end compare = X ("compare (List, List|table)", compare), --- Concatenate the elements from any number of lists. - -- @static - -- @function concat - -- @tparam List l a list - -- @param ... tuple of lists - -- @treturn List new list with elements from arguments + -- @function prototype:concat + -- @tparam prototype|table ... additional lists, or list-like tables + -- @treturn prototype new list with elements from arguments -- @usage - -- --> {1, 2, 3, {4, 5}, 6, 7} - -- list.concat ({1, 2, 3}, {{4, 5}, 6, 7}) + -- --> List {"shorter", "short", "longer", "longest"} + -- longest = (List {"shorter"}):concat ({"short", "longer"}, {"longest"}) concat = X ("concat (List, List|table...)", concat), --- Prepend an item to a list. - -- @static - -- @function cons - -- @tparam List l a list + -- @function prototype:cons -- @param x item - -- @treturn List new list with *x* followed by elements of *l* + -- @treturn prototype new list with *x* followed by elements of *l* -- @usage - -- --> {"x", 1, 2, 3} - -- list.cons ({1, 2, 3}, "x") - cons = X ("cons (List, any)", function (l, x) return List {x, unpack (l)} end), + -- --> List {"x", 1, 2, 3} + -- consed = (List {1, 2, 3}):cons "x" + cons = X ("cons (List, any)", function (l, x) return prototype {x, unpack (l)} end), --- Repeat a list. - -- @static - -- @function rep - -- @tparam List l a list + -- @function prototype:rep -- @int n number of times to repeat - -- @treturn List *n* copies of *l* appended together + -- @treturn prototype *n* copies of *l* appended together -- @usage - -- --> {1, 2, 3, 1, 2, 3, 1, 2, 3} - -- list.rep ({1, 2, 3}, 3) + -- --> List {1, 2, 3, 1, 2, 3, 1, 2, 3} + -- repped = (List {1, 2, 3}):rep (3) rep = X ("rep (List, int)", rep), --- Return a sub-range of a list. -- (The equivalent of @{string.sub} on strings; negative list indices -- count from the end of the list.) - -- @static - -- @function sub - -- @tparam List l a list + -- @function prototype:sub -- @int[opt=1] from start of range -- @int[opt=#l] to end of range - -- @treturn List new list containing elements between *from* and *to* + -- @treturn prototype new list containing elements between *from* and *to* -- inclusive -- @usage - -- --> {3, 4, 5} - -- list.sub ({1, 2, 3, 4, 5, 6}, 3, 5) + -- --> List {3, 4, 5} + -- subbed = (List {1, 2, 3, 4, 5, 6}):sub (3, 5) sub = X ("sub (List, ?int, ?int)", sub), --- Return a list with its first element removed. - -- @static - -- @function tail - -- @tparam List l a list - -- @treturn List new list with all but the first element of *l* + -- @function prototype:tail + -- @treturn prototype new list with all but the first element of *l* -- @usage - -- --> {3, {4, 5}, 6, 7} - -- list.tail {{1, 2}, 3, {4, 5}, 6, 7} + -- --> List {3, {4, 5}, 6, 7} + -- tailed = (List {{1, 2}, 3, {4, 5}, 6, 7}):tail () tail = X ("tail (List)", function (l) return sub (l, 2) end), enpair = DEPRECATED ("41", "'std.list:enpair'", enpair), @@ -383,59 +417,19 @@ List = Object { "use 'std.table.shape' instead", function (t, l) return shape (l, t) end), }, - - ------ - -- Concatenate lists. - -- @function __concat - -- @tparam List l a list - -- @tparam List|table m another list, or table (hash part is ignored) - -- @see concat - -- @usage - -- new = alist .. {"append", "these", "elements"} - __concat = concat, - - ------ - -- Append element to list. - -- @function __add - -- @tparam List l a list - -- @param e element to append - -- @see append - -- @usage - -- list = list + "element" - __add = append, - - ------ - -- List order operator. - -- @function __lt - -- @tparam List l a list - -- @tparam List m another list - -- @see compare - -- @usage - -- max = list1 > list2 and list1 or list2 - __lt = function (list1, list2) return compare (list1, list2) < 0 end, - - ------ - -- List equality or order operator. - -- @function __le - -- @tparam List l a list - -- @tparam List m another list - -- @see compare - -- @usage - -- min = list1 <= list2 and list1 or list2 - __le = function (list1, list2) return compare (list1, list2) <= 0 end, } return std.object.Module { - prototype = List, - - append = List.append, - compare = List.compare, - concat = List.concat, - cons = List.cons, - rep = List.rep, - sub = List.sub, - tail = List.tail, + prototype = prototype, + + append = prototype.append, + compare = prototype.compare, + concat = prototype.concat, + cons = prototype.cons, + rep = prototype.rep, + sub = prototype.sub, + tail = prototype.tail, depair = DEPRECATED ("41", "'std.list.depair'", depair), enpair = DEPRECATED ("41", "'std.list.enpair'", enpair), diff --git a/lib/std/math.lua b/lib/std/math.lua index 82938aa..3ca4a91 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -7,7 +7,7 @@ local math = require "std.math" - @module std.math + @corelibrary std.math ]] @@ -53,6 +53,9 @@ end M = { + --- Core Functions + -- @section corefuncs + --- Extend `math.floor` to take the number of decimal places. -- @function floor -- @number n number @@ -61,13 +64,6 @@ M = { -- @usage tenths = floor (magnitude, 1) floor = X ("floor (number, ?int)", floor), - --- Overwrite core `math` methods with `std` enhanced versions. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage require "std.math".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), - --- Round a number to a given number of decimal places -- @function round -- @number n number @@ -75,6 +71,17 @@ M = { -- @treturn number `n` rounded to `p` decimal places -- @usage roughly = round (exactly, 2) round = X ("round (number, ?int)", round), + + + --- Module Functions + -- @section modulefuncs + + --- Overwrite core `math` methods with `std` enhanced versions. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage require "std.math".monkey_patch () + monkey_patch = X ("monkey_patch (?table)", monkey_patch), } diff --git a/lib/std/object.lua b/lib/std/object.lua index 94d435d..1fe44c2 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -1,35 +1,27 @@ --[[-- - Prototype-based objects. + Object prototype. - This module creates the root prototype object from which every other - object is descended. There are no classes as such, rather new objects - are created by cloning an existing object, and then changing or adding - to the clone. Further objects can then be made by cloning the changed - object, and so on. + This module provides a specialization of the @{std.container.prototype} + with the addition of object methods. In addition to the functionality + described here, object prototypes also have all the methods and + metamethods of the @{std.container.prototype}. - Objects are cloned by simply calling an existing object, which then - serves as a prototype from which the new object is copied. - - Note that Object methods are stored in the `__index` field of their - metatable, and so cannot also use `__index` to lookup references with - square brackets. See @{std.container} objects if you want to do that. + Note that object methods are stored in the `__index` field of their + metatable, and so cannot also use the `__index` metamethod to lookup + references with square brackets. Use a @{std.container.prototype} based + object if you want to do that. Prototype Chain --------------- table - `-> Object + `-> Container + `-> Object - @classmod std.object + @prototype std.object ]] --- Surprise!! The real root object is Container, which has less --- functionality than Object, but that makes the heirarchy hard to --- explain, so the documentation pretends this is the root object, and --- Container is derived from it. Confused? ;-) - - local container = require "std.container" local debug = require "std.debug" local std = require "std.base" @@ -42,19 +34,12 @@ local function X (decl, fn) end ---- Root object. --- --- Changing the values of these fields in a new object will change the --- corresponding behaviour. --- @object Object +--- Object prototype. +-- @object prototype -- @string[opt="Object"] _type object name --- @tfield[opt={}] table|function _init object initialisation --- @tfield table _functions module functions omitted when cloned --- @see __call +-- @tfield[opt] table|function _init object initialisation -- @usage --- -- `_init` can be a list of keys; then the unnamed `init_1` through --- -- `init_m` values from the argument table are assigned to the --- -- corresponding keys in `new_object`. +-- local Object = require "std.object".prototype -- local Process = Object { -- _type = "Process", -- _init = { "status", "out", "err" }, @@ -63,171 +48,80 @@ end -- procs[pid].status, procs[pid].out, procs[pid].err, -- auto assigned -- command = pipeline[pid], -- manual assignment -- } --- @usage --- -- Or it can be a function, in which the arguments passed to the --- -- prototype during cloning are simply handed to the `_init` function. --- local Bag = Object { --- _type = "Bag", --- _init = function (obj, ...) --- for e in std.elems {...} do --- obj[#obj + 1] = e --- end --- return obj --- end, --- } --- local bag = Bag ("function", "arguments", "sent", "to", "_init") - -local Object = Container { +local prototype = Container { _type = "Object", + --- Metamethods + -- @section metamethods + + --- Return an in-order iterator over public object fields. + -- @function prototype:__pairs + -- @treturn function iterator function + -- @treturn Object *self* + -- @usage + -- for k, v in std.pairs (anobject) do process (k, v) end + __index = { - --- Clone an Object. - -- - -- Objects are essentially tables of `field_n = value_n` pairs. - -- - -- Normally `new_object` automatically shares a metatable with - -- `proto_object`. However, field names beginning with "_" are *private*, - -- and moved into the object metatable during cloning. So, adding new - -- private fields to an object during cloning will result in a new - -- metatable for `new_object` that also happens to contain a copy of all - -- the entries from the `proto_object` metatable. + --- Methods + -- @section methods + + --- Return a clone of this object and its metatable. -- - -- While clones of @{Object} inherit all properties of their prototype, - -- it's idiomatic to always keep separate tables for the module table and - -- the root object itself: That way you can't mistakenly engage the slower - -- clone-from-module-table process unnecessarily. - -- @static - -- @function clone - -- @tparam Object obj an object - -- @param ... a list of arguments if *obj.\_init* is a function, or a - -- single table if *obj.\_init* is a table. - -- @treturn Object a clone of *obj* - -- @see __call + -- This function is useful if you need to override the normal use of + -- the `__call` metamethod for object cloning, without losing the + -- ability to clone an object. + -- @function prototype:clone + -- @param ... arguments to prototype's *\_init*, often a single table + -- @treturn prototype a clone of this object, with shared or merged + -- metatable as appropriate + -- @see std.container.__call -- @usage - -- local object = require "std.object" -- module table - -- local Object = object {} -- root object - -- local o = Object { - -- field_1 = "value_1", - -- method_1 = function (self) return self.field_1 end, + -- local Node = Object { _type = "Node" } + -- -- A trivial FSA to recognize powers of 10, either "0" or a "1" + -- -- followed by zero or more "0"s can transition to state 'finish' + -- local states; states = { + -- start = Node { ["1"] = states[1], ["0"] = states.finish }, + -- [1] = Node { ["0"] = states[1], [""] = states.finish }, + -- finish = Node {}, -- } - -- print (o.field_1) --> value_1 - -- o.field_2 = 2 - -- function o:method_2 (n) return self.field_2 + n end - -- print (o:method_2 (2)) --> 4 - -- os.exit (0) clone = std.getmetamethod (Container, "__call"), - --- Return *obj* with references to the fields of *src* merged in. - -- - -- More importantly, split the fields in *src* between *obj* and its - -- metatable. If any field names begin with "_", attach a metatable - -- to *obj* by cloning the metatable from *src*, and then copy the - -- "private" `_` prefixed fields there. - -- - -- You might want to use this function to instantiate your derived - -- object clones when the *src.\_init* is a function -- when - -- *src.\_init* is a table, the default (inherited unless you overwrite - -- it) clone method calls @{mapfields} automatically. When you're - -- using a function `_init` setting, @{clone} doesn't know what to - -- copy into a new object from the `_init` function's arguments... - -- so you're on your own. Except that calling @{mapfields} inside - -- `_init` is safer than manually splitting `src` into `obj` and - -- its metatable, because you'll pick up any fixes and changes when - -- you upgrade stdlib. - -- @static - -- @function mapfields - -- @tparam table obj destination object - -- @tparam table src fields to copy int clone - -- @tparam[opt={}] table map key renames as `{old_key=new_key, ...}` - -- @treturn table *obj* with non-private fields from *src* merged, - -- and a metatable with private fields (if any) merged, both sets - -- of keys renamed according to *map* - -- @usage - -- myobject.mapfields = function (obj, src, map) - -- object.mapfields (obj, src, map) - -- ... - -- end - mapfields = X ("mapfields (table, table|object, ?table)", std.object.mapfields), - - --- Type of an object, or primitive. - -- - -- It's conventional to organise similar objects according to a - -- string valued *\_type* field, which can then be queried using this - -- method. - -- - -- Additionally, this function returns the results of @{io.type} for - -- file objects, or @{type} otherwise. - -- - -- @static - -- @function type - -- @param x anything - -- @treturn string type of *x* + --- Type of this object. + -- @function prototype:type + -- @treturn string type of this object. + -- @see std.type -- @usage - -- local Stack = Object { - -- _type = "Stack", - -- - -- __tostring = function (self) ... end, - -- - -- __index = { - -- push = function (self) ... end, - -- pop = function (self) ... end, - -- }, - -- } - -- local stack = Stack {} - -- assert (stack:type () == getmetatable (stack)._type) - -- - -- local objtype = Object.type - -- assert (objtype (stack) == getmetatable (stack)._type) - -- - -- local h = io.open (os.tmpname (), "w") - -- assert (objtype (h) == io.type (h)) - -- - -- assert (type {} == type {}) + -- assert (Object:type () == getmetatable (Object)._type) type = X ("type (?any)", std.type), + --- Object Functions + -- @section objfunctions + + --- Return *new* with references to the fields of *src* merged in. + -- + -- You can change the value of this function in an object, and that + -- new function will be called during cloning instead of the + -- standard @{std.container.mapfields} implementation. + -- @function prototype.mapfields + -- @tparam table new partially instantiated clone container + -- @tparam table src @{clone} argument table that triggered cloning + -- @tparam[opt={}] table map key renaming specification in the form + -- `{old_key=new_key, ...}` + -- @treturn table merged public fields from *new* and *src*, with a + -- metatable of private fields (if any), both renamed according to + -- *map* + -- @see std.container.mapfields + mapfields = X ("mapfields (table, table|object, ?table)", std.object.mapfields), + -- Backwards compatibility: prototype = debug.DEPRECATED ("41.3", "'std.object.prototype'", std.type), }, - - - --- Return a @{clone} of this object, and its metatable. - -- - -- Private fields are stored in the metatable. - -- @function __call - -- @param ... arguments for prototype's *\_init* - -- @treturn Object a clone of the this object. - -- @see clone - -- @usage - -- local Object = require "std.object" {} -- not a typo! - -- new = Object {"initialisation", "elements"} - - - --- Return an in-order iterator over public object fields. - -- @function __pairs - -- @treturn function iterator function - -- @treturn Object *self* - -- @usage - -- for k, v in std.pairs (anobject) do process (k, v) end - - - --- Return a string representation of this object. - -- - -- First the object type, and then between { and } a list of the - -- array part of the object table (without numeric keys) followed - -- by the remaining key-value pairs. - -- - -- This function doesn't recurse explicity, but relies upon suitable - -- `__tostring` metamethods in field values. - -- @function __tostring - -- @treturn string stringified object representation - -- @see tostring - -- @usage print (anobject) } return std.object.Module { - prototype = Object, + prototype = prototype, - type = Object.type, + type = debug.DEPRECATED ("41.3", "'std.object.type'", std.type), } diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 545586f..b73df29 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -1,7 +1,7 @@ --[[-- Functional forms of Lua operators. - @module std.operator + @functional std.operator ]] local std = require "std.base" diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index bd30f5b..d9fe925 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -1,14 +1,24 @@ --[=[-- Parse and process command line options. + In the common case, you can write the long-form help output typical of + a modern-command line program, and let this module generate a custom + parser that collects and diagnoses the options it describes. + + The parser is a @{std.object} instance which can then be tweaked for + the uncommon case, by hand, or by using the @{on} function to tie your + custom handlers to options that are not handled quite the way you'd + like. + Prototype Chain --------------- table - `-> Object - `-> OptionParser + `-> Container + `-> Object + `-> OptionParser - @classmod std.optparse + @module std.optparse ]=] diff --git a/lib/std/package.lua b/lib/std/package.lua index 4281797..89f3d9b 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -7,7 +7,32 @@ local package = require "std.package" - @module 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: + + 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: + + package.path = package.normalize ("./lib/?.lua", package.path) + + @corelibrary std.package ]] diff --git a/lib/std/set.lua b/lib/std/set.lua index 777a59f..2ee588f 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -1,20 +1,24 @@ --[[-- Set container prototype. - Note that Functions listed below are only available from the Set - prototype returned by requiring this module, because Container - objects cannot have object methods. + This module returns a table of set operators, as well as the prototype + for a Set container object. + + Every possible object or primitive value is always present in any Set + container exactly zero or one times. + + In addition to the functionality described here, Set containers also + have all the methods and metamethods of the @{std.container.prototype} + (except where overridden here), Prototype Chain --------------- table - `-> Object - `-> Container - `-> Set + `-> Container + `-> Set - @classmod std.set - @see std.container + @prototype std.set ]] local std = require "std.base" @@ -23,7 +27,7 @@ local Container = require "std.container".prototype local ielems, pairs, type = std.ielems, std.pairs, std.type -local Set -- forward declaration +local prototype -- forward declaration @@ -64,7 +68,7 @@ local difference, symmetric_difference, intersection, union, subset, function difference (set1, set2) - local r = Set {} + local r = prototype {} for e in elems (set1) do if not member (set2, e) then insert (r, e) @@ -80,7 +84,7 @@ end function intersection (set1, set2) - local r = Set {} + local r = prototype {} for e in elems (set1) do if member (set2, e) then insert (r, e) @@ -131,159 +135,152 @@ end --- Set prototype object. --- --- Set also inherits all the fields and methods from --- @{std.container.Container}. --- @object Set +-- @object prototype -- @string[opt="Set"] _type object name --- @see std.container --- @see std.object.__call +-- @see std.container.prototype -- @usage --- local std = require "std" --- std.type (std.set) --> "Set" --- os.exit (0) -Set = Container { +-- local Set = require "std.set".prototype +-- assert (std.type (Set) == "Set") +prototype = Container { _type = "Set", - _init = function (self, t) - for e in ielems (t) do - insert (self, e) - end - return self - end, + --- Set object initialisation. + -- + -- Returns table partially initialised Set container with contents + -- from *t*. + -- @init prototype._init + -- @tparam table new uninitialised Set container object + -- @tparam table t initialisation table from `__call` + _init = function (new, t) + for e in ielems (t) do + insert (new, e) + end + return new + end, + + --- Metamethods + -- @section metamethods - --- Union operator. - -- @static - -- @function __add - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set everything from *set1* plus everything from *set2* + --- Union operation. + -- @function prototype:__add + -- @tparam prototype s another set + -- @treturn prototype everything from *this* set plus everything from *s* -- @see union -- @usage - -- union = set1 + set2 + -- union = this + s __add = union, - --- Difference operator. - -- @static - -- @function __sub - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set everything from *set1* that is not also in *set2* + --- Difference operation. + -- @function prototype:__sub + -- @tparam prototype s another set + -- @treturn prototype everything from *this* set that is not also in *s* -- @see difference -- @usage - -- difference = set1 - set2 + -- difference = this - s __sub = difference, - --- Intersection operator. - -- @static - -- @function __mul - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set anything this is in both *set1* and *set2* + --- Intersection operation. + -- @function prototype:__mul + -- @tparam prototype s another set + -- @treturn prototype anything in both *this* set and in *s* -- @see intersection -- @usage - -- intersection = set1 * set2 + -- intersection = this * s __mul = intersection, - --- Symmetric difference operator. - -- @function __div - -- @static - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set everything from *set1* or *set2* but not both + --- Symmetric difference operation. + -- @function prototype:__div + -- @tparam prototype s another set + -- @treturn prototype everything in *this* set or in *s* but not in both -- @see symmetric_difference -- @usage - -- symmetric_difference = set1 / set2 + -- symmetric_difference = this / s __div = symmetric_difference, - --- Subset operator. + --- Subset operation. -- @static - -- @function __le - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn boolean `true` if everything in *set1* is also in *set2* + -- @function prototype:__le + -- @tparam prototype s another set + -- @treturn boolean `true` if everything in *this* set is also in *s* -- @see subset -- @usage - -- issubset = set1 <= set2 + -- issubset = this <= s __le = subset, - --- Proper subset operator. - -- @static - -- @function __lt - -- @tparam Set set1 set - -- @tparam Set set2 another set - -- @treturn boolean `true` if *set2* is not equal to *set1*, but does - -- contain everything from *set1* + --- Proper subset operation. + -- @function prototype:__lt + -- @tparam prototype s another set + -- @treturn boolean `true` if *s* is not equal to *this* set, but does + -- contain everything from *this* set -- @see proper_subset -- @usage - -- ispropersubset = set1 < set2 + -- ispropersubset = this < s __lt = proper_subset, -- Return a string representation of this set. + -- @function prototype:__tostring -- @treturn string string representation of a set. -- @see std.tostring __tostring = function (self) - local keys = {} - for k in pairs (self) do - keys[#keys + 1] = tostring (k) - end - table.sort (keys) - return type (self) .. " {" .. table.concat (keys, ", ") .. "}" - end, + local keys = {} + for k in pairs (self) do + keys[#keys + 1] = tostring (k) + end + table.sort (keys) + return type (self) .. " {" .. table.concat (keys, ", ") .. "}" + end, } return std.object.Module { - prototype = Set, + prototype = prototype, + + --- Functions + -- @section functions --- Delete an element from a set. - -- @static -- @function delete - -- @tparam Set set a set + -- @tparam prototype set a set -- @param e element - -- @treturn Set the modified *set* + -- @treturn prototype the modified *set* -- @usage -- set.delete (available, found) delete = X ("delete (Set, any)", function (set, e) return rawset (set, e, nil) end), --- Find the difference of two sets. - -- @static -- @function difference - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set a copy of *set1* with elements of *set2* removed + -- @tparam prototype set1 a set + -- @tparam prototype set2 another set + -- @treturn prototype a copy of *set1* with elements of *set2* removed -- @usage - -- all = set.difference (all, {32, 49, 56}) + -- all = set.difference (all, Set {32, 49, 56}) difference = X ("difference (Set, Set)", difference), --- Iterator for sets. - -- @static -- @function elems - -- @tparam Set set a set - -- @treturn *set* iterator + -- @tparam prototype set a set + -- @return *set* iterator -- @todo Make the iterator return only the key -- @usage -- for code in set.elems (isprintable) do print (code) end elems = X ("elems (Set)", elems), --- Find whether two sets are equal. - -- @static -- @function equal - -- @tparam Set set1 a set - -- @tparam Set set2 another set + -- @tparam prototype set1 a set + -- @tparam prototype set2 another set -- @treturn boolean `true` if *set1* and *set2* each contain identical -- elements, `false` otherwise -- @usage - -- if set.equal (keys, {META, CTRL, "x"}) then process (keys) end + -- if set.equal (keys, Set {META, CTRL, "x"}) then process (keys) end equal = X ( "equal (Set, Set)", equal), --- Insert an element into a set. - -- @static -- @function insert - -- @tparam Set set a set + -- @tparam prototype set a set -- @param e element - -- @treturn Set the modified *set* + -- @treturn prototype the modified *set* -- @usage -- for byte = 32,126 do -- set.insert (isprintable, string.char (byte)) @@ -291,19 +288,17 @@ return std.object.Module { insert = X ("insert (Set, any)", insert), --- Find the intersection of two sets. - -- @static -- @function intersection - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set a new set with elements in both *set1* and *set2* + -- @tparam prototype set1 a set + -- @tparam prototype set2 another set + -- @treturn prototype a new set with elements in both *set1* and *set2* -- @usage -- common = set.intersection (a, b) intersection = X ("intersection (Set, Set)", intersection), --- Say whether an element is in a set. - -- @static -- @function difference - -- @tparam Set set a set + -- @tparam prototype set a set -- @param e element -- @return `true` if *e* is in *set*, otherwise `false` -- otherwise @@ -312,10 +307,9 @@ return std.object.Module { member = X ("member (Set, any)", member), --- Find whether one set is a proper subset of another. - -- @static -- @function proper_subset - -- @tparam Set set1 a set - -- @tparam Set set2 another set + -- @tparam prototype set1 a set + -- @tparam prototype set2 another set -- @treturn boolean `true` if *set2* contains all elements in *set1* -- but not only those elements, `false` otherwise -- @usage @@ -328,10 +322,9 @@ return std.object.Module { proper_subset = X ("proper_subset (Set, Set)", proper_subset), --- Find whether one set is a subset of another. - -- @static -- @function subset - -- @tparam Set set1 a set - -- @tparam Set set2 another set + -- @tparam prototype set1 a set + -- @tparam prototype set2 another set -- @treturn boolean `true` if all elements in *set1* are also in *set2*, -- `false` otherwise -- @usage @@ -339,11 +332,10 @@ return std.object.Module { subset = X ("subset (Set, Set)", subset), --- Find the symmetric difference of two sets. - -- @static -- @function symmetric_difference - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set a new set with elements that are in *set1* or *set2* + -- @tparam prototype set1 a set + -- @tparam prototype set2 another set + -- @treturn prototype a new set with elements that are in *set1* or *set2* -- but not both -- @usage -- unique = set.symmetric_difference (a, b) @@ -351,11 +343,10 @@ return std.object.Module { symmetric_difference), --- Find the union of two sets. - -- @static -- @function union - -- @tparam Set set1 a set - -- @tparam Set set2 another set - -- @treturn Set a copy of *set1* with elements in *set2* merged in + -- @tparam prototype set1 a set + -- @tparam prototype set2 another set + -- @treturn prototype a copy of *set1* with elements in *set2* merged in -- @usage -- all = set.union (a, b) union = X ("union (Set, Set)", union), diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 0db4b05..999e423 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -1,24 +1,29 @@ --[[-- - String buffers. + String buffer prototype. Buffers are mutable by default, but being based on objects, they can also be used in a functional style: - local StrBuf = require "std.strbuf" {} + local StrBuf = require "std.strbuf".prototype local a = StrBuf {"a"} local b = a:concat "b" -- mutate *a* print (a, b) --> ab ab local c = a {} .. "c" -- copy and append print (a, c) --> ab abc + In addition to the functionality described here, StrBuf objects also + have all the methods and metamethods of the @{std.object.prototype} + (except where overridden here), + Prototype Chain --------------- table - `-> Object - `-> StrBuf + `-> Container + `-> Object + `-> StrBuf - @classmod std.strbuf + @prototype std.strbuf ]] local std = require "std.base" @@ -50,17 +55,32 @@ local function X (decl, fn) return debug.argscheck ("std.strbuf." .. decl, fn) end +--- StrBuf prototype object. +-- @object prototype +-- @string[opt="StrBuf"] _type object name +-- @see std.object.prototype +-- @usage +-- local StrBuf = require "std.strbuf".prototype +-- local a = StrBuf {1, 2, 3} +-- local b = StrBuf {a, "five", "six"} +-- a = a .. 4 +-- b = b:concat "seven" +-- print (a, b) --> 1234 1234fivesixseven +-- os.exit (0) local M = { + --- Methods + -- @section methods + --- Add a object to a buffer. - -- Elements are stringified lazily, so if add a table and then change - -- its contents, the contents of the buffer will be affected too. - -- @static - -- @function concat + -- Elements are stringified lazily, so if you add a table and then + -- change its contents, the contents of the buffer will be affected + -- too. + -- @function prototype:concat -- @param x object to add to buffer - -- @treturn StrBuf modified buffer + -- @treturn prototype modified buffer -- @usage - -- buf = buf:concat "append this" {" and", " this"} + -- c = StrBuf {} :concat "append this" :concat (StrBuf {" and", " this"}) concat = X ("concat (StrBuf, any)", __concat), } @@ -83,40 +103,25 @@ M.tostring = DEPRECATED ("41.1", "std.strbuf.tostring", --[[ ================== ]]-- ---- StrBuf prototype object. --- --- Set also inherits all the fields and methods from --- @{std.object.Object}. --- @object StrBuf --- @string[opt="StrBuf"] _type object name --- @see std.object.__call --- @usage --- local std = require "std" --- local StrBuf = std.strbuf {} --- local a = {1, 2, 3} --- local b = {a, "five", "six"} --- a = a .. 4 --- b = b:concat "seven" --- print (a, b) --> 1234 1234fivesixseven --- os.exit (0) -local StrBuf = Object { +local prototype = Object { _type = "StrBuf", + --- Metamethods + -- @section metamethods + __index = M, --- Support concatenation to StrBuf objects. - -- @function __concat - -- @tparam StrBuf buffer object + -- @function prototype:__concat -- @param x a string, or object that can be coerced to a string - -- @treturn StrBuf modified *buf* + -- @treturn prototype modified *buf* -- @see concat -- @usage -- buf = buf .. x __concat = __concat, --- Support fast conversion to Lua string. - -- @function __tostring - -- @tparam StrBuf buffer object + -- @function prototype:__tostring -- @treturn string concatenation of buffer contents -- @see tostring -- @usage @@ -126,5 +131,5 @@ local StrBuf = Object { return std.object.Module { - prototype = StrBuf, + prototype = prototype, } diff --git a/lib/std/string.lua b/lib/std/string.lua index 75b32d1..b361fb9 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -7,7 +7,7 @@ local string = require "std.string" - @module std.string + @corelibrary std.string ]] local std = require "std.base" @@ -261,7 +261,11 @@ local function X (decl, 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) @@ -271,6 +275,7 @@ M = { __concat = __concat, --- String subscript operation. + -- @function __index -- @string s string -- @tparam int|string i index or method name -- @return `s:sub (i, i)` if i is a number, otherwise @@ -280,6 +285,10 @@ M = { -- third = ("12345")[3] __index = __index, + + --- Core Functions + -- @section corefuncs + --- Capitalise each word in a string. -- @function caps -- @string s any string @@ -342,16 +351,6 @@ M = { ltrim = X ("ltrim (string, ?string)", function (s, r) return (s:gsub ("^" .. (r or "%s+"), "")) end), - --- Overwrite core `string` methods with `std` enhanced versions. - -- - -- Also adds auto-stringification to `..` operator on core strings, and - -- integer indexing of strings with `[]` dereferencing. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage local string = require "std.string".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), - --- Write a number using SI suffixes. -- The number is always written to 3 s.f. -- @function numbertosi @@ -384,6 +383,7 @@ M = { --- Convert a value to a string. -- The string can be passed to `functional.eval` to retrieve the value. -- @todo Make it work for recursive tables. + -- @function pickle -- @param x object to pickle -- @treturn string reversible string rendering of *x* -- @see std.eval @@ -470,6 +470,20 @@ M = { -- @usage -- print (wrap (copyright, 72, 4)) wrap = X ("wrap (string, ?int, ?int, ?int)", wrap), + + + --- Module Functions + -- @section modulefuncs + + --- Overwrite core `string` methods with `std` enhanced versions. + -- + -- Also adds auto-stringification to `..` operator on core strings, and + -- integer indexing of strings with `[]` dereferencing. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage local string = require "std.string".monkey_patch () + monkey_patch = X ("monkey_patch (?table)", monkey_patch), } diff --git a/lib/std/table.lua b/lib/std/table.lua index b7e4fa3..e23d9f5 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -7,7 +7,7 @@ local table = require "std.table" - @module std.table + @corelibrary std.table ]] @@ -198,9 +198,69 @@ local function X (decl, 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)", std.table.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)", std.table.maxn), + + --- 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 accordind 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)", std.table.unpack), + + + --- Accessor Functions + -- @section accessorfuncs + --- Make a shallow copy of a table, including any metatable. -- - -- To make deep copies, use @{tree.clone}. + -- To make deep copies, use @{std.tree.clone}. -- @function clone -- @tparam table t source table -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` @@ -268,82 +328,6 @@ M = { -- flatten {{1, {{2}, 3}, 4}, 5} flatten = X ("flatten (table)", flatten), - --- 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)", std.table.insert), - - --- 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)", std.table.invert), - - --- Make the list of keys in table. - -- @function keys - -- @tparam table t a table - -- @treturn table list of keys from *t* - -- @see okeys - -- @see values - -- @usage globals = keys (_G) - keys = X ("keys (table)", keys), - - --- 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)", std.table.maxn), - - --- Destructively merge another 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), - - --- Overwrite core `table` methods with `std` enhanced versions. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage local table = require "std.table".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), - --- Make a table with a default value for unset keys. -- @function new -- @param[opt=nil] x default entry value @@ -379,19 +363,6 @@ M = { -- project ("xx", {{"a", xx=1, yy="z"}, {"b", yy=2}, {"c", xx=3}, {xx="yy"}) project = X ("project (any, list of tables)", project), - --- 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), - --- Shape a table according to a list of dimensions. -- -- Dimensions are given outermost first and items from the original @@ -426,23 +397,6 @@ M = { -- size {foo = true, bar = true, baz = false} size = X ("size (table)", size), - --- 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 accordind 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)", std.table.unpack), - --- Make the list of values of a table. -- @function values -- @tparam table t any table @@ -452,6 +406,67 @@ M = { -- --> {"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)", std.table.invert), + + --- Make the list of keys in table. + -- @function keys + -- @tparam table t a table + -- @treturn table list of keys from *t* + -- @see okeys + -- @see values + -- @usage globals = keys (_G) + keys = X ("keys (table)", keys), + + --- Destructively merge another 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), + + + --- Module Functions + -- @section modulefuncs + + --- Overwrite core `table` methods with `std` enhanced versions. + -- @function monkey_patch + -- @tparam[opt=_G] table namespace where to install global functions + -- @treturn table the module table + -- @usage local table = require "std.table".monkey_patch () + monkey_patch = X ("monkey_patch (?table)", monkey_patch), } diff --git a/lib/std/tree.lua b/lib/std/tree.lua index f3d83bb..3784b11 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -1,20 +1,26 @@ --[[-- Tree container prototype. - Note that Functions listed below are only available from the Tree - prototype returned by requiring this module, because Container objects - cannot have object methods. + This module returns a table of tree operators, as well as the prototype + for a Tree container object. + + This is not a search tree, but rather a way to efficiently store and + retrieve values stored with a path as a key, such as a multi-key + keytable. Although it does have iterators for walking the tree with + various algorithms. + + In addition to the functionality described here, Tree containers also + have all the methods and metamethods of the @{std.container.prototype} + (except where overridden here), Prototype Chain --------------- table - `-> Object - `-> Container - `-> Tree + `-> Container + `-> Tree - @classmod std.tree - @see std.container + @prototype std.tree ]] local std = require "std.base" @@ -29,13 +35,13 @@ local reduce = std.functional.reduce local len = std.operator.len local leaves = std.tree.leaves -local Tree -- forward declaration +local prototype -- forward declaration --- Tree iterator. -- @tparam function it iterator function --- @tparam tree|table tr tree or tree-like table +-- @tparam prototype|table tr tree container or tree-like table -- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order)) -- @treturn table path to node (`{i1, ...in}`) -- @treturn node node @@ -108,13 +114,12 @@ end --- Tree prototype object. --- @object Tree +-- @object prototype -- @string[opt="Tree"] _type object name --- @see std.container --- @see std.object.__call +-- @see std.container.prototype -- @usage --- local std = require "std" --- local Tree = std.tree {} +-- local tree = require "std.tree" +-- local Tree = tree.prototype -- local tr = Tree {} -- tr[{"branch1", 1}] = "leaf1" -- tr[{"branch1", 2}] = "leaf2" @@ -123,16 +128,17 @@ end -- print (tr[{"branch1", 2}]) --> leaf2 -- print (tr[{"branch1", 3}]) --> nil -- --> leaf1 leaf2 leaf3 --- for leaf in std.tree.leaves (tr) do +-- for leaf in tree.leaves (tr) do -- io.write (leaf .. "\t") -- end -Tree = Container { +prototype = Container { _type = "Tree", + --- Metamethods + -- @section metamethods + --- Deep retrieval. - -- @static - -- @function __index - -- @tparam Tree tr a tree + -- @function prototype:__index -- @param i non-table, or list of keys `{i1, ...i_n}` -- @return `tr[i1]...[i_n]` if *i* is a key list, `tr[i]` otherwise -- @todo the following doesn't treat list keys correctly @@ -148,9 +154,7 @@ Tree = Container { end, --- Deep insertion. - -- @static - -- @function __newindex - -- @tparam Tree tr a tree + -- @function prototype:__newindex -- @param i non-table, or list of keys `{i1, ...i_n}` -- @param[opt] v value -- @usage @@ -159,7 +163,7 @@ Tree = Container { if stdtype (i) == "table" then for n = 1, len (i) - 1 do if stdtype (tr[i[n]]) ~= "Tree" then - rawset (tr, i[n], Tree {}) + rawset (tr, i[n], prototype {}) end tr = tr[i[n]] end @@ -172,15 +176,18 @@ Tree = Container { return std.object.Module { - prototype = Tree, + prototype = prototype, - --- Make a deep copy of a tree, including any metatables. - -- @static + --- Functions + -- @section functions + + --- Make a deep copy of a tree or table, including any metatables. -- @function clone - -- @tparam table t tree or tree-like table + -- @tparam table tr tree or tree-like table -- @tparam boolean nometa if non-`nil` don't copy metatables - -- @treturn Tree|table a deep copy of *tr* + -- @treturn prototype|table a deep copy of *tr* -- @see std.table.clone + -- @see std.object.clone -- @usage -- tr = {"one", {two=2}, {{"three"}, four=4}} -- copy = clone (tr) @@ -189,11 +196,10 @@ return std.object.Module { clone = X ("clone (table, ?boolean|:nometa)", clone), --- Tree iterator which returns just numbered leaves, in order. - -- @static -- @function ileaves - -- @tparam Tree|table tr tree or tree-like table + -- @tparam prototype|table tr tree or tree-like table -- @treturn function iterator function - -- @treturn Tree|table the tree *tr* + -- @treturn prototype|table the tree *tr* -- @see inodes -- @see leaves -- @usage @@ -208,16 +214,14 @@ return std.object.Module { -- -- The iterator function behaves like @{nodes}, but only traverses the -- array part of the nodes of *tr*, ignoring any others. - -- @static -- @function inodes - -- @tparam Tree|table tr tree or tree-like table to iterate over + -- @tparam prototype|table tr tree or tree-like table to iterate over -- @treturn function iterator function -- @treturn tree|table the tree, *tr* -- @see nodes inodes = X ("inodes (table)", function (t) return _nodes (ipairs, t) end), --- Tree iterator which returns just leaves. - -- @static -- @function leaves -- @tparam table t tree or tree-like table -- @treturn function iterator function @@ -234,7 +238,6 @@ return std.object.Module { leaves = X ("leaves (table)", function (t) return leaves (pairs, t) end), --- Destructively deep-merge one tree into another. - -- @static -- @function merge -- @tparam table t destination tree -- @tparam table u table with nodes to merge @@ -256,11 +259,10 @@ return std.object.Module { -- you must `table.clone` a copy if you want to take a snap-shot of the -- current state of the `tree-path` list before the next iteration -- changes it. - -- @static -- @function nodes - -- @tparam Tree|table tr tree or tree-like table to iterate over + -- @tparam prototype|table tr tree or tree-like table to iterate over -- @treturn function iterator function - -- @treturn Tree|table the tree, *tr* + -- @treturn prototype|table the tree, *tr* -- @see inodes -- @usage -- -- tree = +-- node1 diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 22f7318..631b840 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -1,24 +1,28 @@ --[[-- Tuple container prototype. - An interned immutable nil-preserving tuple object. + An interned, immutable, nil-preserving tuple object. - Like Lua strings, tuples with the same elements can be quickly compared with - a straight forward `==` comparison. + Like Lua strings, tuples with the same elements can be quickly compared + with a straight forward `==` comparison. The `prototype` field in the + returned module table is the empty tuple, which can be cloned to create + tuples with other contents. - The immutability guarantees only work if you don't change the contents of - tables after adding them to a tuple. Don't do that! + In addition to the functionality described here, Tuple containers also + have all the methods and metamethods of the @{std.container.prototype} + (except where overridden here), + + The immutability guarantees only work if you don't change the contents + of tables after adding them to a tuple. Don't do that! Prototype Chain --------------- table - `-> Object - `-> Container - `-> Tuple + `-> Container + `-> Tuple - @classmod std.tuple - @see std.container + @prototype std.tuple ]] local Container = require "std.container".prototype @@ -27,8 +31,8 @@ local std = require "std.base" local stdtype = std.type --- Stringify tuple values, as a memoization key. --- @tparam Tuple tup tuple to process +--- Stringify tuple values, as a memoization key. +-- @tparam prototype tuple tuple to process -- @treturn string a comma separated ordered list of stringified *tup* elements local function argstr (tuple) local s = {} @@ -41,7 +45,6 @@ end -- Maintain a weak functable of all interned tuples. --- @static -- @function intern -- @param ... tuple elements -- @treturn table an interned proxied table with ... elements @@ -67,14 +70,12 @@ local intern = setmetatable ({}, { --- Tuple prototype object. --- --- Set also inherits all the fields from @{std.container.Container} --- @object Tuple +-- @object prototype -- @string[opt="Tuple"] _type object name -- @int n number of tuple elements --- @see std.container +-- @see std.container.prototype -- @usage --- local Tuple = require "std.tuple" +-- local Tuple = require "std.tuple".prototype -- function count (...) -- argtuple = Tuple (...) -- return argtuple.n @@ -83,16 +84,19 @@ local intern = setmetatable ({}, { -- count (nil) --> 1 -- count (false) --> 1 -- count (false, nil, true, nil) --> 4 -local Tuple = Container { +local prototype = Container { _type = "Tuple", _init = function (obj, ...) return intern (...) end, + --- Metamethods + -- @section metamethods + -- The actual contents of *tup*. -- This ensures __newindex will trigger for existing elements too. - -- It also informs `table.unpack` that that the elements to unpack are + -- It also informs @{std.table.unpack} that that the elements to unpack are -- not in the usual place. __contents = getmetatable (intern ()).__contents, @@ -102,9 +106,7 @@ local Tuple = Container { __index = getmetatable (intern ()).__index, --- Return the length of this tuple. - -- @static - -- @function __len - -- @tparam Tuple tup object to process + -- @function prototype:__len -- @treturn int number of elements in *tup* -- @usage -- -- Only works on Lua 5.2 or newer: @@ -116,20 +118,16 @@ local Tuple = Container { end, --- Prevent mutation of *tup*. - -- This metamethod never returns, because Tuples are immutable. - -- @static - -- @function __newindex - -- @tparam Tuple tup object to process + -- @function prototype:__newindex -- @param k tuple key -- @param v tuple value + -- @raise cannot change immutable tuple object __newindex = function (self, k, v) error ("cannot change immutable tuple object", 2) end, --- Return a string representation of *tup* - -- @static - -- @function __tostring - -- @tparam Tuple tup object to process + -- @function prototype:__tostring -- @treturn string representation of *tup* -- @usage -- -- 'Tuple ("nil", nil, false)' @@ -141,5 +139,5 @@ local Tuple = Container { return std.object.Module { - prototype = Tuple, + prototype = prototype, } From cd0c03798d2a51ed8a718e2da7030ba9b223527b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 27 Jun 2015 19:38:22 +0100 Subject: [PATCH 570/703] doc: install documentation correctly. Due to LDoc's unusual choice of argument for directory names, in combination with Automake not being good with spaces in lists of path names, installation requires a more lateral approach. * build-aux/config.ld.in (new_type): Use underscores for headings, because they are also case folded into directory names. (postprocess_html): Put the spaces back into the headings' text. * local.mk (Documentation): Install new doc tree heirarchy with more care! Signed-off-by: Gary V. Vaughan --- build-aux/config.ld.in | 16 ++++----- local.mk | 80 +++++++++++++++++++++++++++--------------- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 052dd2c..59ed91d 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -70,20 +70,20 @@ file = { "../lib/std/strict.lua", } -new_type ("corefunction", "Core Functions", true) -new_type ("corelibrary", "Core Libraries", true) -new_type ("prototype", "Object System", true) -new_type ("functional", "Functional Style", true) +new_type ("corefunction", "Core_Functions", true) +new_type ("corelibrary", "Core_Libraries", true) +new_type ("prototype", "Object_System", true) +new_type ("functional", "Functional_Style", true) function postprocess_html(s) s = s:gsub("

    %s*Corefunction (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Corelibrary (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Prototype (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Functional (.-)

    ", '

    Module %1

    ') - s = s:gsub('href="core[ _]functions', 'href="core%%20functions') - s = s:gsub('href="core[ _]libraries', 'href="core%%20libraries') - s = s:gsub('href="object[ _]system', 'href="object%%20system') - s = s:gsub('href="functional[ _]style', 'href="functional%%20style') + s = s:gsub("

    Core_Functions

    ", '

    Core Functions

    ') + s = s:gsub("

    Core_Libraries

    ", '

    Core Libraries

    ') + s = s:gsub("

    Object_System

    ", '

    Object System

    ') + s = s:gsub("

    Functional_Style

    ", '

    Functional Style

    ') return s end diff --git a/local.mk b/local.mk index 5f9977b..33361fd 100644 --- a/local.mk +++ b/local.mk @@ -41,12 +41,18 @@ update_copyright_env = \ ## Declarations. ## ## ------------- ## -classesdir = $(docdir)/classes -modulesdir = $(docdir)/modules - -dist_doc_DATA = -dist_classes_DATA = -dist_modules_DATA = +doccorefunctionsdir = $(docdir)/core_functions +doccorelibrariesdir = $(docdir)/core_libraries +docfunctionaldir = $(docdir)/functional_style +docmodulesdir = $(docdir)/modules +docobjectsdir = $(docdir)/object_system + +dist_doc_DATA = +dist_doccorefunctions_DATA = +dist_doccorelibraries_DATA = +dist_docfunctional_DATA = +dist_docmodules_DATA = +dist_docobjects_DATA = include specs/specs.mk @@ -121,39 +127,55 @@ EXTRA_DIST += \ ## Documentation. ## ## -------------- ## +doccorefunctions = $(srcdir)/doc/core_functions/std +doccorelibraries = $(srcdir)/doc/core_libraries/std +docfunctional = $(srcdir)/doc/functional_style/std +docmodules = $(srcdir)/doc/modules/std +docobjects = $(srcdir)/doc/object_system/std -dist_doc_DATA += \ - $(srcdir)/doc/index.html \ +dist_doc_DATA += \ + $(srcdir)/doc/index.html \ $(srcdir)/doc/ldoc.css -dist_classes_DATA += \ - $(srcdir)/doc/classes/std.container.html \ - $(srcdir)/doc/classes/std.list.html \ - $(srcdir)/doc/classes/std.object.html \ - $(srcdir)/doc/classes/std.optparse.html \ - $(srcdir)/doc/classes/std.set.html \ - $(srcdir)/doc/classes/std.strbuf.html \ - $(srcdir)/doc/classes/std.tree.html \ - $(srcdir)/doc/classes/std.tuple.html \ +dist_doccorefunctions_DATA += \ + $(doccorefunctions).html \ + $(NOTHING_ELSE) + +dist_doccorelibraries_DATA += \ + $(doccorelibraries).debug.html \ + $(doccorelibraries).io.html \ + $(doccorelibraries).math.html \ + $(doccorelibraries).package.html \ + $(doccorelibraries).string.html \ + $(doccorelibraries).table.html \ + $(NOTHING_ELSE) + +dist_docfunctional_DATA += \ + $(docfunctional).functional.html \ + $(docfunctional).operator.html \ + $(NOTHING_ELSE) + +dist_docmodules_DATA += \ + $(docmodules).optparse.html \ + $(docmodules).strict.html \ $(NOTHING_ELSE) -dist_modules_DATA += \ - $(srcdir)/doc/modules/std.html \ - $(srcdir)/doc/modules/std.debug.html \ - $(srcdir)/doc/modules/std.functional.html \ - $(srcdir)/doc/modules/std.io.html \ - $(srcdir)/doc/modules/std.math.html \ - $(srcdir)/doc/modules/std.operator.html \ - $(srcdir)/doc/modules/std.package.html \ - $(srcdir)/doc/modules/std.strict.html \ - $(srcdir)/doc/modules/std.string.html \ - $(srcdir)/doc/modules/std.table.html \ +dist_docobjects_DATA += \ + $(docobjects).container.html \ + $(docobjects).list.html \ + $(docobjects).object.html \ + $(docobjects).set.html \ + $(docobjects).strbuf.html \ + $(docobjects).tree.html \ + $(docobjects).tuple.html \ $(NOTHING_ELSE) ## Parallel make gets confused when one command ($(LDOC)) produces ## multiple targets (all the html files above), so use the presence ## of the doc directory as a sentinel file. -$(dist_doc_DATA) $(dist_classes_DATA) $(dist_modules_DATA): $(srcdir)/doc +$(dist_doc_DATA) $(dist_doccorefunctions_DATA): $(srcdir)/doc +$(dist_doccorelibraries_DATA) $(dist_docfunctional_DATA): $(srcdir)/doc +$(dist_docmodules_DATA) $(dist_docobjects_DATA): $(srcdir)/doc $(srcdir)/doc: $(dist_lua_DATA) $(dist_luastd_DATA) test -d $@ || mkdir $@ From 6f5b85ca143eb4a14c96d96546e598e307dc11f7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 27 Jun 2015 20:04:31 +0100 Subject: [PATCH 571/703] slingshot: sync with upstream for Travis Lua 5.3.1 support. * slingshot: Sync with upstream. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 6 +++--- slingshot | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8593705..0f8d610 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,8 +32,8 @@ before_install: # Fetch Lua sources. - cd $TRAVIS_BUILD_DIR - 'if test lua5.3 = "$LUA"; then - curl http://www.lua.org/ftp/lua-5.3.0.tar.gz | tar xz; - cd lua-5.3.0; + curl http://www.lua.org/ftp/lua-5.3.1.tar.gz | tar xz; + cd lua-5.3.1; fi' - 'if test lua5.2 = "$LUA"; then curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz; @@ -91,7 +91,7 @@ before_install: # Tidy up file droppings. - cd $TRAVIS_BUILD_DIR - - rm -rf lua-5.3.0 lua-5.2.3 lua-5.1.5 LuaJIT-2.0.3 luarocks-2.2.0 + - rm -rf lua-5.3.1 lua-5.2.3 lua-5.1.5 LuaJIT-2.0.3 luarocks-2.2.0 install: diff --git a/slingshot b/slingshot index f369b47..43a723d 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit f369b4710ebfaff0db8e579dc7f6f3e0bb78c462 +Subproject commit 43a723dd7f5585aaefa0b209d927eb0e77aa674a From 6a5157ba2b269789edfd800e4e31d9c946737677 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 27 Jun 2015 20:11:08 +0100 Subject: [PATCH 572/703] maint: add MIT license badge to README.md. * README.md: Add MIT license badge. Signed-off-by: Gary V. Vaughan --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d5a82a5..bd8b233 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ by the [stdlib project][github] [github]: http://github.com/lua-stdlib/lua-stdlib/ "Github repository" +[![License](http://img.shields.io/:license-mit-blue.svg)](http://mit-license.org) [![travis-ci status](https://secure.travis-ci.org/lua-stdlib/lua-stdlib.png?branch=master)](http://travis-ci.org/lua-stdlib/lua-stdlib/builds) [![Stories in Ready](https://badge.waffle.io/lua-stdlib/lua-stdlib.png?label=ready&title=Ready)](https://waffle.io/lua-stdlib/lua-stdlib) From 9fd55e9b197a52962c798967021cff702791d46e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 29 Jul 2015 22:35:49 +0100 Subject: [PATCH 573/703] doc: fix a punctuation typo in std.set. * lib/std/set.lua: Sentences are terminated with a period. Signed-off-by: Gary V. Vaughan --- lib/std/set.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/set.lua b/lib/std/set.lua index 2ee588f..8a6c6ab 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -9,7 +9,7 @@ In addition to the functionality described here, Set containers also have all the methods and metamethods of the @{std.container.prototype} - (except where overridden here), + (except where overridden here). Prototype Chain --------------- From 1326ed1a89b3fe936dbb4bb043c2f399b46fca8f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 31 Jul 2015 22:03:37 +0100 Subject: [PATCH 574/703] std: getmetamethod returns functables correctly. * specs/std_spec.yaml (getmetamethod): Correct behaviour when encountering a functable value in the named metatable key is to return it! * lib/std/base.lua (callable): Check directly for and return __call metatable value if argument is not a function. (getmetamethod): Use callable rather than "function" type to determine whether a method value was stored at the given key. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++++ lib/std/base.lua | 30 ++++++++++++++++++------------ specs/std_spec.yaml | 13 ++++++++++--- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index bb140d4..cfbe20d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -91,6 +91,10 @@ - `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. + ### Incompatible changes - Deprecated multi-argument `functional.bind` has been removed. diff --git a/lib/std/base.lua b/lib/std/base.lua index a17284e..bf7889b 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -50,21 +50,27 @@ local function assert (expect, fmt, arg1, ...) end -local function getmetamethod (x, n) - local _, m = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if type (m) ~= "function" then - m = nil - end - return m +-- 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 callable (x) - if type (x) == "function" then return x end - return getmetamethod (x, "__call") +local function getmetamethod (x, n) + local m = (getmetatable (x) or {})[n] + if callable (m) then return m end end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 404dc0a..18f3110 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -233,14 +233,21 @@ specify std: - context with a table: - before: - method = function () end - t = setmetatable ({}, { _type = "table", _method = method }) + 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-function metatable entries: + - 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" - context with an object: - before: From c0ab0bec84a5b68df64fb7c5deb330e3a7e50c87 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 31 Jul 2015 22:34:24 +0100 Subject: [PATCH 575/703] refactor: call core string.format with format strings. * lib/std/string.lua (numbertosi, prettytostring, pickle): Since we know we want % interpolation, call directly into core Lua string.format implementation rather than our std.string.format wrapper. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index b361fb9..41701e3 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -43,7 +43,7 @@ local function __index (s, i) end -local _format = string.format +local _format = string.format local function format (f, arg1, ...) return (arg1 ~= nil) and _format (f, arg1, ...) or f @@ -156,14 +156,14 @@ local function numbertosi (n) [4] = "T", [5] = "P", [6] = "E", [7] = "Z", [8] = "Y" } - local t = format("% #.2e", n) + local t = _format ("% #.2e", n) local _, _, m, e = t:find(".(.%...)e(.+)") local man, exp = tonumber (m), tonumber (e) local siexp = math.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 + return _format ("%0.f", man) .. s end @@ -188,7 +188,7 @@ local function prettytostring (x, indent, spacing) end, function (x) if type (x) == "string" then - return format ("%q", x) + return _format ("%q", x) else return tostring (x) end @@ -229,7 +229,7 @@ end local function pickle (x) if type (x) == "string" then - return format ("%q", x) + return _format ("%q", x) elseif type (x) == "number" or type (x) == "boolean" or type (x) == "nil" then return tostring (x) From 8bbd0b9df24cc29c7c846864d03447f2ba11f269 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 2 Aug 2015 16:01:27 +0100 Subject: [PATCH 576/703] specs: disable an unstable example. * specs/std_spec.yaml (barrel): There's no guarantee what, if any, std submodules might be autoloaded by calling std.barrel(), and we don't want to tie ourselves to a contract, so simply don't check! Signed-off-by: Gary V. Vaughan --- specs/std_spec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 18f3110..9d4d329 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -102,8 +102,8 @@ specify std: # Ideally, `.to_be (M)`, except that M is cloned from a nested context # by Specl to prevent us from affecting any other examples, thus the # address is different by now. - - it returns std module table: - expect (f (namespace)).to_equal (M) + #- it returns std module table: + # expect (f (namespace)).to_equal (M) - it installs std monkey patches: for _, api in ipairs (exported_apis) do if type (M[api]) == "function" and From 623d79ca6336f5842574c09d3766c0e92ffd1945 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 2 Aug 2015 16:14:59 +0100 Subject: [PATCH 577/703] string: much improved pickle implementation. * specs/string_spec.yaml (pickle): Add examples of behaviour when pickling with nested tables, mixed sequence/hash type tables and tables with mutable keys. * lib/std/string.lua (pickle_table): New function for pickling each key/value pair in a table to a readable string. (pickle): Use it to support pickling nested tables. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 2 + lib/std/string.lua | 102 +++++++++++++++++++++++++++++------------ specs/string_spec.yaml | 42 +++++++++++++---- 3 files changed, 107 insertions(+), 39 deletions(-) diff --git a/NEWS.md b/NEWS.md index cfbe20d..cf26168 100644 --- a/NEWS.md +++ b/NEWS.md @@ -89,6 +89,8 @@ ### Bug fixes + - `std.string.pickle` works with nested tables, and mutable keys. + - `std.string.wrap` doesn't throw a StrBuf deprecation warning any more. - `std.getmetamethod` now returns functable valued metamethods diff --git a/lib/std/string.lua b/lib/std/string.lua index 41701e3..be9c8fc 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -16,11 +16,12 @@ local debug = require "std.debug" local StrBuf = require "std.strbuf".prototype local getmetamethod, pairs = std.getmetamethod, std.pairs -local copy = std.base.copy -local len = std.operator.len -local render = std.string.render -local insert = std.table.insert -local type = type -- avoid mutual recursion between debug argument checker and string.__index +local callable = std.functional.callable +local copy = std.base.copy +local len = std.operator.len +local render = std.string.render +local insert = std.table.insert +local type = type -- avoid mutual recursion between debug argument checker and string.__index local M @@ -167,9 +168,70 @@ local function numbertosi (n) end -local function trim (s, r) - r = r or "%s+" - return (s:gsub ("^" .. r, ""):gsub (r .. "$", "")) +local pickle_table -- forward declaration + +local function pickle (x) + -- __pickle metamethod + local __pickle = (getmetatable (x) or {}).__pickle + if callable (__pickle) then + return __pickle (x) + elseif type (__pickle) == "string" then + return __pickle + end + + -- math + if x == nil then + return "nil" + elseif x ~= x then + return "0/0" + elseif x == math.huge then + return "math.huge" + elseif x == -math.huge then + return "-math.huge" + end + + -- common types + local type_x = type (x) + if type_x == "table" then + return pickle_table (x) + elseif type_x == "string" then + return _format ("%q", x) + elseif type_x == "number" or type_x == "boolean" then + return tostring (x) + end + + -- don't know what to do with this :( + die ("cannot pickle " .. tostring (x)) +end + + +function pickle_table (t) + local buf = {} + + -- sequence values, if any + local seq, i = {}, 1 + while t[i] ~= nil do + i, seq[i] = i + 1, pickle (t[i]) + end + if i > 1 then + buf[1] = table.concat (seq, ", ") + end + + -- hash values with keys, if any, after sequence value + local hash, i = {}, 1 + for k, v in pairs (t) do + if seq[k] == nil then + i, hash[i] = i + 1, "[" .. pickle (k) .. "] = " .. pickle (v) + end + end + if i > 1 then + buf[#buf + 1] = table.concat (hash, ", ") + end + + -- wrap in Lua table read-syntax + buf[1] = "{" .. (buf[1] or "") + buf[#buf] = buf[#buf] .. "}" + return table.concat (buf, "; ") end @@ -227,26 +289,9 @@ local function prettytostring (x, indent, spacing) end -local function pickle (x) - if type (x) == "string" then - return _format ("%q", x) - elseif type (x) == "number" or type (x) == "boolean" or - type (x) == "nil" then - return tostring (x) - else - x = copy (x) or x - if type (x) == "table" then - local s, sep = "{", "" - for i, v in pairs (x) do - s = s .. sep .. "[" .. M.pickle (i) .. "]=" .. M.pickle (v) - sep = "," - end - s = s .. "}" - return s - else - die ("cannot pickle " .. tostring (x)) - end - end +local function trim (s, r) + r = r or "%s+" + return (s:gsub ("^" .. r, ""):gsub (r .. "$", "")) end @@ -382,7 +427,6 @@ M = { --- Convert a value to a string. -- The string can be passed to `functional.eval` to retrieve the value. - -- @todo Make it work for recursive tables. -- @function pickle -- @param x object to pickle -- @treturn string reversible string rendering of *x* diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index aa53ed7..24c34ba 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -384,27 +384,49 @@ specify std.string: - before: loadstring = loadstring or load function unpickle (s) return loadstring ("return " .. s) () end - t = {1, {{2, 3}, 4, {5}}} + seq = {1, {{2, 3}, 4, {5}}} + hash = {foo={[{5,42}]={bar=0}}} f = M.pickle - it converts a primitive to a representative string: expect (f (nil)).to_be "nil" expect (f (false)).to_be "false" expect (f (42)).to_be "42" expect (f "string").to_be '"string"' - - it returns a loadable string that results in the original value: + - it converts infinities to a loadable string: + expect (unpickle (f (math.huge))).to_be (math.huge) + expect (unpickle (f (-math.huge))).to_be (-math.huge) + - it converts nan values to a loadable string: + expect (f (0/0)).to_be "0/0" + nan = unpickle (f (0/0)) + expect (type (nan)).to_be "number" + expect (nan).not_to_equal (nan) + - it round trips primitive values: expect (unpickle (f (nil))).to_be (nil) expect (unpickle (f (false))).to_be (false) expect (unpickle (f (42))).to_be (42) expect (unpickle (f "string")).to_be "string" - - it converts a table to a representative string: - expect (f {"table", 42}).to_be '{[1]="table",[2]=42}' - - it returns a loadable string that results in the original table: - expect (unpickle (f {"table", 42})).to_equal {"table", 42} - - it converts a nested table to a representative string: - expect (f (t)). - to_be "{[1]=1,[2]={[1]={[1]=2,[2]=3},[2]=4,[3]={[1]=5}}}" - - it returns a loadable string that results in the original nested table: + - it converts a sequence to a representative string: + expect (f {"sequence", 42}).to_be '{"sequence", 42}' + - it roundtrips sequences: + expect (unpickle (f {"sequence", 42})).to_equal {"sequence", 42} + - it converts a nested sequence to a representative string: + expect (f (seq)).to_be "{1, {{2, 3}, 4, {5}}}" + - it roundtrips a nested sequence: + expect (unpickle (f (seq))).to_equal (seq) + - it converts a hash to a representative string: + expect (f {hash=42}).to_be '{["hash"] = 42}' + - it roundtrips a hash: + expect (unpickle (f {hash=42})).to_equal {hash=42} + - it converts a mixed table to a representative string: + expect (f {"one", foo=3, 2}).to_be '{"one", 2; ["foo"] = 3}' + - it roundtrips a mixed table: + t = {"one", foo=3, 2} expect (unpickle (f (t))).to_equal (t) + - it converts a nested hash to a representative string: + expect (f (hash)). + to_be '{["foo"] = {[{5, 42}] = {["bar"] = 0}}}' + - it roundtrips a nested hash: + expect (unpickle (f (hash))).to_equal (hash) - describe prettytostring: From 944c6a7168b46b71989410a2d06047ec00875b2a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 2 Aug 2015 16:48:35 +0100 Subject: [PATCH 578/703] refactor: move bulk of pickle to std/base.lua. * lib/std/string.lua (pickle, pickle_table): Move from here... * lib/std/base.lua (pickle, pickle_table): ...to here. * lib/std/string.lua (M): Except __pickle metatable management, which stays right here. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 60 ++++++++++++++++++++++++++++++++++ lib/std/string.lua | 80 +++++++--------------------------------------- 2 files changed, 71 insertions(+), 69 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index bf7889b..4a052b5 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -327,6 +327,65 @@ local function npairs (t) end +local pickle_table -- forward declaration + +local function pickle (x) + -- math + if x == nil then + return "nil" + elseif x ~= x then + return "0/0" + elseif x == math.huge then + return "math.huge" + elseif x == -math.huge then + return "-math.huge" + end + + -- common types + local type_x = type (x) + if type_x == "table" then + return pickle_table (x) + elseif type_x == "string" then + return string.format ("%q", x) + elseif type_x == "number" or type_x == "boolean" then + return tostring (x) + end + + -- don't know what to do with this :( + die ("cannot pickle " .. tostring (x)) +end + + +function pickle_table (t) + local buf = {} + + -- sequence values, if any + local seq, i = {}, 1 + while t[i] ~= nil do + i, seq[i] = i + 1, pickle (t[i]) + end + if i > 1 then + buf[1] = table.concat (seq, ", ") + end + + -- hash values with keys, if any, after sequence value + local hash, i = {}, 1 + for k, v in pairs (t) do + if seq[k] == nil then + i, hash[i] = i + 1, "[" .. pickle (k) .. "] = " .. pickle (v) + end + end + if i > 1 then + buf[#buf + 1] = table.concat (hash, ", ") + end + + -- wrap in Lua table read-syntax + buf[1] = "{" .. (buf[1] or "") + buf[#buf] = buf[#buf] .. "}" + return table.concat (buf, "; ") +end + + local function collect (ifn, ...) local argt, r = {...}, {} if not callable (ifn) then @@ -555,6 +614,7 @@ return { string = { escape_pattern = escape_pattern, + pickle = pickle, render = render, split = split, }, diff --git a/lib/std/string.lua b/lib/std/string.lua index be9c8fc..32f2cda 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -19,7 +19,7 @@ local getmetamethod, pairs = std.getmetamethod, std.pairs local callable = std.functional.callable local copy = std.base.copy local len = std.operator.len -local render = std.string.render +local pickle, render = std.string.pickle, std.string.render local insert = std.table.insert local type = type -- avoid mutual recursion between debug argument checker and string.__index @@ -168,73 +168,6 @@ local function numbertosi (n) end -local pickle_table -- forward declaration - -local function pickle (x) - -- __pickle metamethod - local __pickle = (getmetatable (x) or {}).__pickle - if callable (__pickle) then - return __pickle (x) - elseif type (__pickle) == "string" then - return __pickle - end - - -- math - if x == nil then - return "nil" - elseif x ~= x then - return "0/0" - elseif x == math.huge then - return "math.huge" - elseif x == -math.huge then - return "-math.huge" - end - - -- common types - local type_x = type (x) - if type_x == "table" then - return pickle_table (x) - elseif type_x == "string" then - return _format ("%q", x) - elseif type_x == "number" or type_x == "boolean" then - return tostring (x) - end - - -- don't know what to do with this :( - die ("cannot pickle " .. tostring (x)) -end - - -function pickle_table (t) - local buf = {} - - -- sequence values, if any - local seq, i = {}, 1 - while t[i] ~= nil do - i, seq[i] = i + 1, pickle (t[i]) - end - if i > 1 then - buf[1] = table.concat (seq, ", ") - end - - -- hash values with keys, if any, after sequence value - local hash, i = {}, 1 - for k, v in pairs (t) do - if seq[k] == nil then - i, hash[i] = i + 1, "[" .. pickle (k) .. "] = " .. pickle (v) - end - end - if i > 1 then - buf[#buf + 1] = table.concat (hash, ", ") - end - - -- wrap in Lua table read-syntax - buf[1] = "{" .. (buf[1] or "") - buf[#buf] = buf[#buf] .. "}" - return table.concat (buf, "; ") -end - - local function prettytostring (x, indent, spacing) indent = indent or "\t" spacing = spacing or "" @@ -433,7 +366,16 @@ M = { -- @see std.eval -- @usage -- function slow_identity (x) return functional.eval (pickle (x)) end - pickle = pickle, + pickle = function (x) + local __pickle = (getmetatable (x) or {}).__pickle + if callable (__pickle) then + return __pickle (x) + elseif type (__pickle) == "string" then + return __pickle + end + + return pickle (x) + end, --- Pretty-print a table, or other object. -- @function prettytostring From cb47c442e5c3417e39390833d59c7edebafb72ed Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 2 Aug 2015 16:55:22 +0100 Subject: [PATCH 579/703] specs: check that pickle diagnoses unpicklable objects. * specs/string_spec.yaml (pickle): Specify expected diagnosis when given an unpicklable object. * lib/std/base.lua (pickle): Don't use out of scope command-line diagnosis functions here, throwing an error is the right thing to do in a library. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 2 +- specs/string_spec.yaml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 4a052b5..ef6fb3a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -352,7 +352,7 @@ local function pickle (x) end -- don't know what to do with this :( - die ("cannot pickle " .. tostring (x)) + error ("cannot pickle " .. tostring (x)) end diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 24c34ba..ae084cf 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -387,6 +387,8 @@ specify std.string: seq = {1, {{2, 3}, 4, {5}}} hash = {foo={[{5,42}]={bar=0}}} f = M.pickle + - it diagnoses unpicklable arguments: + expect (f (function () end)).to_error "cannot pickle function" - it converts a primitive to a representative string: expect (f (nil)).to_be "nil" expect (f (false)).to_be "false" From fa6c82a0834e5d614841d733ecdae23356504853 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 2 Aug 2015 18:26:23 +0100 Subject: [PATCH 580/703] string: present pickled table keys in a stable order. * specs/string_spec.yaml (pickle): Add an example of stable key order when pickling a table with several keys. * lib/std/base.lua (pickle_table): Sort hash rows of pickled string before concatenating and returning. * lib/std/string.lua (pickle): Improve and correct LDocs. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 1 + lib/std/string.lua | 7 +++++-- specs/string_spec.yaml | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index ef6fb3a..76163cf 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -376,6 +376,7 @@ function pickle_table (t) end end if i > 1 then + table.sort (hash) buf[#buf + 1] = table.concat (hash, ", ") end diff --git a/lib/std/string.lua b/lib/std/string.lua index 32f2cda..30188e1 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -359,13 +359,16 @@ M = { pad = X ("pad (string, int, ?string)", pad), --- Convert a value to a string. - -- The string can be passed to `functional.eval` to retrieve the value. + -- The string can be passed to `std.eval` to retrieve the value. + -- Only primitives for which `tostring` returns an evalable result, + -- and objects with a `__pickle` metamethod are picklable. -- @function pickle -- @param x object to pickle -- @treturn string reversible string rendering of *x* -- @see std.eval -- @usage - -- function slow_identity (x) return functional.eval (pickle (x)) end + -- freeze = std.functional.memoize (pickle) + -- thaw = function (x) return std.eval (x) end pickle = function (x) local __pickle = (getmetatable (x) or {}).__pickle if callable (__pickle) then diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index ae084cf..2d0c50b 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -419,6 +419,13 @@ specify std.string: expect (f {hash=42}).to_be '{["hash"] = 42}' - it roundtrips a hash: expect (unpickle (f {hash=42})).to_equal {hash=42} + - it returns hashs keys in a stable order: + expect (f { + _=95 , a=97, ["A1"]=65.49, A=65, ["["]=91, [" "]=32, + ["0"]=48, ["~"]=126, + }).should_be ('{[" "] = 32, ["0"] = 48, ["A"] = 65, ' .. + '["A1"] = 65.49, ["["] = 91, ["_"] = 95, ' .. + '["a"] = 97, ["~"] = 126}') - it converts a mixed table to a representative string: expect (f {"one", foo=3, 2}).to_be '{"one", 2; ["foo"] = 3}' - it roundtrips a mixed table: From ebe9285ea0d666c99c4b235145f6cba92b878679 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 3 Aug 2015 23:18:05 +0100 Subject: [PATCH 581/703] string: improve render and reuse for pickle and memoize. * specs/std_spec.yaml (tostring): Adjust for more compact output. * specs/string_spec.yaml (tostring): Likewise. (pickle): Adjust expected output for simplified output. (pickle returns hash keys in a stable order): Removed. Sorting pickled tables only slows down the pickler. (render): Adjust example to sort keys to enable comparing with known outputs. * lib/std/base.lua (pickle_table): Remove. (render): Pass previous key and value as new arguments to pair dispatch function. Delegate decision about whether an element is a 'terminal' that can be rendered immediately by the `elem` dispatch function using a new `term` dispatch function instead of hardcoding. (tostring): Output in a more compact format by skipping integer keys in sequences and initial sequence part of a mixed table, which requires sorting the output pairs by key. (pickle): Move from here... * lib/std/string.lua (pickle): ...to here, but reimplement over render using new `pair` and `term` facilities. Signed-off-by: Gary V. Vaughan --- NEWS.md | 32 ++++++ lib/std/base.lua | 134 ++++++++++-------------- lib/std/functional.lua | 15 ++- lib/std/string.lua | 225 ++++++++++++++++++++++++++++++----------- specs/std_spec.yaml | 4 +- specs/string_spec.yaml | 71 ++++++++----- 6 files changed, 310 insertions(+), 171 deletions(-) diff --git a/NEWS.md b/NEWS.md index cf26168..a762fe8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -52,6 +52,31 @@ and object prototype tables, the central `std.object.mapfields` instantiation function is much cleaner and faster. + - `std.string.render` now takes a table of named arguments as documented; + the `pairs` function is now supplied with the key and value of the + preceding key/value pair. There is also support for two new named + functions: `sort`, which can change the rendering order of keys; and + `term`, a predicate function to determine whether the argument can be + rendered directly by the `elem` function. + + There are sensible fallbacks for functions not passed in the new + function table. Among other advantages of this improved API, this + means render can be called without ceremony to perform basic object + rendering: + + ```lua + std.string.render (thing) + ``` + + - There are a few clients of the improved `std.string.render`; as before, + `std.string.prettytostring` shows the argument object with nicely + formatted and indented nested tables; `std.tostring` outputs a compact + display for quickly recognizing objects; `std.string.pickle` outputs + a `std.eval`able string that recreates an equivalent object to its + original argument; and finally, the default `std.functional.memoize` + normalizer outputs stable strings, as required to retrieve previously + calculated memoized results. + - New `std.tuple` object, for managing interned immutable nil-preserving tuples: @@ -87,6 +112,10 @@ - `std.table.len` has been deprecated in favour of `std.operator.len`, because it is not just for tables! + - `std.string.render` function arguments have been deprecated in favour + of a table of named functions backed by defaults. + + ### Bug fixes - `std.string.pickle` works with nested tables, and mutable keys. @@ -131,6 +160,9 @@ If `__len` is not present, or gives the same result as `maxn` then `npairs` continues to behave as in the previous release. + - The output format of `std.tostring` skips initial sequence keys in + the new compact format. + ## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] diff --git a/lib/std/base.lua b/lib/std/base.lua index 76163cf..53ac47f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -327,66 +327,6 @@ local function npairs (t) end -local pickle_table -- forward declaration - -local function pickle (x) - -- math - if x == nil then - return "nil" - elseif x ~= x then - return "0/0" - elseif x == math.huge then - return "math.huge" - elseif x == -math.huge then - return "-math.huge" - end - - -- common types - local type_x = type (x) - if type_x == "table" then - return pickle_table (x) - elseif type_x == "string" then - return string.format ("%q", x) - elseif type_x == "number" or type_x == "boolean" then - return tostring (x) - end - - -- don't know what to do with this :( - error ("cannot pickle " .. tostring (x)) -end - - -function pickle_table (t) - local buf = {} - - -- sequence values, if any - local seq, i = {}, 1 - while t[i] ~= nil do - i, seq[i] = i + 1, pickle (t[i]) - end - if i > 1 then - buf[1] = table.concat (seq, ", ") - end - - -- hash values with keys, if any, after sequence value - local hash, i = {}, 1 - for k, v in pairs (t) do - if seq[k] == nil then - i, hash[i] = i + 1, "[" .. pickle (k) .. "] = " .. pickle (v) - end - end - if i > 1 then - table.sort (hash) - buf[#buf + 1] = table.concat (hash, ", ") - end - - -- wrap in Lua table read-syntax - buf[1] = "{" .. (buf[1] or "") - buf[#buf] = buf[#buf] .. "}" - return table.concat (buf, "; ") -end - - local function collect (ifn, ...) local argt, r = {...}, {} if not callable (ifn) then @@ -431,6 +371,20 @@ local function reduce (fn, d, ifn, ...) end +local fallbacks = { + __index = { + open = function (x) return "{" end, + close = function (x) return "}" end, + elem = _G.tostring, + pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. "=" .. vstr end, + sep = function (x, kp, vp, kn, vn) return kp and kn and "," or "" end, + sort = function (keys) return keys end, + term = function (x) + return type (x) ~= "table" or getmetamethod (x, "__tostring") + end, + }, +} + -- Write pretty-printing based on: -- -- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators @@ -440,27 +394,37 @@ end -- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps -- Heavily modified by Simon Peyton Jones, Dec 96 -local function render (x, opencb, closecb, elemcb, paircb, sepcb, roots) +local function render (x, fns, roots) + fns = setmetatable (fns or {}, fallbacks) roots = roots or {} + local function stop_roots (x) - return roots[x] or render (x, opencb, closecb, elemcb, paircb, sepcb, copy (roots)) + return roots[x] or render (x, fns, copy (roots)) end - if type (x) ~= "table" or getmetamethod (x, "__tostring") then - return elemcb (x) + if fns.term (x) then + return fns.elem (x) + else - local buf, k_, v_ = { opencb (x) } -- pre-buffer table open - roots[x] = elemcb (x) -- initialise recursion protection + local buf, keys = {fns.open (x)}, {} -- pre-buffer table open + roots[x] = fns.elem (x) -- recursion protection + + for k in pairs (x) do -- collect keys + keys[#keys + 1] = k + end + keys = fns.sort (keys) - for _, k in ipairs (okeys (x)) do -- for ordered table members + local pair, sep = fns.pair, fns.sep + local kp, vp -- previous key and value + for _, k in ipairs (keys) do local v = x[k] - buf[#buf + 1] = sepcb (x, k_, v_, k, v) -- | buffer separator - buf[#buf + 1] = paircb (x, k, v, stop_roots (k), stop_roots (v)) - -- | buffer key/value pair - k_, v_ = k, v + buf[#buf + 1] = sep (x, kp, vp, k, v) -- | buffer << separator + buf[#buf + 1] = pair (x, kp, vp, k, v, stop_roots (k), stop_roots (v)) + -- | buffer << key/value pair + kp, vp = k, v end - buf[#buf + 1] = sepcb (x, k_, v_) -- buffer trailing separator - buf[#buf + 1] = closecb (x) -- buffer table close + buf[#buf + 1] = sep (x, kp, vp) -- buffer << trailing separator + buf[#buf + 1] = fns.close (x) -- buffer << table close return table.concat (buf) -- stringify buffer end @@ -535,15 +499,22 @@ local function require (module, min, too_big, pattern) end -local _tostring = _G.tostring - local function tostring (x) - return render (x, - function () return "{" end, - function () return "}" end, - _tostring, - function (_, _, _, is, vs) return is .."=".. vs end, - function (_, i, _, k) return i and k and "," or "" end) + return render (x, { + pair = function (x, kp, vp, k, v, kstr, vstr) + local type_k = type (k) + if k == 1 or type_k == "number" and k -1 == kp then + return vstr + end + return kstr .. "=" .. vstr + end, + + sort = function (keys) + -- need to sort numeric keys to be able to skip printing them. + table.sort (keys, keysort) + return keys + end, + }) end @@ -615,7 +586,6 @@ return { string = { escape_pattern = escape_pattern, - pickle = pickle, render = render, split = split, }, diff --git a/lib/std/functional.lua b/lib/std/functional.lua index fab1209..6833bad 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -15,6 +15,7 @@ local ielems, ipairs, ireverse, npairs, pairs = local copy = std.base.copy local callable, reduce = std.functional.callable, std.functional.reduce local len = std.operator.len +local render = std.string.render local unpack = std.table.unpack local loadstring = loadstring or load @@ -146,10 +147,18 @@ local function id (...) end +local function fallback (...) + return render ({...}, { + sort = function (keys) + table.sort (keys, keysort) + return keys + end, + }) +end + + local function memoize (fn, normalize) - if normalize == nil then - normalize = function (...) return std.tostring {...} end - end + normalize = normalize or fallback return setmetatable ({}, { __call = function (self, ...) diff --git a/lib/std/string.lua b/lib/std/string.lua index 30188e1..033ba88 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -17,9 +17,9 @@ local StrBuf = require "std.strbuf".prototype local getmetamethod, pairs = std.getmetamethod, std.pairs local callable = std.functional.callable -local copy = std.base.copy +local copy, keysort = std.base.copy, std.base.keysort local len = std.operator.len -local pickle, render = std.string.pickle, std.string.render +local render = std.string.render local insert = std.table.insert local type = type -- avoid mutual recursion between debug argument checker and string.__index @@ -168,57 +168,110 @@ local function numbertosi (n) end +local picklable = { + boolean = true, ["nil"] = true, number = true, string = true, +} + +local function pickle (x) + return render (x, { + term = function (x) + if picklable[type (x)] or getmetamethod (x, "__tostring") then + return true + elseif type (x) ~= "table" then + -- don't know what to do with this :( + error ("cannot pickle " .. _tostring (x)) + end + end, + + elem = function (x) + -- math + if x ~= x then + return "0/0" + elseif x == math.huge then + return "math.huge" + elseif x == -math.huge then + return "-math.huge" + elseif x == nil then + return "nil" + end + + -- common types + local type_x = type (x) + if type_x == "string" then + return string.format ("%q", x) + elseif type_x == "number" or type_x == "boolean" then + return _tostring (x) + end + end, + + pair = function (x, kp, vp, k, v, kstr, vstr) + return "[" .. kstr .. "]=" .. vstr + end, + }) +end + + local function prettytostring (x, indent, spacing) indent = indent or "\t" spacing = spacing or "" - return render (x, - function () - local s = spacing .. "{" - spacing = spacing .. indent - return s - end, - function () - spacing = string.gsub (spacing, indent .. "$", "") - return spacing .. "}" - end, - function (x) - if type (x) == "string" then - return _format ("%q", x) - else - return tostring (x) - end - end, - function (x, k, v, ks, vs) - local s = spacing - if type (k) ~= "string" or k:match "[^%w_]" then - s = s .. "[" - if type (k) == "table" then - s = s .. "\n" - end - s = s .. ks - 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 .. vs - return s - end, - function (_, k) - local s = "\n" - if k then - s = "," .. s - end - return s - end) + return render (x, { + open = function () + local s = spacing .. "{" + spacing = spacing .. indent + return s + end, + + close = function () + spacing = string.gsub (spacing, indent .. "$", "") + return spacing .. "}" + end, + + elem = function (x) + if type (x) == "string" then + return _format ("%q", x) + else + return tostring (x) + end + end, + + pair = function (x, _, _, k, v, kstr, vstr) + local s = spacing + if type (k) ~= "string" or k:match "[^%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, + + sort = function (keys) + table.sort (keys, keysort) + return keys + end, + }) end @@ -234,6 +287,9 @@ end --[[ ================= ]]-- +local DEPRECATIONMSG = require "std.debug".DEPRECATIONMSG + + local function X (decl, fn) return debug.argscheck ("std.string." .. decl, fn) end @@ -394,20 +450,33 @@ M = { -- detection will not work. -- @function render -- @param x object to convert to string - -- @tparam opentablecb open open table rendering function - -- @tparam closetablecb close close table rendering function - -- @tparam elementcb elem element rendering function - -- @tparam paircb pair pair rendering function - -- @tparam separatorcb sep separator rendering function - -- @tparam[opt] table roots accumulates table references to detect recursion + -- @tparam[opt] rendercbs fns default rendering function overrides -- @return string representation of *x* -- @usage - -- function tostring (x) - -- return render (x, lambda '="{"', lambda '="}"', tostring, - -- lambda '=_4.."=".._5', lambda '= _4 and "," or ""', - -- lambda '=","') + -- function tostablestring (x) + -- return render (x, { + -- sort = function (keys) + -- table.sort (keys, lambda "=tostring (_1) < tostring (_2)") + -- return keys + -- end, + -- }) -- end - render = X ("render (?any, func, func, func, func, func, ?table)", render), + render = X ("render (?any, ?table|func, ?func, ?func, ?func, ?func, ?table)", + function (x, opencb, closecb, elemcb, paircb, sepcb, roots) + if type (opencb) == "function" then + io.stderr:write (DEPRECATIONMSG ("41.3", + "multiple function arguments to 'std.string.render'", + "pass a table of named functions as the second parameter instead", 2)) + opencb = { + open = opencb, close = closecb, elem = elemcb, sep = sepcb, + pair = function (x, kp, vp, k, v, kstr, vstr) + return paircb (x, k, v, kstr, vstr) + end, + } + end + return render (x, opencb, roots) + end + ), --- Remove trailing matter from a string. -- @function rtrim @@ -505,6 +574,22 @@ return std.base.merge (M, string) --- Types -- @section Types +--- Table of default render callback functions. +-- @table rendercbs +-- @tfield[opt] opentablecb open open table rendering function +-- @tfield[opt] closetablecb close close table rendering function +-- @tfield[opt] elementcb elem element rendering function +-- @tfield[opt] paircb pair pair rendering function +-- @tfield[opt] separatorcb sep separator rendering function +-- @tfield[opt] sortcb sort key sorting function +-- @tfield[opt] termcb term terminal predicate +-- @see render +-- @usage +-- function tostringstable (x) +-- return render (x, { sort = some_sequence_reordering_fn }) +-- end + + --- Signature of @{render} open table callback. -- @function opentablecb -- @tparam table t table about to be rendered @@ -554,3 +639,21 @@ return std.base.merge (M, string) -- @treturn string separator rendering -- @usage -- function separator (_, _, _, fk) return fk and "," or "" end + + +--- Signature of @{render} key sorting callback. +-- @function sortcb +-- @tparam sequence keys all keys from rendering table +-- @treturn sequence *keys* in desired display order +-- @usage +-- function unsorted (keys) return keys end + + +--- Signature of @{render} terminal predicate callback. +-- @function termcb +-- @param x an element to be rendered +-- @treturn boolean whether *x* can be rendered by @{elementcb} +-- @usage +-- function term (x) +-- return type (x) ~= "table" or getmetamethod (x, "__tostring") +-- end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 9d4d329..f137cbf 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -591,10 +591,10 @@ specify std: expect (f {}).to_be ("{}") - it renders table array part compactly: expect (f {"one", "two", "five"}). - to_be '{1=one,2=two,3=five}' + to_be '{one,two,five}' - it renders a table dictionary part compactly: expect (f { one = true, two = 2, three = {3}}). - to_be '{one=true,three={1=3},two=2}' + to_be '{one=true,three={3},two=2}' - it renders table keys in table.sort order: expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }). to_be '{five=1,four=2,one=3,three=4,two=5}' diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 2d0c50b..c9fa555 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -408,32 +408,25 @@ specify std.string: expect (unpickle (f (42))).to_be (42) expect (unpickle (f "string")).to_be "string" - it converts a sequence to a representative string: - expect (f {"sequence", 42}).to_be '{"sequence", 42}' + expect (f {"sequence", 42}).to_be '{[1]="sequence",[2]=42}' - it roundtrips sequences: expect (unpickle (f {"sequence", 42})).to_equal {"sequence", 42} - it converts a nested sequence to a representative string: - expect (f (seq)).to_be "{1, {{2, 3}, 4, {5}}}" + expect (f (seq)). + to_be "{[1]=1,[2]={[1]={[1]=2,[2]=3},[2]=4,[3]={[1]=5}}}" - it roundtrips a nested sequence: expect (unpickle (f (seq))).to_equal (seq) - it converts a hash to a representative string: - expect (f {hash=42}).to_be '{["hash"] = 42}' + expect (f {hash=42}).to_be '{["hash"]=42}' - it roundtrips a hash: expect (unpickle (f {hash=42})).to_equal {hash=42} - - it returns hashs keys in a stable order: - expect (f { - _=95 , a=97, ["A1"]=65.49, A=65, ["["]=91, [" "]=32, - ["0"]=48, ["~"]=126, - }).should_be ('{[" "] = 32, ["0"] = 48, ["A"] = 65, ' .. - '["A1"] = 65.49, ["["] = 91, ["_"] = 95, ' .. - '["a"] = 97, ["~"] = 126}') - - it converts a mixed table to a representative string: - expect (f {"one", foo=3, 2}).to_be '{"one", 2; ["foo"] = 3}' + # there's no guarantee of ordering... - it roundtrips a mixed table: t = {"one", foo=3, 2} expect (unpickle (f (t))).to_equal (t) - it converts a nested hash to a representative string: expect (f (hash)). - to_be '{["foo"] = {[{5, 42}] = {["bar"] = 0}}}' + to_be '{["foo"]={[{[1]=5,[2]=42}]={["bar"]=0}}}' - it roundtrips a nested hash: expect (unpickle (f (hash))).to_equal (hash) @@ -480,26 +473,41 @@ specify std.string: - describe render: - before: - term = function (s) return function () return s end end - pair = function (_, _, _, i, v) return i .. "=" .. v end - sep = function (_, i, _, j) return (i and j) and "," or "" end r = function (x) - return M.render (x, term "{", term "}", tostring, pair, sep) - end + return M.render (x, { + sort = function (keys) + table.sort (keys, + function (a, b) return tostring (a) < tostring (b) end) + return keys + end, + }) + end t = {1, {{2, 3}, 4, {5}}} f = M.render + - it writes an argument passing deprecation warning: + around = function () return "|" end + inside = function () return "_" end + between = function () return "," end + + setdebug { deprecate = "nil" } + expect (capture (f, {around, around, inside, inside, between})). + to_contain_error "was deprecated" + setdebug { deprecate = false } + expect (capture (f, {around, around, inside, inside, between})). + not_to_contain_error "was deprecated" + - context with bad arguments: - badargs.diagnose (f, "std.string.render (?any, func, func, func, func, func, ?table)") + badargs.diagnose (f, "std.string.render (?any, ?table|func, ?func, ?func, ?func, ?func, ?table)") - it converts a primitive to a representative string: expect (r (nil)).to_be "nil" expect (r (false)).to_be "false" expect (r (42)).to_be "42" - expect (r ("string")).to_be "string" + expect (r "string").to_be "string" - it converts a table to a representative string: - expect (r ({"table", 42})).to_be '{1=table,2=42}' + expect (r {"table", 42}).to_be "{1=table,2=42}" - it converts a nested table to a representative string: expect (r (t)). to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}" @@ -507,6 +515,23 @@ specify std.string: t[1] = t expect (r (t)). to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") + - it supports the legacy api: + term = function (s) return function () return s end end + pair = function (_, _, _, i, v) return i .. "=" .. v end + sep = function (_, i, _, j) return i and j and "," or "" end + r = function (x) + return f (x, term "{", term "}", tostring, pair, sep) + end + expect (r (nil)).to_be "nil" + expect (r (false)).to_be "false" + expect (r (42)).to_be "42" + expect (r "string").to_be "string" + expect (r {"table", 42}).to_be "{1=table,2=42}" + expect (r (t)). + to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}" + t[1] = t + expect (r (t)). + to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") - describe require_version: @@ -683,10 +708,10 @@ specify std.string: expect (f {}).to_be ("{}") - it renders table array part compactly: expect (f {"one", "two", "five"}). - to_be '{1=one,2=two,3=five}' + to_be '{one,two,five}' - it renders a table dictionary part compactly: expect (f { one = true, two = 2, three = {3}}). - to_be '{one=true,three={1=3},two=2}' + to_be '{one=true,three={3},two=2}' - it renders table keys in table.sort order: expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }). to_be '{five=1,four=2,one=3,three=4,two=5}' From c29b96f777580ec85bcf6256058f70d99349f77a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 8 Aug 2015 18:36:37 +0100 Subject: [PATCH 582/703] operator: simplify and improve eqv with render. * specs/operator_spec.yaml (eqv): Improved behaviour is to correctly compare table keys for equivalence. * lib/std/functional.lua (fallback): Move from here... * lib/std/base.lua (mnemonic): ...to here. Use render to flatten nested keys and the like to provide a repeatable serialization. (_tostring): Be explicit with calls to core Lua tostring function. Adjust all callers. * lib/std/functional.lua (memoize): Use mnemonic as the default fallback argument serialization function. * lib/std/operator.lua (eqv): Use base.mnemonic to flatten nested tables and keys for comparison instead of using another custom table recursion algorithm. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 57 ++++++++++++++++++++++------------------ lib/std/base.lua | 35 ++++++++++++++++++------ lib/std/functional.lua | 26 +++++++----------- lib/std/operator.lua | 37 +++----------------------- specs/operator_spec.yaml | 6 ++--- 5 files changed, 75 insertions(+), 86 deletions(-) diff --git a/NEWS.md b/NEWS.md index a762fe8..1051e5c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,21 +10,7 @@ us organization-wise, but improvements and corrections to the content are always welcome! - - We used to have an object module method, `std.object.type`, which - often got imported using: - - ```lua - local prototype = require "std.object".type - ``` - - So we renamed it to `std.object.prototype` to avoid a name clash with - the `type` symbol, and subsequently deprecated the earlier equivalent - `type` method; but that was a mistake, because core Lua provides `type`, - and and `io.type` (and in recent releases, `math.type`). Now, for - orthogonality with core Lua, we're going back to using `std.object.type`, - because that just makes more sense. Sorry! - - - Similarly, for orthogonality with core Lua `type`, we also export the + - For orthogonality with core Lua `type`, we now export the `std.object.type` function as `std.type`. - Objects and Modules are no longer conflated - what you get back from @@ -68,14 +54,24 @@ std.string.render (thing) ``` - - There are a few clients of the improved `std.string.render`; as before, - `std.string.prettytostring` shows the argument object with nicely - formatted and indented nested tables; `std.tostring` outputs a compact - display for quickly recognizing objects; `std.string.pickle` outputs - a `std.eval`able string that recreates an equivalent object to its - original argument; and finally, the default `std.functional.memoize` - normalizer outputs stable strings, as required to retrieve previously - calculated memoized results. + - `std.tostring` uses the more powerful features of `std.string.render` + to return a more compact representation of table arguments, that uses + significantly less horizontal space for sequences. + + - `std.string.prettytostring` continues to use `std.string.render` for + more legible deeply nested table output, identically to previous + releases. + + - `std.string.pickle` uses the more powerful features of the improved + render function to return a `std.eval`able string that recreates an + equivalent object to the original argument more accurately than + before. + + - `std.functional.memoize` uses a fast stable render based serialization + call by default now, when the `mnemonic` parameter is not given. + + - `std.operator.eqv` now uses render to determine equivalence between + tables, which means it works correctly for table keys too. - New `std.tuple` object, for managing interned immutable nil-preserving tuples: @@ -106,8 +102,19 @@ ### Deprecations - - `std.object.prototype` has been deprecated in favor of - `std.object.type` for orthogonality with `io.type` and `math.type`. + - We used to have an object module method, `std.object.type`, which + often got imported using: + + ```lua + local prototype = require "std.object".type + ``` + + So we renamed it to `std.object.prototype` to avoid a name clash with + the `type` symbol, and subsequently deprecated the earlier equivalent + `type` method; but that was a mistake, because core Lua provides `type`, + and `io.type` (and in recent releases, `math.type`). So now, for + orthogonality with core Lua, we're going back to using `std.object.type`, + because that just makes more sense. Sorry! - `std.table.len` has been deprecated in favour of `std.operator.len`, because it is not just for tables! diff --git a/lib/std/base.lua b/lib/std/base.lua index 53ac47f..2b2b564 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -25,6 +25,7 @@ local dirsep = string.match (package.config, "^(%S+)\n") local loadstring = rawget (_G, "loadstring") or load +local _tostring = _G.tostring local type = type @@ -239,7 +240,7 @@ local function keysort (a, b) if type (a) == "number" then return type (b) ~= "number" or a < b else - return type (b) ~= "number" and tostring (a) < tostring (b) + return type (b) ~= "number" and _tostring (a) < _tostring (b) end end @@ -375,7 +376,7 @@ local fallbacks = { __index = { open = function (x) return "{" end, close = function (x) return "}" end, - elem = _G.tostring, + elem = _tostring, pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. "=" .. vstr end, sep = function (x, kp, vp, kn, vn) return kp and kn and "," or "" end, sort = function (keys) return keys end, @@ -431,6 +432,23 @@ local function render (x, fns, roots) end +local function mnemonic (...) + return render ({...}, { + elem = function (x) + if type (x) == "string" then + return string.format ("%q", x) + end + return _tostring (x) + end, + + sort = function (keys) + table.sort (keys, keysort) + return keys + end, + }) +end + + local function ripairs (t) local oob = 1 while t[oob] ~= nil do @@ -486,7 +504,7 @@ local _require = require local function require (module, min, too_big, pattern) local m = _require (module) - local v = tostring (type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") + local v = _tostring (type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") if min then assert (vcompare (v, min) >= 0, "require '" .. module .. "' with at least version " .. min .. ", but found version " .. v) @@ -545,11 +563,12 @@ return { end, base = { - copy = copy, - keysort = keysort, - last = last, - merge = merge, - raise = raise, + copy = copy, + keysort = keysort, + last = last, + merge = merge, + mnemonic = mnemonic, + raise = raise, }, debug = { diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 6833bad..bcb12b6 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -147,22 +147,14 @@ local function id (...) end -local function fallback (...) - return render ({...}, { - sort = function (keys) - table.sort (keys, keysort) - return keys - end, - }) -end - +local serialize = std.base.mnemonic -local function memoize (fn, normalize) - normalize = normalize or fallback +local function memoize (fn, mnemonic) + mnemonic = mnemonic or serialize return setmetatable ({}, { __call = function (self, ...) - local k = normalize (...) + local k = mnemonic (...) local t = self[k] if t == nil then t = {fn (...)} @@ -512,7 +504,7 @@ local M = { -- equivalencies. -- @function memoize -- @func fn pure function: a function with no side effects - -- @tparam[opt=std.tostring] normalize normfn function to normalize arguments + -- @tparam[opt=std.tostring] mnemonic mnemonicfn how to remember the arguments -- @treturn functable memoized function -- @usage -- local fast = memoize (function (...) --[[ slow code ]] end) @@ -642,12 +634,12 @@ return M --- Signature of a @{memoize} argument normalization callback function. --- @function normalize +-- @function mnemonic -- @param ... arguments --- @treturn string normalized arguments +-- @treturn string stable serialized arguments -- @usage --- local normalize = function (name, value, props) return name end --- local intern = std.functional.memoize (mksymbol, normalize) +-- local mnemonic = function (name, value, props) return name end +-- local intern = std.functional.memoize (mksymbol, mnemonic) --- Signature of a @{filter} predicate callback function. diff --git a/lib/std/operator.lua b/lib/std/operator.lua index b73df29..086910f 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -8,47 +8,18 @@ local std = require "std.base" local pairs, stdtype, tostring = std.pairs, std.type, std.tostring - +local serialize = std.base.mnemonic local function eqv (a, b) -- If they are the same primitive value, or they share a metatable -- with an __eq metamethod that says they are equivalent, we're done! if a == b then return true end - -- Unless we have two tables, what we have cannot be equivalent here. + -- Unless we now have two tables, the previous line ensures a ~= b. if type (a) ~= "table" or type (b) ~= "table" then return false end - local type_a, type_b = stdtype (a), stdtype (b) - if type_a ~= type_b then return false end - - local keyeqv = {} -- keys requiring recursive equivalence test - for k, v in pairs (a) do - if b[k] == nil then return false end - if v ~= b[k] then - if type (v) ~= "table" then return false end - -- Only require recursive comparisons for mismatched tables at k. - keyeqv[#keyeqv + 1] = k - end - end - - -- Any uncompared keys remaining in b denote a mismatch. - for k in pairs (b) do - if a[k] == nil then return false end - end - - if #keyeqv == 0 then return true end - if #keyeqv > 1 then - for _, k in ipairs (keyeqv) do - assert (a[k] ~= nil and b[k] ~= nil) - if not eqv (a[k], b[k]) then return false end - end - return true - end - - -- Use a tail call for arbitrary depth single table valued key - -- equivalence. - local _, k = next (keyeqv) - return eqv (a[k], b[k]) + -- Otherwise, compare serializations of each. + return serialize (a) == serialize (b) end diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index ac7a1aa..f2ac1c1 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -191,10 +191,10 @@ specify std.operator: expect (f ({1, {{2, 3}, 4}}, {1, {{2, 3}, 4}})).to_be (true) expect (f ({a=1, b={c={2, d=3}, 4}}, {a=1, b={c={2, d=3}, 4}})). to_be (true) - - it does not compare keys recursively: - expect (f ({[{a=1}]=2}, {[{a=1}]=2})).to_be (false) + - it compares keys recursively: + expect (f ({[{a=1}]=2}, {[{a=1}]=2})).to_be (true) expect (f ({[{a=1}]={[{2}]="b"}}, {[{a=1}]={[{2}]="b"}})). - to_be (false) + to_be (true) - it returns false if table lengths differ: expect (f ({1,2,3,4}, {1,2,3,4,5})).to_be (false) expect (f ({1,2,3,4}, {[0]=0,1,2,3,4})).to_be (false) From e8886f0c2d931260cd3c5ede6b3f86671aab14b4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 9 Aug 2015 14:36:30 +0100 Subject: [PATCH 583/703] maint: regenerate stdlib-git-1.rockspec. * stdlib-git-1.rockspec: Regenerate to pick up improvements from slingshot. Signed-off-by: Gary V. Vaughan --- stdlib-git-1.rockspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index f13aeb7..6ec4919 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -14,8 +14,8 @@ dependencies = { } external_dependencies = nil build = { - build_command = "./bootstrap && ./configure LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' --prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' && make clean all", + build_command = "LUA='$(LUA)' ./bootstrap && ./configure LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' --prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' --datarootdir='$(PREFIX)' && make clean all", copy_directories = {}, - install_command = "make install luadir='$(LUADIR)'", + install_command = "make install luadir='$(LUADIR)' luaexecdir='$(LIBDIR)'", type = "command", } From ffe94f861c040de4eda895da2b7224d6b2c0303d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 9 Aug 2015 15:15:33 +0100 Subject: [PATCH 584/703] functional: consider trailing nils passed to memoized functions. * specs/functional_spec.yaml (memoize): Provide more rigorous behaviours for nil arguments. * lib/std/base.lua (mnemonic): render each argument, including trailing nils, separately into a lookup key. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 ++ lib/std/base.lua | 31 +++++++++++-------- specs/functional_spec.yaml | 61 +++++++++++++++++++++++++++----------- 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1051e5c..3bb603c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -133,6 +133,9 @@ correctly, rather than `nil` as in previous releases. It's also considerably faster now that it doesn't use `pcall` any more. + - `std.functional.memoize` now considers trailing nil arguments when + looking up memoized value for those particular arguments. + ### Incompatible changes - Deprecated multi-argument `functional.bind` has been removed. diff --git a/lib/std/base.lua b/lib/std/base.lua index 2b2b564..b6c824d 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -433,19 +433,24 @@ end local function mnemonic (...) - return render ({...}, { - elem = function (x) - if type (x) == "string" then - return string.format ("%q", x) - end - return _tostring (x) - end, - - sort = function (keys) - table.sort (keys, keysort) - return keys - end, - }) + local seq, n = {...}, select ("#", ...) + local buf = {} + for i = 1, n do + buf[i] = render (seq[i], { + elem = function (x) + if type (x) == "string" then + return string.format ("%q", x) + end + return _tostring (x) + end, + + sort = function (keys) + table.sort (keys, keysort) + return keys + end, + }) + end + return table.concat (buf, ",") end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 4769a0e..f42a8b6 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -463,33 +463,58 @@ specify std.functional: - before: f = M.memoize - memfn = f (function (x) - if x then return {x} else return nil, "bzzt" end + memfn = f (function (...) + n = select ("#", ...) + if n == 0 then return nil, "bzzt" end + return n, {...} end) - context with bad arguments: badargs.diagnose (f, "std.functional.memoize (func, ?func)") - it propagates multiple return values: - expect (select (2, memfn (false))).to_be "bzzt" - - it returns the same object for the same arguments: - t = memfn (1) - expect (memfn (1)).to_be (t) + expect ((memfn ())).to_be (nil) + expect (select (2, memfn ())).to_be "bzzt" + - it propagates multiple arguments: + expect ({memfn ("a", 42, false)}).to_equal {3, {"a", 42, false}} + - it propagates nil arguments: + n, t = memfn (nil) + expect (t).to_equal ({}) + expect ({memfn (nil, 2, nil, nil)}).to_equal {4, {[2]=2}} + - it returns the same results for the same arguments: + n, t = memfn (1) + n, u = memfn (1) + expect (t).to_be (u) - it returns a different object for different arguments: - expect (memfn (1)).not_to_be (memfn (2)) + n, t = memfn (1) + n, u = memfn (1, 2) + expect (t).not_to_be (u) - it returns the same object for table valued arguments: - t = memfn {1, 2, 3} - expect (memfn {1, 2, 3}).to_be (t) - t = memfn {foo = "bar", baz = "quux"} - expect (memfn {foo = "bar", baz = "quux"}).to_be (t) - expect (memfn {baz = "quux", foo = "bar"}).to_be (t) + n, t = memfn {1, 2, 3} + n, u = memfn {1, 2, 3} + expect (t).to_be (u) + n, t = memfn {foo = "bar", baz = "quux"} + n, u = memfn {foo = "bar", baz = "quux"} + expect (t).to_be (u) + n, t = memfn {baz = "quux", foo = "bar"} + expect (t).to_be (u) - it returns a different object for different table arguments: - expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2}) - expect (memfn {1, 2, 3}).not_to_be (memfn {3, 1, 2}) - expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2, 3, 4}) - - it accepts alternative normalization function: - normalize = function (...) return select ("#", ...) end - memfn = f (function (x) return {x} end, normalize) + n, t = memfn {1, 2, 3} + n, u = memfn {1, 2} + expect (t).not_to_be (u) + n, u = memfn {3, 1, 2} + expect (t).not_to_be (u) + n, u = memfn {1, 2, 3, 4} + expect (t).not_to_be (u) + - it returns a different object for additional trailing nils: + n, t = memfn (1, nil) + n, u = memfn (1) + expect (t).not_to_be (u) + n, u = memfn (1, nil, nil) + expect (t).not_to_be (u) + - it accepts alternative mnemonic function: + mnemonic = function (...) return select ("#", ...) end + memfn = f (function (x) return {x} end, mnemonic) expect (memfn "same").to_be (memfn "not same") expect (memfn (1, 2)).to_be (memfn (false, "x")) expect (memfn "one").not_to_be (memfn ("one", "two")) From 27fb6cd85d49cd467365a208606dcdc1986ee857 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 9 Aug 2015 16:18:41 +0100 Subject: [PATCH 585/703] refactor: factor out and share common code for render callers. * lib/std/base.lua (_concat, _format, _sort, _tostring, _type): Cache table.concat, string.format, table.sort, _G.tostring and _G.type respectively, for a slight speed bump, and to make differentiating between use of core Lua functions over stdlib replacements. Adjust all clients. (sortkeys): Export this new function, factored out of... (okeys): ...here. (toqstring): Export this new function, factored out of... (mnemonic): ...here. (mnemonic_vtable, tostring_vtable): Create these once at load-time. (mnemonic, tostring): Simplify accordingly. * lib/std/string.lua (pickle_vtable): Create once at load-time, reusing sortkeys and toqstring functions appropriately. (pickle): Simplify accordingly. * lib/std/tuple.lua (argstr): Simplify with toqstring. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 116 ++++++++++++++++++++++++--------------------- lib/std/string.lua | 90 +++++++++++++++++------------------ lib/std/tuple.lua | 3 +- 3 files changed, 108 insertions(+), 101 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index b6c824d..c30925b 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -25,13 +25,15 @@ local dirsep = string.match (package.config, "^(%S+)\n") local loadstring = rawget (_G, "loadstring") or load -local _tostring = _G.tostring -local type = type +local _concat = table.concat +local _format = string.format +local _tostring = tostring +local _type = type local function raise (bad, to, name, i, extramsg, level) level = level or 1 - local s = string.format ("bad %s #%d %s '%s'", bad, i, to, name) + local s = _format ("bad %s #%d %s '%s'", bad, i, to, name) if extramsg ~= nil then s = s .. " (" .. extramsg .. ")" end @@ -46,7 +48,7 @@ end local function assert (expect, fmt, arg1, ...) - local msg = (arg1 ~= nil) and string.format (fmt, arg1, ...) or fmt or "" + local msg = (arg1 ~= nil) and _format (fmt, arg1, ...) or fmt or "" return expect or error (msg, 2) end @@ -64,7 +66,7 @@ end -- --> stdin:1: in main chunk -- --> [C]: in ? local function callable (x) - if type (x) == "function" then return x end + if _type (x) == "function" then return x end return (getmetatable (x) or {}).__call end @@ -76,7 +78,7 @@ end local function catfile (...) - return table.concat ({...}, dirsep) + return _concat ({...}, dirsep) end @@ -104,7 +106,7 @@ local _pairs = pairs 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 + if _type (k) == "number" and k > n then n = k end end return n end @@ -237,19 +239,26 @@ end -- Sort numbers first then asciibetically local function keysort (a, b) - if type (a) == "number" then - return type (b) ~= "number" or a < b + if _type (a) == "number" then + return _type (b) ~= "number" or a < b else - return type (b) ~= "number" and _tostring (a) < _tostring (b) + return _type (b) ~= "number" and _tostring (a) < _tostring (b) end end +local _sort = table.sort + +local function sortkeys (t) + _sort (t, keysort) + return t +end + + local function okeys (t) local r = {} for k in pairs (t) do r[#r + 1] = k end - table.sort (r, keysort) - return r + return sortkeys (r) end @@ -258,7 +267,7 @@ local function last (t) return t[len (t)] end local function leaves (it, tr) local function visit (n) - if type (n) == "table" then + if _type (n) == "table" then for _, v in it (n) do visit (v) end @@ -281,7 +290,7 @@ local function mapfields (obj, src, map) local k, v = next (src) while k do local key, dst = map[k] or k, obj - local kind = type (key) + local kind = _type (key) if kind == "string" and key:sub (1, 1) == "_" then mt[key] = v elseif next (map) and kind == "number" and len (dst) + 1 < key then @@ -381,7 +390,7 @@ local fallbacks = { sep = function (x, kp, vp, kn, vn) return kp and kn and "," or "" end, sort = function (keys) return keys end, term = function (x) - return type (x) ~= "table" or getmetamethod (x, "__tostring") + return _type (x) ~= "table" or getmetamethod (x, "__tostring") end, }, } @@ -427,30 +436,30 @@ local function render (x, fns, roots) buf[#buf + 1] = sep (x, kp, vp) -- buffer << trailing separator buf[#buf + 1] = fns.close (x) -- buffer << table close - return table.concat (buf) -- stringify buffer + return _concat (buf) -- stringify buffer end end +local function toqstring (x) + if _type (x) ~= "string" then return _tostring (x) end + return _format ("%q", x) +end + + +local mnemonic_vtable = { + elem = toqstring, + sort = sortkeys, +} + + local function mnemonic (...) local seq, n = {...}, select ("#", ...) local buf = {} for i = 1, n do - buf[i] = render (seq[i], { - elem = function (x) - if type (x) == "string" then - return string.format ("%q", x) - end - return _tostring (x) - end, - - sort = function (keys) - table.sort (keys, keysort) - return keys - end, - }) + buf[i] = render (seq[i], mnemonic_vtable) end - return table.concat (buf, ",") + return _concat (buf, ",") end @@ -509,7 +518,7 @@ local _require = require local function require (module, min, too_big, pattern) local m = _require (module) - local v = _tostring (type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") + local v = _tostring (_type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") if min then assert (vcompare (v, min) >= 0, "require '" .. module .. "' with at least version " .. min .. ", but found version " .. v) @@ -522,22 +531,21 @@ local function require (module, min, too_big, pattern) end +local tostring_vtable = { + pair = function (x, kp, vp, k, v, kstr, vstr) + if k == 1 or _type (k) == "number" and k -1 == kp then + return vstr + end + return kstr .. "=" .. vstr + end, + + -- need to sort numeric keys to be able to skip printing them. + sort = sortkeys, +} + + local function tostring (x) - return render (x, { - pair = function (x, kp, vp, k, v, kstr, vstr) - local type_k = type (k) - if k == 1 or type_k == "number" and k -1 == kp then - return vstr - end - return kstr .. "=" .. vstr - end, - - sort = function (keys) - -- need to sort numeric keys to be able to skip printing them. - table.sort (keys, keysort) - return keys - end, - }) + return render (x, tostring_vtable) end @@ -564,16 +572,18 @@ return { tostring = tostring, type = function (x) - return (getmetatable (x) or {})._type or io.type (x) or type (x) + return (getmetatable (x) or {})._type or io.type (x) or _type (x) end, base = { - copy = copy, - keysort = keysort, - last = last, - merge = merge, - mnemonic = mnemonic, - raise = raise, + copy = copy, + keysort = keysort, + last = last, + merge = merge, + mnemonic = mnemonic, + raise = raise, + sortkeys = sortkeys, + toqstring = toqstring, }, debug = { diff --git a/lib/std/string.lua b/lib/std/string.lua index 033ba88..294bcc0 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -15,6 +15,7 @@ local debug = require "std.debug" local StrBuf = require "std.strbuf".prototype +local sortkeys, toqstring = std.base.sortkeys, std.base.toqstring local getmetamethod, pairs = std.getmetamethod, std.pairs local callable = std.functional.callable local copy, keysort = std.base.copy, std.base.keysort @@ -172,42 +173,45 @@ local picklable = { boolean = true, ["nil"] = true, number = true, string = true, } -local function pickle (x) - return render (x, { - term = function (x) - if picklable[type (x)] or getmetamethod (x, "__tostring") then - return true - elseif type (x) ~= "table" then - -- don't know what to do with this :( - error ("cannot pickle " .. _tostring (x)) - end - end, +local pickle_vtable = { + term = function (x) + if picklable[type (x)] or getmetamethod (x, "__tostring") then + return true + elseif type (x) ~= "table" then + -- don't know what to do with this :( + error ("cannot pickle " .. _tostring (x)) + end + end, - elem = function (x) - -- math - if x ~= x then - return "0/0" - elseif x == math.huge then - return "math.huge" - elseif x == -math.huge then - return "-math.huge" - elseif x == nil then - return "nil" - end + elem = function (x) + -- math + if x ~= x then + return "0/0" + elseif x == math.huge then + return "math.huge" + elseif x == -math.huge then + return "-math.huge" + elseif x == nil then + return "nil" + end - -- common types - local type_x = type (x) - if type_x == "string" then - return string.format ("%q", x) - elseif type_x == "number" or type_x == "boolean" then - return _tostring (x) - end - end, + -- common types + local type_x = type (x) + if type_x == "string" then + return _format ("%q", x) + elseif type_x == "number" or type_x == "boolean" then + return _tostring (x) + end + end, - pair = function (x, kp, vp, k, v, kstr, vstr) - return "[" .. kstr .. "]=" .. vstr - end, - }) + pair = function (x, kp, vp, k, v, kstr, vstr) + return "[" .. kstr .. "]=" .. vstr + end, +} + + +local function pickle (x) + return render (x, pickle_vtable) end @@ -226,23 +230,18 @@ local function prettytostring (x, indent, spacing) return spacing .. "}" end, - elem = function (x) - if type (x) == "string" then - return _format ("%q", x) - else - return tostring (x) - end - end, + elem = toqstring, pair = function (x, _, _, k, v, kstr, vstr) + local type_k = type (k) local s = spacing - if type (k) ~= "string" or k:match "[^%w_]" then + if type_k ~= "string" or k:match "[^%w_]" then s = s .. "[" - if type (k) == "table" then + if type_k == "table" then s = s .. "\n" end s = s .. kstr - if type (k) == "table" then + if type_k == "table" then s = s .. "\n" end s = s .. "]" @@ -267,10 +266,7 @@ local function prettytostring (x, indent, spacing) return s end, - sort = function (keys) - table.sort (keys, keysort) - return keys - end, + sort = sortkeys, }) end diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 631b840..0a2da3e 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -29,6 +29,7 @@ local Container = require "std.container".prototype local std = require "std.base" local stdtype = std.type +local toqstring = std.base.toqstring --- Stringify tuple values, as a memoization key. @@ -38,7 +39,7 @@ local function argstr (tuple) local s = {} for i = 1, tuple.n do local v = tuple[i] - s[i] = (type (v) ~= "string" and "%s" or "%q"):format (tostring (v)) + s[i] = toqstring (v) end return table.concat (s, ", ") end From 240d521768c1f56b6c86277d9caf83aae07fbfa8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 9 Aug 2015 17:25:13 +0100 Subject: [PATCH 586/703] container: reimplement __tostring over render. * lib/std/container.lua (tostring_vtable): Create these once at load time. (prototype.__tostring): Replace custom code with a render invocation using tostring_vtable. * NEWS.md (Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 5 ++-- lib/std/container.lua | 65 +++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3bb603c..177b7a7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -157,7 +157,7 @@ ``` - Objects no longer honor mangling and stripping `_functions` tables - from objects during instantiation, instead move you actual object + from objects during instantiation, instead move your actual object into the module `prototype` field, and add the module functions to the parent table returned whn the module is required. @@ -171,7 +171,8 @@ `npairs` continues to behave as in the previous release. - The output format of `std.tostring` skips initial sequence keys in - the new compact format. + the new compact format, including stringification of Objects and + Containers using their `__tostring` metamethods. ## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] diff --git a/lib/std/container.lua b/lib/std/container.lua index d7edc30..7ab91f5 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -34,9 +34,13 @@ local _DEBUG = require "std.debug_init"._DEBUG local std = require "std.base" local debug = require "std.debug" +local copy = std.base.copy local ipairs, tostring = std.ipairs, std.tostring local mapfields = std.object.mapfields -local okeys = std.table.okeys +local render = std.string.render + +local _concat = table.concat +local _type = type @@ -74,6 +78,23 @@ local function instantiate (proto, t) end +local tostring_vtable = { + pair = function (x, kp, vp, k, v, kstr, vstr) + if k == 1 or _type (k) == "number" and k -1 == kp then return vstr end + return kstr .. "=" .. vstr + end, + + sep = function (x, kp, vp, kn, vn) + if kp == nil or kn == nil then return "" end + if _type (kp) == "number" and kn ~= kp + 1 then return "; " end + return ", " + end, + + sort = std.base.sortkeys, +} + + + --[[ ================= ]]-- --[[ Container Object. ]]-- --[[ ================= ]]-- @@ -145,7 +166,7 @@ local prototype = { k, v = next (self, k) end - if type (mt._init) == "function" then + if _type (mt._init) == "function" then obj = mt._init (obj, ...) else obj = (self.mapfields or mapfields) (obj, (...), mt._init) @@ -156,8 +177,8 @@ local prototype = { obj_mt = instantiate (mt, getmetatable (obj)) -- Merge object methods. - if type (obj_mt.__index) == "table" and - type ((mt or {}).__index) == "table" + if _type (obj_mt.__index) == "table" and + _type ((mt or {}).__index) == "table" then obj_mt.__index = instantiate (mt.__index, obj_mt.__index) end @@ -181,35 +202,11 @@ local prototype = { -- @usage -- assert (tostring (list) == 'Cons {car="head", cdr=Cons {car="tail"}}') __tostring = function (self) - local n, k_ = 1, nil - local buf = { getmetatable (self)._type, " {" } - for _, k in ipairs (okeys (self)) do -- for ordered public members - local v = self[k] - - if k_ ~= nil then -- | buffer separator - if k ~= n and type (k_) == "number" and k_ == n - 1 then - -- `;` separates `v` elements from `k=v` elements - buf[#buf + 1] = "; " - elseif k ~= nil then - -- `,` separator everywhere else - buf[#buf + 1] = ", " - end - end - - if type (k) == "number" and k == n then -- | buffer key/value pair - -- render initial array-like elements as just `v` - buf[#buf + 1] = tostring (v) - n = n + 1 - else - -- render remaining elements as `k=v` - buf[#buf + 1] = tostring (k) .. "=" .. tostring (v) - end - - k_ = k -- maintain loop invariant: k_ is previous key - end - buf[#buf + 1] = "}" -- buffer object close - - return table.concat (buf) -- stringify buffer + return _concat { + -- Pass a shallow copy to render to avoid triggering __tostring + -- again and blowing the stack. + getmetatable (self)._type, " ", render (copy (self), tostring_vtable), + } end, } @@ -224,7 +221,7 @@ if _DEBUG.argcheck then -- A function initialised object can be passed arguments of any -- type, so only argcheck non-function initialised objects. - if type (mt._init) ~= "function" then + if _type (mt._init) ~= "function" then local name, n = mt._type, select ("#", ...) -- Don't count `self` as an argument for error messages, because -- it just refers back to the object being called: `prototype {"x"}. From aeffb483df45e06d21639b8f92157688986ab22a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 9 Aug 2015 17:56:39 +0100 Subject: [PATCH 587/703] table: deprecate table.okeys for lack of utility. * specs/table_spec.yaml (okeys): Check for deprecation warning. * lib/std/base.lua (okeys): Move from here... * lib/std/table.lua (okeys): Deprecate. ...to here. * NEWS.md (Deprecations): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 7 +++++++ lib/std/base.lua | 8 -------- lib/std/table.lua | 18 +++++++++--------- specs/table_spec.yaml | 12 +++++++++--- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/NEWS.md b/NEWS.md index 177b7a7..048975f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -119,6 +119,13 @@ - `std.table.len` has been deprecated in favour of `std.operator.len`, because it is not just for tables! + - `std.table.okeys` has been deprecated for lack of utility. If you + still need it, use this instead: + + ```lua + local okeys = std.functional.compose (std.table.keys, std.table.sort) + ``` + - `std.string.render` function arguments have been deprecated in favour of a table of named functions backed by defaults. diff --git a/lib/std/base.lua b/lib/std/base.lua index c30925b..28ca63c 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -255,13 +255,6 @@ local function sortkeys (t) end -local function okeys (t) - local r = {} - for k in pairs (t) do r[#r + 1] = k end - return sortkeys (r) -end - - local function last (t) return t[len (t)] end @@ -628,7 +621,6 @@ return { insert = insert, invert = invert, maxn = maxn, - okeys = okeys, unpack = unpack, }, diff --git a/lib/std/table.lua b/lib/std/table.lua index e23d9f5..0c5887e 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -336,14 +336,6 @@ M = { -- @usage t = new (0) new = X ("new (?any, ?table)", new), - --- Make an ordered list of keys in table. - -- @function okeys - -- @tparam table t a table - -- @treturn table ordered list of keys from *t* - -- @see keys - -- @usage globals = keys (_G) - okeys = X ("okeys (table)", std.table.okeys), - --- Turn a tuple into a list. -- @function pack -- @param ... tuple @@ -424,7 +416,6 @@ M = { -- @function keys -- @tparam table t a table -- @treturn table list of keys from *t* - -- @see okeys -- @see values -- @usage globals = keys (_G) keys = X ("keys (table)", keys), @@ -489,6 +480,15 @@ M.metamethod = DEPRECATED ("41", "'std.table.metamethod'", "use 'std.getmetamethod' instead", std.getmetamethod) +M.okeys = DEPRECATED ("41.3", "'std.table.okeys'", + "compose 'std.table.keys' and 'std.table.sort' instead", + X ("okeys (table)", function (t) + local r = {} + for k in pairs (t) do r[#r + 1] = k end + return std.base.sortkeys (r) + end)) + + M.ripairs = DEPRECATED ("41", "'std.table.ripairs'", "use 'std.ripairs' instead", std.ripairs) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 4ec2a5a..1cbcde8 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -6,9 +6,9 @@ before: | extend_base = { "clone", "clone_select", "depair", "empty", "enpair", "flatten", "insert", "invert", "keys", "maxn", "merge", "merge_select", "monkey_patch", - "new", "okeys", "pack", "project", "remove", - "shape", "size", "sort", "unpack", "values" } - deprecations = { "len", "metamethod", "ripairs", "totable" } + "new", "pack", "project", "remove", "shape", + "size", "sort", "unpack", "values" } + deprecations = { "len", "okeys", "metamethod", "ripairs", "totable" } M = require "std.table" @@ -514,6 +514,12 @@ specify std.table: f = M.okeys + - it writes a deprecation warning: + setdebug { deprecate = "nil" } + expect (capture (f, {{}})).to_contain_error "was deprecated" + setdebug { deprecate = false } + expect (capture (f, {{}})).not_to_contain_error "was deprecated" + - context with bad arguments: badargs.diagnose (f, "std.table.okeys (table)") From 60742a85123ea5cbb713edca55cad035fe385543 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 9 Aug 2015 19:11:27 +0100 Subject: [PATCH 588/703] string: render separators around false valued keys correctly. * specs/string_spec.yaml (render): Demonstrate correct behaviours with false valued keys and elements. * lib/std/base.lua (fallbacks.sep): Use explicit nil comparison to fix a rendering bug with false-valued keys. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 4 +++- specs/string_spec.yaml | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 28ca63c..de8c6a8 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -380,7 +380,9 @@ local fallbacks = { close = function (x) return "}" end, elem = _tostring, pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. "=" .. vstr end, - sep = function (x, kp, vp, kn, vn) return kp and kn and "," or "" end, + sep = function (x, kp, vp, kn, vn) + return kp ~= nil and kn ~= nil and "," or "" + end, sort = function (keys) return keys end, term = function (x) return _type (x) ~= "table" or getmetamethod (x, "__tostring") diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index c9fa555..1d2ce29 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -506,11 +506,14 @@ specify std.string: expect (r (false)).to_be "false" expect (r (42)).to_be "42" expect (r "string").to_be "string" - - it converts a table to a representative string: - expect (r {"table", 42}).to_be "{1=table,2=42}" - - it converts a nested table to a representative string: + - it converts a sequence to a representative string: + expect (r {false, "table", 42}).to_be "{1=false,2=table,3=42}" + - it converts a nested sequence to a representative string: expect (r (t)). to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}" + - it converts a hash to a representative string: + expect (r {[false]=true, hash="table", answer=42}). + to_be "{answer=42,false=true,hash=table}" - it converts a recursive table to a representative string: t[1] = t expect (r (t)). @@ -526,7 +529,7 @@ specify std.string: expect (r (false)).to_be "false" expect (r (42)).to_be "42" expect (r "string").to_be "string" - expect (r {"table", 42}).to_be "{1=table,2=42}" + expect (r {false, "table", 42}).to_be "{1=false,2=table,3=42}" expect (r (t)). to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}" t[1] = t From 4bebf3e2b6ecc7e86314071f189a587e5608338d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 11 Aug 2015 00:45:39 +0100 Subject: [PATCH 589/703] string: argcheck pickle. * specs/string_spec.yaml (pickle): Exercise argument checking. * lib/std/string.lua (pickle): Implement argument checking. Signed-off-by: Gary V. Vaughan --- lib/std/string.lua | 11 +---------- specs/string_spec.yaml | 4 ++++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/std/string.lua b/lib/std/string.lua index 294bcc0..825f0e1 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -421,16 +421,7 @@ M = { -- @usage -- freeze = std.functional.memoize (pickle) -- thaw = function (x) return std.eval (x) end - pickle = function (x) - local __pickle = (getmetatable (x) or {}).__pickle - if callable (__pickle) then - return __pickle (x) - elseif type (__pickle) == "string" then - return __pickle - end - - return pickle (x) - end, + pickle = X ("pickle (?any)", pickle), --- Pretty-print a table, or other object. -- @function prettytostring diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 1d2ce29..c688a78 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -387,6 +387,10 @@ specify std.string: seq = {1, {{2, 3}, 4, {5}}} hash = {foo={[{5,42}]={bar=0}}} f = M.pickle + + - context with bad arguments: + badargs.diagnose (f, "std.string.pickle (?any)") + - it diagnoses unpicklable arguments: expect (f (function () end)).to_error "cannot pickle function" - it converts a primitive to a representative string: From 55e6fcf6704048bd611dfb72a1f27609635a289b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 11 Aug 2015 00:49:51 +0100 Subject: [PATCH 590/703] refactor: move string.pickle to std.base. * lib/std/string.lua (picklable, pickle_vtable, pickle): Move from here... * lib/std/base.lua (picklable, pickle_vtable, pickle): ...to here. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 52 ++++++++++++++++++++++++++++++++++++++++++++++ lib/std/string.lua | 48 +----------------------------------------- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index de8c6a8..27323f6 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -458,6 +458,57 @@ local function mnemonic (...) end +local picklable = { + boolean = true, ["nil"] = true, number = true, string = true, +} + +local pickle_vtable = { + term = function (x) + local type_x = type (x) + if picklable[type_x] or getmetamethod (x, "__pickle") then + return true + elseif type (x) ~= "table" then + -- don't know what to do with this :( + error ("cannot pickle " .. _tostring (x)) + end + end, + + elem = function (x) + -- math + if x ~= x then + return "0/0" + elseif x == math.huge then + return "math.huge" + elseif x == -math.huge then + return "-math.huge" + elseif x == nil then + return "nil" + end + + -- common types + local type_x = type (x) + if type_x == "string" then + return _format ("%q", x) + elseif type_x == "number" or type_x == "boolean" then + return _tostring (x) + end + + -- pickling metamethod + local __pickle = getmetamethod (x, "__pickle") + if __pickle then return __pickle (x) end + end, + + pair = function (x, kp, vp, k, v, kstr, vstr) + return "[" .. kstr .. "]=" .. vstr + end, +} + + +local function pickle (x) + return render (x, pickle_vtable) +end + + local function ripairs (t) local oob = 1 while t[oob] ~= nil do @@ -615,6 +666,7 @@ return { string = { escape_pattern = escape_pattern, + pickle = pickle, render = render, split = split, }, diff --git a/lib/std/string.lua b/lib/std/string.lua index 825f0e1..533dfc1 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -169,52 +169,6 @@ local function numbertosi (n) end -local picklable = { - boolean = true, ["nil"] = true, number = true, string = true, -} - -local pickle_vtable = { - term = function (x) - if picklable[type (x)] or getmetamethod (x, "__tostring") then - return true - elseif type (x) ~= "table" then - -- don't know what to do with this :( - error ("cannot pickle " .. _tostring (x)) - end - end, - - elem = function (x) - -- math - if x ~= x then - return "0/0" - elseif x == math.huge then - return "math.huge" - elseif x == -math.huge then - return "-math.huge" - elseif x == nil then - return "nil" - end - - -- common types - local type_x = type (x) - if type_x == "string" then - return _format ("%q", x) - elseif type_x == "number" or type_x == "boolean" then - return _tostring (x) - end - end, - - pair = function (x, kp, vp, k, v, kstr, vstr) - return "[" .. kstr .. "]=" .. vstr - end, -} - - -local function pickle (x) - return render (x, pickle_vtable) -end - - local function prettytostring (x, indent, spacing) indent = indent or "\t" spacing = spacing or "" @@ -421,7 +375,7 @@ M = { -- @usage -- freeze = std.functional.memoize (pickle) -- thaw = function (x) return std.eval (x) end - pickle = X ("pickle (?any)", pickle), + pickle = X ("pickle (?any)", std.string.pickle), --- Pretty-print a table, or other object. -- @function prettytostring From f23317c883255dd5294d845a04b7648fb2dfce71 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 11 Aug 2015 00:55:40 +0100 Subject: [PATCH 591/703] specs: remove use of deprecated std.object.type call. * specs/tuple_spec.yaml (objtype): Fallback on implementation in specs/spec_helpers.lua, rather than using the deprecated object type method. Signed-off-by: Gary V. Vaughan --- specs/tuple_spec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/tuple_spec.yaml b/specs/tuple_spec.yaml index 061f0ea..c103790 100644 --- a/specs/tuple_spec.yaml +++ b/specs/tuple_spec.yaml @@ -1,5 +1,4 @@ before: - objtype = require "std.object".type Tuple = require "std.tuple".prototype t0, t1, t2 = Tuple (), Tuple "one", Tuple (false, true) From fcdd3d42defbd7262cbf86dadcaf503c6842e001 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 11 Aug 2015 20:39:41 +0100 Subject: [PATCH 592/703] set: change the `_type` field like other table init objects. * specs/set_spec.yaml (serves as a prototype for new types): Specify correct behaviour when passed a `_type` field when initialized. * lib/std/set.lua (prototype): Handle `_` prefixed fields like Container and Object based prototypes. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++++ lib/std/set.lua | 26 ++++++++++++++++++++------ specs/set_spec.yaml | 6 +++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 048975f..9193885 100644 --- a/NEWS.md +++ b/NEWS.md @@ -143,6 +143,10 @@ - `std.functional.memoize` now considers trailing nil arguments when looking up memoized value for those particular arguments. + - You can now derive other types from `std.set` by passing a `_type` + field in the init argument, just like the other table argument + objects. + ### Incompatible changes - Deprecated multi-argument `functional.bind` has been removed. diff --git a/lib/std/set.lua b/lib/std/set.lua index 8a6c6ab..ecd4348 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -21,10 +21,17 @@ @prototype std.set ]] +local _concat = table.concat +local _sort = table.sort +local _type = type + local std = require "std.base" local Container = require "std.container".prototype -local ielems, pairs, type = std.ielems, std.pairs, std.type +local pairs = std.pairs +local pickle = std.string.pickle +local tostring = std.tostring +local type = std.type local prototype -- forward declaration @@ -146,16 +153,23 @@ prototype = Container { --- Set object initialisation. -- - -- Returns table partially initialised Set container with contents + -- Returns partially initialised Set container with contents -- from *t*. -- @init prototype._init -- @tparam table new uninitialised Set container object -- @tparam table t initialisation table from `__call` _init = function (new, t) - for e in ielems (t) do - insert (new, e) + local mt = {} + for k, v in pairs (t) do + local type_k = _type (k) + if type_k == "number" then + insert (new, v) + elseif type_k == "string" and k:sub (1, 1) == "_" then + mt[k] = v + end + -- non-underscore-prefixed string keys are discarded! end - return new + return next (mt) and setmetatable (new, mt) or new end, --- Metamethods @@ -217,7 +231,7 @@ prototype = Container { -- ispropersubset = this < s __lt = proper_subset, - -- Return a string representation of this set. + --- Return a string representation of this set. -- @function prototype:__tostring -- @treturn string string representation of a set. -- @see std.tostring diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index b9227dd..48f981f 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -23,7 +23,11 @@ specify std.set: expect (objtype (obj)).to_be "Set" expect (obj).to_equal (s) expect (getmetatable (obj)).to_be (getmetatable (s)) - + - it serves as a prototype for new types: + Bag = Set { _type = "Bag" } + expect (objtype (Bag)).to_be "Bag" + bag = Bag {"goo", "gar", "gar"} + expect (objtype (bag)).to_be "Bag" - describe delete: - context when called as a Set module function: From 08dd3d75ec19330eb66e6b860d3d265867d0356d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 11 Aug 2015 23:30:14 +0100 Subject: [PATCH 593/703] string: objects all provide __pickle metamethod. * specs/container_spec.yaml, specs/list_spec.yaml, specs/object_spec.yaml, specs/set_spec.yaml, specs/strbuf_spec.yaml, specs/tree_spec.yaml, specs/tuple_spec.yaml: Specify correct behaviours when pickling these objects, and examples of roundtripping a pickled object back to an equivalent Lua object. * lib/std/container.lua (prototype.__module): Name of the module that can construct this object. (prototype:__call): Manage __module and __type metafields when cloning from this one, taking care to support both old style `_type = "Name"` objects, and new style `__type = "module.Name"` objects. (prototype:__pickle): Use a `require` statement with the object `_module` metafield string when available for a self-contained pickle format, or fall back to `_type` call for objects that have not been updated yet. * lib/std/set.lua (prototype._type): Prepend 'std.list' module path. (prototype:__pickle): Similar old/new pickle string output, with sorted unique elements. * lib/std/list.lua (prototype._type): Prepend module path string. * lib/std/object.lua, lib/std/strbuf.lua, lib/std/tree.lua, lib/std/tuple.lua (prototype._type): Likewise. * lib/std/tuple.lua (prototype:__pickle): Old/new pickle string again, but using parentheses instead of braces to match read syntax of a Tuple object constructor. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 ++ lib/std/container.lua | 67 ++++++++++++++++++++++++++++++---- lib/std/list.lua | 2 +- lib/std/object.lua | 2 +- lib/std/set.lua | 32 +++++++++++++++-- lib/std/strbuf.lua | 2 +- lib/std/tree.lua | 2 +- lib/std/tuple.lua | 27 ++++++++++++-- specs/container_spec.yaml | 75 +++++++++++++++++++++++++++++++++++++-- specs/list_spec.yaml | 47 ++++++++++++++++++++++++ specs/object_spec.yaml | 47 ++++++++++++++++++++++++ specs/set_spec.yaml | 47 ++++++++++++++++++++++++ specs/strbuf_spec.yaml | 47 ++++++++++++++++++++++++ specs/tree_spec.yaml | 48 +++++++++++++++++++++++++ specs/tuple_spec.yaml | 28 ++++++++++++++- 15 files changed, 457 insertions(+), 19 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9193885..4563076 100644 --- a/NEWS.md +++ b/NEWS.md @@ -67,6 +67,9 @@ equivalent object to the original argument more accurately than before. + - All of stdlib's object prototypes now provide a `__pickle` metamethod, + which makes them picklable with `std.string.pickle` too! + - `std.functional.memoize` uses a fast stable render based serialization call by default now, when the `mnemonic` parameter is not given. diff --git a/lib/std/container.lua b/lib/std/container.lua index 7ab91f5..abfbd57 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -29,6 +29,12 @@ ]] +local _concat = table.concat +local _find = string.find +local _lower = string.lower +local _sub = string.sub +local _type = type + local _DEBUG = require "std.debug_init"._DEBUG local std = require "std.base" @@ -37,11 +43,9 @@ local debug = require "std.debug" local copy = std.base.copy local ipairs, tostring = std.ipairs, std.tostring local mapfields = std.object.mapfields +local pickle = std.string.pickle local render = std.string.render -local _concat = table.concat -local _type = type - --[[ ================= ]]-- @@ -115,7 +119,8 @@ local tostring_vtable = { -- local g = Graph { "node1", "node2" } -- assert (nodes (g) == 2) local prototype = { - _type = "Container", + _module = "std.container", -- for pickle() + _type = "Container", -- for tostring() and type() --- Metamethods -- @section metamethods @@ -174,6 +179,16 @@ local prototype = { -- If a metatable was set, then merge our fields and use it. if next (getmetatable (obj) or {}) then + local new_mt = getmetatable (obj) + local new_type = new_mt._type or "" + local i = _find ("." .. new_type, "%.[^%.]*$") + if i > 1 then + -- expand long-form type. + new_mt._type = _sub (new_type, i) + new_mt._module = _sub (new_type, 1, i -2) + end + + -- Merge fields. obj_mt = instantiate (mt, getmetatable (obj)) -- Merge object methods. @@ -182,13 +197,18 @@ local prototype = { then obj_mt.__index = instantiate (mt.__index, obj_mt.__index) end + + -- Invalidate obsoleted _module field + if new_mt._type ~= nil and new_mt._module == nil then + obj_mt._module = nil + end end return setmetatable (obj, obj_mt) end, - --- Return a string representation of this object. + --- Return a compact string representation of this object. -- -- First the container name, and then between { and } an ordered list -- of the array elements of the contained values with numeric keys, @@ -205,7 +225,42 @@ local prototype = { return _concat { -- Pass a shallow copy to render to avoid triggering __tostring -- again and blowing the stack. - getmetatable (self)._type, " ", render (copy (self), tostring_vtable), + getmetatable (self)._type, + " ", + render (copy (self), tostring_vtable), + } + end, + + + --- Return a loadable serialization of this object, where possible. + -- + -- If the object contains an unpicklable element (e.g. a userdata with + -- no `__pickle` metamethod) then neither is the entire container + -- picklable, and an error will be raised. + -- + -- Assuming the object metatable carries a correct `_module` field, + -- (either set manually when the prototype was created, or else because + -- the long form `_type` field was provided) that module path will be + -- required when the pickled object is evaluated. Otherwise, the bare + -- `_type` string is used and you will be responsible for setting that + -- to the correct object prototype before evaluating a pickled object. + -- @function prototype:__pickle + -- @treturn string pickled object representation + -- @see std.string.pickle + __pickle = function (self) + local mt = getmetatable (self) + if _type (mt._module) == "string" then + -- object with _module set + return _concat { + 'require "', + mt._module, + '".prototype ', + pickle (copy (self)), + } + end + -- rely on caller preloading `local ObjectName = require "obj".prototype` + return _concat { + mt._type, " ", pickle (copy (self)), } end, } diff --git a/lib/std/list.lua b/lib/std/list.lua index 38ff771..9557fb3 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -273,7 +273,7 @@ end -- local List = require "std.list".prototype -- assert (std.type (List) == "List") prototype = Object { - _type = "List", + _type = "std.list.List", --- Metamethods -- @section metamethods diff --git a/lib/std/object.lua b/lib/std/object.lua index 1fe44c2..30d1626 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -49,7 +49,7 @@ end -- command = pipeline[pid], -- manual assignment -- } local prototype = Container { - _type = "Object", + _type = "std.object.Object", --- Metamethods -- @section metamethods diff --git a/lib/std/set.lua b/lib/std/set.lua index ecd4348..ecf9b40 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -149,7 +149,7 @@ end -- local Set = require "std.set".prototype -- assert (std.type (Set) == "Set") prototype = Container { - _type = "Set", + _type = "std.set.Set", --- Set object initialisation. -- @@ -240,8 +240,34 @@ prototype = Container { for k in pairs (self) do keys[#keys + 1] = tostring (k) end - table.sort (keys) - return type (self) .. " {" .. table.concat (keys, ", ") .. "}" + _sort (keys) + return type (self) .. " {" .. _concat (keys, ", ") .. "}" + end, + + --- Return a loadable serialization of this object, where possible. + -- @function prototype:__pickle + -- @treturn string pickled object representation + -- @see std.string.pickle + __pickle = function (self) + local mt, keys = getmetatable (self), {} + for k in pairs (self) do + keys[#keys + 1] = pickle (k) + end + _sort (keys) + if _type (mt._module) == "string" then + -- object with _module set + return _concat { + 'require "', + mt._module, + '".prototype {', + _concat (keys, ","), + "}", + } + end + -- rely on caller preloading `local ObjName = require "module".prototype` + return _concat { + mt._type, " {", _concat (keys, ","), "}" + } end, } diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 999e423..83aa1bd 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -104,7 +104,7 @@ M.tostring = DEPRECATED ("41.1", "std.strbuf.tostring", local prototype = Object { - _type = "StrBuf", + _type = "std.strbuf.StrBuf", --- Metamethods -- @section metamethods diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 3784b11..450a182 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -132,7 +132,7 @@ end -- io.write (leaf .. "\t") -- end prototype = Container { - _type = "Tree", + _type = "std.tree.Tree", --- Metamethods -- @section metamethods diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 0a2da3e..01649ff 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -25,10 +25,15 @@ @prototype std.tuple ]] +local _concat = table.concat +local _format = string.format +local _type = type + local Container = require "std.container".prototype local std = require "std.base" -local stdtype = std.type +local pickle = std.string.pickle +local type = std.type local toqstring = std.base.toqstring @@ -86,7 +91,7 @@ local intern = setmetatable ({}, { -- count (false) --> 1 -- count (false, nil, true, nil) --> 4 local prototype = Container { - _type = "Tuple", + _type = "std.tuple.Tuple", _init = function (obj, ...) return intern (...) @@ -134,7 +139,23 @@ local prototype = Container { -- -- 'Tuple ("nil", nil, false)' -- print (Tuple ("nil", nil, false)) __tostring = function (self) - return ("%s (%s)"):format (stdtype (self), argstr (self)) + return _format ("%s (%s)", type (self), argstr (self)) + end, + + --- Return a loadable serialization of this object, where possible. + -- @function prototype:__pickle + -- @treturn string pickled object representataion + -- @see std.string.pickle + __pickle = function (self) + local mt, vals = getmetatable (self), {} + for i = 1, self.n do + vals[i] = pickle (self[i]) + end + if _type (mt._module) == "string" then + return _format ('require "%s".prototype (%s)', + mt._module, _concat (vals, ",")) + end + return _format ("%s (%s)", mt._type, _concat (vals, ",")) end, } diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 917cd9b..8afff15 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -49,10 +49,22 @@ specify std.container: things = Container {foo="bar", _baz="quux"} expect (getmetatable (things)).not_to_be (getmetatable (Container)) expect (getmetatable (things)._baz).to_be "quux" + - it propagates '_type' and '_module' fields: + things = Container {1} + u, v = things {"u"}, things {"v"} + expect (objtype (u)).to_be "Container" + expect (getmetatable (u)._module).to_be "std.container" + expect (objtype (v)).to_be (objtype (Container)) + expect (getmetatable (v)._module). + to_be (getmetatable (Container)._module) + - it sets '_type' and '_module' from long prototype '_type' field: + things = Container {_type = "specs.example.Things"} + expect (objtype (things)).to_be "Things" + expect (getmetatable (things)._module).to_be "specs.example" - context with module functions: - before: Bag = require "std.base".object.Module { - prototype = Container { _type = "Bag" }, + prototype = Container { _type = "Bag", _module = "specs.example" }, count = function (bag) local n = 0 for _, m in pairs (bag) do n = n + m end @@ -74,6 +86,18 @@ specify std.container: - it does allow elements named after module functions: things = Bag { count = 1337 } expect (Bag.count (things)).to_be (1337) + - it propagates '_type' and '_module' fields: + things = Bag { bananas=0 } + u, v = things { bananas=1 }, things { coconuts=0 } + expect (objtype (u)).to_be "Bag" + expect (getmetatable (u)._module).to_be "specs.example" + expect (objtype (v)).to_be (objtype (Bag.prototype)) + expect (getmetatable (v)._module). + to_be (getmetatable (Bag.prototype)._module) + - it sets '_type' and '_module' from long prototype '_type' field: + things = Bag {_type = "specs.example.Things"} + expect (objtype (things)).to_be "Things" + expect (getmetatable (things)._module).to_be "specs.example" - describe field access: @@ -94,7 +118,7 @@ specify std.container: expect (things.new).to_be "value" -- describe stringification: +- describe __tostring: - before: things = Container {_type = "Derived", "one", "two", "three"} - it returns a string: @@ -115,3 +139,50 @@ specify std.container: not_to_contain ";" expect (tostring (things {one = true, two = true, three = true})). to_contain ";" + + +- describe __pickle: + - before: + loadstring = loadstring or load + function unpickle (s) return loadstring ("return " .. s) () end + f = require "std.string".pickle + + Derived = Container {_type = "Derived"} + things = Derived {one="two", "three"} + + - it returns a string: + expect (type (f (things))).to_be "string" + - it resets '_module' metafield when '_type' changes: + expect (getmetatable (things)._module).to_be (nil) + - it supports setting '_module' and '_type' metafields: + Everything = Derived { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + Everything = Container { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + - it does not have a period in '_type': + Period = Derived { _type = "specs.example.Period" } + expect (string.find (type (Period), "%.")).to_be (nil) + - it propagates the '_module' metafield when '_type' is unchanged: + expect (getmetatable (Container {1})._module). + to_be (getmetatable (Container)._module) + - context with '_module' metafield: + - it requires the module path for later invocation: + expect (f (Container)).to_be 'require "std.container".prototype {}' + - it roundtrips objects: + expect (unpickle (f (Container))).to_equal (Container) + - context with '_type' metafield only: + - it uses the '_type' value for later invocation: + expect (f (things)).to_be 'Derived {[1]="three",["one"]="two"}' + - it roundtrips objects: + compat = require "specl.compat" + compat.setfenv (unpickle, compat.getfenv (1)) + expect (unpickle (f (things))).to_equal (things) + - it converts a nested object to a representative string: + container = Container {Derived {"?"}, ["!"]=42} + expect (f (container)). + to_be 'require "std.container".prototype {[1]=Derived {[1]="?"},["!"]=42}' + - it roundtrips nested objects: + container = Container {Derived {"?"}, ["!"]=42} + expect (unpickle (f (container))).to_equal (container) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 4e72523..85bb28c 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -1134,3 +1134,50 @@ specify std.list: expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) - it combines column entries with a function: expect (f (l, fn)).to_equal (List {135, 24}) + + +- describe __pickle: + - before: + loadstring = loadstring or load + function unpickle (s) return loadstring ("return " .. s) () end + f = require "std.string".pickle + + Derived = List {_type = "Derived"} + things = Derived {false, "str", 42} + + - it returns a string: + expect (type (f (things))).to_be "string" + - it resets '_module' metafield when '_type' changes: + expect (getmetatable (things)._module).to_be (nil) + - it supports setting '_module' and '_type' metafields: + Everything = Derived { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + Everything = List { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + - it does not have a period in '_type': + Period = Derived { _type = "specs.example.Period" } + expect (string.find (type (Period), "%.")).to_be (nil) + - it propagates the '_module' metafield when '_type' is unchanged: + expect (getmetatable (List {1})._module). + to_be (getmetatable (List)._module) + - context with '_module' metafield: + - it requires the module path for later invocation: + expect (f (List)).to_be 'require "std.list".prototype {}' + - it roundtrips objects: + expect (unpickle (f (List))).to_equal (List) + - context with '_type' metafield only: + - it uses the '_type' value for later invocation: + expect (f (things)).to_be 'Derived {[1]=false,[2]="str",[3]=42}' + - it roundtrips objects: + compat = require "specl.compat" + compat.setfenv (unpickle, compat.getfenv (1)) + expect (unpickle (f (things))).to_equal (things) + - it converts a nested object to a representative string: + buf = List {Derived {"?"}, ["!"]=42} + expect (f (buf)). + to_be 'require "std.list".prototype {[1]=Derived {[1]="?"},["!"]=42}' + - it roundtrips nested objects: + buf = List {Derived {"?"}, ["!"]=42} + expect (unpickle (f (buf))).to_equal (buf) diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 6f87aec..08f947d 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -361,3 +361,50 @@ specify std.object: not_to_contain ";" expect (tostring (o {one = true, two = true, three = true})). to_contain ";" + + +- describe __pickle: + - before: + loadstring = loadstring or load + function unpickle (s) return loadstring ("return " .. s) () end + f = require "std.string".pickle + + Derived = Object {_type = "Derived"} + things = Derived {one="two", "three"} + + - it returns a string: + expect (type (f (things))).to_be "string" + - it resets '_module' metafield when '_type' changes: + expect (getmetatable (things)._module).to_be (nil) + - it supports setting '_module' and '_type' metafields: + Everything = Derived { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + Everything = Object { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + - it does not have a period in '_type': + Period = Derived { _type = "specs.example.Period" } + expect (string.find (type (Period), "%.")).to_be (nil) + - it propagates the '_module' metafield when '_type' is unchanged: + expect (getmetatable (Object {1})._module). + to_be (getmetatable (Object)._module) + - context with '_module' metafield: + - it requires the module path for later invocation: + expect (f (Object)).to_be 'require "std.object".prototype {}' + - it roundtrips objects: + expect (unpickle (f (Object))).to_equal (Object) + - context with '_type' metafield only: + - it uses the '_type' value for later invocation: + expect (f (things)).to_be 'Derived {[1]="three",["one"]="two"}' + - it roundtrips objects: + compat = require "specl.compat" + compat.setfenv (unpickle, compat.getfenv (1)) + expect (unpickle (f (things))).to_equal (things) + - it converts a nested object to a representative string: + object = Object {Derived {"?"}, ["!"]=42} + expect (f (object)). + to_be 'require "std.object".prototype {[1]=Derived {[1]="?"},["!"]=42}' + - it roundtrips nested objects: + object = Object {Derived {"?"}, ["!"]=42} + expect (unpickle (f (object))).to_equal (object) diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index 48f981f..9e298b7 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -305,3 +305,50 @@ specify std.set: expect (tostring (s)).to_contain "Set" - it contains the ordered set elements: expect (tostring (s)).to_contain "bar, baz, foo" + + +- describe __pickle: + - before: + loadstring = loadstring or load + function unpickle (s) return loadstring ("return " .. s) () end + f = require "std.string".pickle + + Derived = Set {_type = "Derived"} + things = Derived {false, "str", 42} + + - it returns a string: + expect (type (f (things))).to_be "string" + - it resets '_module' metafield when '_type' changes: + expect (getmetatable (things)._module).to_be (nil) + - it supports setting '_module' and '_type' metafields: + Everything = Derived { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + Everything = Set { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + - it does not have a period in '_type': + Period = Derived { _type = "specs.example.Period" } + expect (string.find (type (Period), "%.")).to_be (nil) + - it propagates the '_module' metafield when '_type' is unchanged: + expect (getmetatable (Set {1})._module). + to_be (getmetatable (Set)._module) + - context with '_module' metafield: + - it requires the module path for later invocation: + expect (f (Set {1})).to_be 'require "std.set".prototype {1}' + - it roundtrips objects: + expect (unpickle (f (Set))).to_equal (Set) + - context with '_type' metafield only: + - it uses the '_type' value for later invocation: + expect (f (things)).to_be 'Derived {"str",42,false}' + - it roundtrips objects: + compat = require "specl.compat" + compat.setfenv (unpickle, compat.getfenv (1)) + expect (unpickle (f (things))).to_equal (things) + - it converts a nested object to a representative string: + set = Set {Derived {"?"}, "!"} + expect (f (set)). + to_be 'require "std.set".prototype {"!",Derived {"?"}}' + - it roundtrips nested objects: + set = Set {Derived {"?"}, "!"} + expect (unpickle (f (set))).to_equal (set) diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index 0d9e1ed..919a387 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -118,3 +118,50 @@ specify std.strbuf: a = StrBuf {1} b = a {} .. 2 expect (tostring (a)).to_be "1" + + +- describe __pickle: + - before: + loadstring = loadstring or load + function unpickle (s) return loadstring ("return " .. s) () end + f = require "std.string".pickle + + Derived = StrBuf {_type = "Derived"} + things = Derived {false, "str", 42} + + - it returns a string: + expect (type (f (things))).to_be "string" + - it resets '_module' metafield when '_type' changes: + expect (getmetatable (things)._module).to_be (nil) + - it supports setting '_module' and '_type' metafields: + Everything = Derived { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + Everything = StrBuf { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + - it does not have a period in '_type': + Period = Derived { _type = "specs.example.Period" } + expect (string.find (type (Period), "%.")).to_be (nil) + - it propagates the '_module' metafield when '_type' is unchanged: + expect (getmetatable (StrBuf {1})._module). + to_be (getmetatable (StrBuf)._module) + - context with '_module' metafield: + - it requires the module path for later invocation: + expect (f (StrBuf)).to_be 'require "std.strbuf".prototype {}' + - it roundtrips objects: + expect (unpickle (f (StrBuf))).to_equal (StrBuf) + - context with '_type' metafield only: + - it uses the '_type' value for later invocation: + expect (f (things)).to_be 'Derived {[1]=false,[2]="str",[3]=42}' + - it roundtrips objects: + compat = require "specl.compat" + compat.setfenv (unpickle, compat.getfenv (1)) + expect (unpickle (f (things))).to_equal (things) + - it converts a nested object to a representative string: + buf = StrBuf {Derived {"?"}, ["!"]=42} + expect (f (buf)). + to_be 'require "std.strbuf".prototype {[1]=Derived {[1]="?"},["!"]=42}' + - it roundtrips nested objects: + buf = StrBuf {Derived {"?"}, ["!"]=42} + expect (unpickle (f (buf))).to_equal (buf) diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index b910bd5..18a7ab0 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -396,6 +396,7 @@ specify std.tree: tr[{"foo", "branch", "baz"}] = "leaf2" expect (tr).to_equal (Tree {foo=Tree {branch=Tree {bar="leaf1", baz="leaf2"}}}) + - describe __tostring: - it returns a string: expect (objtype (tostring (tr))).to_be "string" @@ -407,3 +408,50 @@ specify std.tree: quux = "quux"} expect (tostring (tr)). to_contain 'fnord=Tree {branch=Tree {bar=bar, baz=baz}}, foo=foo, quux=quux' + + +- describe __pickle: + - before: + loadstring = loadstring or load + function unpickle (s) return loadstring ("return " .. s) () end + f = require "std.string".pickle + + Derived = Tree {_type = "Derived"} + things = Derived {false, "str", 42} + + - it returns a string: + expect (type (f (things))).to_be "string" + - it resets '_module' metafield when '_type' changes: + expect (getmetatable (things)._module).to_be (nil) + - it supports setting '_module' and '_type' metafields: + Everything = Derived { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + Everything = Tree { _type = "Everything", _module = "nearly" } + expect (objtype (Everything)).to_be "Everything" + expect (getmetatable (Everything)._module).to_be "nearly" + - it does not have a period in '_type': + Period = Derived { _type = "specs.example.Period" } + expect (string.find (type (Period), "%.")).to_be (nil) + - it propagates the '_module' metafield when '_type' is unchanged: + expect (getmetatable (Tree {1})._module). + to_be (getmetatable (Tree)._module) + - context with '_module' metafield: + - it requires the module path for later invocation: + expect (f (Tree)).to_be 'require "std.tree".prototype {}' + - it roundtrips objects: + expect (unpickle (f (Tree))).to_equal (Tree) + - context with '_type' metafield only: + - it uses the '_type' value for later invocation: + expect (f (things)).to_be 'Derived {[1]=false,[2]="str",[3]=42}' + - it roundtrips objects: + compat = require "specl.compat" + compat.setfenv (unpickle, compat.getfenv (1)) + expect (unpickle (f (things))).to_equal (things) + - it converts a nested object to a representative string: + buf = Tree {Derived {"?"}, ["!"]=42} + expect (f (buf)). + to_be 'require "std.tree".prototype {[1]=Derived {[1]="?"},["!"]=42}' + - it roundtrips nested objects: + buf = Tree {Derived {"?"}, ["!"]=42} + expect (unpickle (f (buf))).to_equal (buf) diff --git a/specs/tuple_spec.yaml b/specs/tuple_spec.yaml index c103790..ac1d8a8 100644 --- a/specs/tuple_spec.yaml +++ b/specs/tuple_spec.yaml @@ -126,7 +126,7 @@ specify std.tuple: expect (collect (Tuple (nil, nil))).to_be "nil,nil" -- describe stringification: +- describe __tostring: - it starts with the object type: expect (tostring (Tuple ())).to_match "^Tuple " - it contains all the elements: @@ -134,3 +134,29 @@ specify std.tuple: for _, e in ipairs (elems) do expect (tostring (Tuple (unpack (elems)))).to_match (e) end + + +- describe __pickle: + - before: + loadstring = loadstring or load + function unpickle (s) return loadstring ("return " .. s) () end + f = require "std.string".pickle + + things = Tuple (false, "str", 42) + + - it returns a string: + expect (type (f (things))).to_be "string" + - it propagates the '_module' metafield when '_type' is unchanged: + expect (getmetatable (Tuple (1))._module). + to_be (getmetatable (Tuple)._module) + - it requires the module path for later invocation: + expect (f (things)).to_be 'require "std.tuple".prototype (false,"str",42)' + - it roundtrips objects: + expect (unpickle (f (things))).to_equal (things) + - it converts a nested object to a representative string: + tuple = Tuple (Tuple ("?"), "!", 42) + expect (f (tuple)).to_be ('require "std.tuple".prototype ' .. + '(require "std.tuple".prototype ("?"),"!",42)') + - it roundtrips nested objects: + tuple = Tuple (Tuple ("?"), "!", 42) + expect (unpickle (f (tuple))).to_equal (tuple) From 0af3385535eec13a931aa89df1fe91047e2ea5d5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 29 Aug 2015 19:34:48 +0100 Subject: [PATCH 594/703] strict: don't mangle _G by default. * specs/strict_spec.yaml: New file. Examples of correct behaviour for new std.strict API. * specs/specs.mk (specl_SPECS): Add specs/strict_spec.yaml. * lib/std/strict.lua (mt): Return a functable suitable for strict variable use checking, instead of setting it on _G automatically. * NEWS (New features, Backwards compatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 19 +++++++++ lib/std/strict.lua | 90 +++++++++++++++++++++++------------------- specs/specs.mk | 1 + specs/strict_spec.yaml | 64 ++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 41 deletions(-) create mode 100644 specs/strict_spec.yaml diff --git a/NEWS.md b/NEWS.md index 4563076..e5c33a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,17 @@ us organization-wise, but improvements and corrections to the content are always welcome! + - `require "std.strict"` now returns a callable that can be applied + to any environment that should detect references to undeclared + variables. For example, to check within the implementation of a + module, but without changing the behaviour of the client code: + + ```lua + local strict = require "std.strict" + local _ENV = strict (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end + ``` + - For orthogonality with core Lua `type`, we now export the `std.object.type` function as `std.type`. @@ -188,6 +199,14 @@ the new compact format, including stringification of Objects and Containers using their `__tostring` metamethods. + - `std.strict` now returns a callable for detecting accidental global + variable leakage and reference, but does not apply it to `_G` by + default. You can emulate the old behaviour with: + + ```lua + _G = require "std.strict" (_G) + ``` + ## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] diff --git a/lib/std/strict.lua b/lib/std/strict.lua index 6d7be04..0be4fce 100644 --- a/lib/std/strict.lua +++ b/lib/std/strict.lua @@ -1,29 +1,27 @@ --[[-- - Checks uses of undeclared global variables. + Checks uses of undeclared variables. - All global variables must be 'declared' through a regular - assignment (even assigning `nil` will do) in a top-level - chunk before being used anywhere or assigned to inside a function. + All variables (including functions!) must be "declared" through a regular + assignment (even assigning `nil` will do) in a strict scope before being + used anywhere or assigned to inside a function. - To use this module, just require it near the start of your program. + Use the callable returned by this module to interpose a strictness check + proxy table to the argument table. To apply to just the current module, + for example: - From Lua distribution (`etc/strict.lua`). + local strict = require "std.strict" + local _ENV = strict (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end + + Note that we have to be careful not to reference `setfenv` directly in + the `if` statement, because on Lua >= 5.2, it doesn't exist and would + trigger an undeclared variable error! @module std.strict ]] local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget -local mt = getmetatable (_G) -if mt == nil then - mt = {} - setmetatable (_G, mt) -end - - --- The set of globally declared variables. -mt.__declared = {} - --- What kind of variable declaration is this? -- @treturn string "C", "Lua" or "main" @@ -33,30 +31,40 @@ local function what () end ---- Detect assignment to undeclared global. --- @function __newindex --- @tparam table t `_G` --- @string n name of the variable being declared --- @param v initial value of the variable -mt.__newindex = function (t, n, v) - if not mt.__declared[n] then - local w = what () - if w ~= "main" and w ~= "C" then - error ("assignment to undeclared variable '" .. n .. "'", 2) - end - mt.__declared[n] = true - end - rawset (t, n, v) -end +return setmetatable ({}, { + __call = function (self, env) + -- The set of globally declared variables. + local declared = {} + return setmetatable ({}, { + --- Detect dereference of undeclared global. + -- @function __index + -- @string n name of the variable being dereferenced + __index = function (_, n) + local v = env[n] + if v ~= nil then + declared[n] = true + elseif not declared[n] and what () ~= "C" then + error ("variable '" .. n .. "' is not declared", 2) + end + return v + end, ---- Detect dereference of undeclared global. --- @function __index --- @tparam table t `_G` --- @string n name of the variable being dereferenced -mt.__index = function (t, n) - if not mt.__declared[n] and what () ~= "C" then - error ("variable '" .. n .. "' is not declared", 2) - end - return rawget (t, n) -end + --- Detect assignment to undeclared global. + -- @function __newindex + -- @string n name of the variable being declared + -- @param v initial value of the variable + __newindex = function (_, n, v) + local x = env[n] + if x == nil and not declared[n] then + local w = what () + if w ~= "main" and w ~= "C" then + error ("assignment to undeclared variable '" .. n .. "'", 2) + end + end + declared[n] = true + env[n] = v + end, + }) + end, +}) diff --git a/specs/specs.mk b/specs/specs.mk index f42f651..ac08f26 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -25,6 +25,7 @@ specl_SPECS = \ $(srcdir)/specs/package_spec.yaml \ $(srcdir)/specs/set_spec.yaml \ $(srcdir)/specs/strbuf_spec.yaml \ + $(srcdir)/specs/strict_spec.yaml \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ diff --git a/specs/strict_spec.yaml b/specs/strict_spec.yaml new file mode 100644 index 0000000..f1d6685 --- /dev/null +++ b/specs/strict_spec.yaml @@ -0,0 +1,64 @@ +before: + strict = require "std.strict" + +specify std.strict: +- describe require: + - it returns a callable: + expect (getmetatable (strict).__call).not_to_be (nil) + + +- describe strict: + - it allows assignment to declared variables: + scope = strict { foo = "bar" } + + expect ((function () scope.foo = "baz" end) ()). + not_to_raise "not declared" + expect (scope.foo).to_be "baz" + + - it diagnoses assignment to undeclared variable: + scope = strict { foo = "bar" } + + expect ((function () scope.undefined = "rval" end) ()). + to_raise "assignment to undeclared variable 'undefined'" + + - it allows reference to declared variables: + scope = strict { foo = "bar" } + + expect ((function () return scope.foo end) ()).to_be "bar" + + - it diagnoses reference to undeclared variable: + scope = strict {} + + expect ((function () return scope.undefined end) ()). + to_raise "variable 'undefined' is not declared" + + - it allows assignemnt to undeclared global variables: + _ENV = strict (setmetatable ({}, {__index=_G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end + + defined = "rval" + expect (_ENV.defined).to_be "rval" + expect ((function () defined = "foo" end) ()). + not_to_raise "undeclared variable" + + - it diagnoses assignment to undeclared global variable: + _ENV = strict (setmetatable ({}, {__index=_G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end + + expect ((function () undefined = "rval" end) ()). + to_raise "assignment to undeclared variable 'undefined'" + + - it diagnoses reference to undeclared global variable: + _ENV = strict (setmetatable ({}, {__index=_G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end + + expect ((function () foo = undefined end) ()). + to_raise "variable 'undefined' is not declared" + + - it does not leak into surrounding scope: + _ENV = strict (setmetatable ({}, {__index=_G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end + + expect ((function () _G.undefined = "rval" end) ()). + not_to_raise "undefined" + From 17092c57ef872fe97980a181f7e8d34347be4117 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 30 Aug 2015 17:29:32 +0100 Subject: [PATCH 595/703] strict: enable strict mode in all stdlib modules. * lib/std/debug_init/init.lua (_DEBUG): Add new `strict` field, `true` by default, to determine whether to enable strict mode assignment checking in stdlib modules. * lib/std/lua.in, lib/std/base.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/object.lua, lib/std/operator.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua, lib/std/tuple.lua: Enable strict mode unless _DEBUG.strict is falsey. * lib/std/io.lua (warnfmt): Check whether global `prog` and `opts` variables have been set more carefully, so as not to trigger errors in strict mode. * lib/std/optparse.lua (optional): Fix a new bug found by strict mode! Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 8 ++++++++ lib/std/base.lua | 7 +++++++ lib/std/container.lua | 8 ++++++++ lib/std/debug.lua | 13 ++++++++++++- lib/std/debug_init/init.lua | 3 +++ lib/std/functional.lua | 8 ++++++++ lib/std/io.lua | 16 +++++++++++++--- lib/std/list.lua | 8 ++++++++ lib/std/math.lua | 8 ++++++++ lib/std/object.lua | 8 ++++++++ lib/std/operator.lua | 8 ++++++++ lib/std/optparse.lua | 10 +++++++++- lib/std/package.lua | 8 ++++++++ lib/std/set.lua | 9 +++++++++ lib/std/strbuf.lua | 9 +++++++++ lib/std/string.lua | 9 +++++++++ lib/std/table.lua | 8 ++++++++ lib/std/tree.lua | 9 +++++++++ lib/std/tuple.lua | 8 ++++++++ 19 files changed, 160 insertions(+), 5 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 6d2cab1..c23d520 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -20,6 +20,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local M, monkeys diff --git a/lib/std/base.lua b/lib/std/base.lua index 27323f6..73b05c8 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -22,6 +22,13 @@ @module std.base ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + local dirsep = string.match (package.config, "^(%S+)\n") local loadstring = rawget (_G, "loadstring") or load diff --git a/lib/std/container.lua b/lib/std/container.lua index abfbd57..6466a96 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -29,6 +29,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local _concat = table.concat local _find = string.find local _lower = string.lower diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 8cf6bf4..5a7ae3b 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -29,6 +29,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local debug_init = require "std.debug_init" local std = require "std.base" @@ -45,7 +53,10 @@ local std = require "std.base" -- deprecation warning each time a deprecated api is used; any other -- value causes deprecated APIs not to be defined at all -- @tfield[opt=1] int level debugging level --- @usage _DEBUG = { argcheck = false, level = 9 } +-- @tfield[opt=true] boolean strict enforce strict variable declaration +-- before use **in stdlib internals** +-- @see std.strict +-- @usage _DEBUG = { argcheck = false, level = 9, strict = false } local _DEBUG = debug_init._DEBUG local ipairs, pairs, stdtype, tostring = diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index ccdcb69..cb0e3c9 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -15,6 +15,7 @@ elseif _DEBUG == false then call = false, deprecate = false, level = math.huge, + strict = false, } -- Turn everything on (except _DEBUG.call must be set explicitly). @@ -23,6 +24,7 @@ elseif _DEBUG == true then argcheck = true, call = false, deprecate = true, + strict = true, } else @@ -42,6 +44,7 @@ setdefault ("argcheck", true) setdefault ("call", false) setdefault ("deprecate", nil) setdefault ("level", 1) +setdefault ("strict", true) return M diff --git a/lib/std/functional.lua b/lib/std/functional.lua index bcb12b6..e44a177 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,6 +8,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local ielems, ipairs, ireverse, npairs, pairs = diff --git a/lib/std/io.lua b/lib/std/io.lua index 2be534a..5fbb82c 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,6 +11,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local debug = require "std.debug" @@ -107,17 +115,19 @@ end local function warnfmt (msg, ...) local prefix = "" - if (prog or {}).name then + 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 .. tostring (prog.line) .. ":" end - elseif (prog or {}).file then + elseif prog.file then prefix = prog.file .. ":" if prog.line then prefix = prefix .. tostring (prog.line) .. ":" end - elseif (opts or {}).program then + elseif opts.program then prefix = opts.program .. ":" if opts.line then prefix = prefix .. tostring (opts.line) .. ":" diff --git a/lib/std/list.lua b/lib/std/list.lua index 9557fb3..e7afdcf 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -17,6 +17,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local Object = require "std.object".prototype diff --git a/lib/std/math.lua b/lib/std/math.lua index 3ca4a91..5ebe116 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,6 +11,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local M diff --git a/lib/std/object.lua b/lib/std/object.lua index 30d1626..d906559 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -22,6 +22,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local container = require "std.container" local debug = require "std.debug" local std = require "std.base" diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 086910f..d364976 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -4,6 +4,14 @@ @functional std.operator ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local pairs, stdtype, tostring = diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index d9fe925..4a70ee1 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -22,6 +22,14 @@ ]=] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local Object = require "std.object".prototype @@ -166,7 +174,7 @@ function optional (self, arglist, i, value) end if type (value) == "function" then - value = value (self, opt, nil) + value = value (self, arglist[i], nil) elseif value == nil then value = true end diff --git a/lib/std/package.lua b/lib/std/package.lua index 89f3d9b..dd41a21 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -36,6 +36,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local ipairs, pairs = std.ipairs, std.pairs diff --git a/lib/std/set.lua b/lib/std/set.lua index ecf9b40..f614d8f 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -21,6 +21,15 @@ @prototype std.set ]] + +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local _concat = table.concat local _sort = table.sort local _type = type diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 83aa1bd..ad330d6 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -26,6 +26,15 @@ @prototype std.strbuf ]] + +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local debug = require "std.debug" diff --git a/lib/std/string.lua b/lib/std/string.lua index 533dfc1..382de80 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -10,6 +10,15 @@ @corelibrary std.string ]] + +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local debug = require "std.debug" diff --git a/lib/std/table.lua b/lib/std/table.lua index 0c5887e..e4bba72 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,6 +11,14 @@ ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local core = _G.table local std = require "std.base" diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 450a182..ec375b5 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -23,6 +23,15 @@ @prototype std.tree ]] + +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local std = require "std.base" local operator = require "std.operator" diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 01649ff..41d1d2f 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -25,6 +25,14 @@ @prototype std.tuple ]] +local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG + +if _DEBUG.strict then + _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + if rawget (_G, "setfenv") then setfenv (1, _ENV) end +end + + local _concat = table.concat local _format = string.format local _type = type From 08f768d282a4d1119874495d8ca96c6498536623 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 30 Aug 2015 17:57:05 +0100 Subject: [PATCH 596/703] strict: reference _G.loadstring without tripping strict on Lua 5.3. * lib/std/functional.lua (loadstring): Use rawget to avoid an undeclared variable error from strict mode on Lua 5.3, Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index e44a177..82a02e1 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -25,7 +25,7 @@ local callable, reduce = std.functional.callable, std.functional.reduce local len = std.operator.len local render = std.string.render local unpack = std.table.unpack -local loadstring = loadstring or load +local loadstring = rawget (_G, "loadstring") or load local function bind (fn, bound) From 85e43cc08a9adb1759f035059a9348e60f39241b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 12 Sep 2015 12:11:02 +0100 Subject: [PATCH 597/703] refactor: isolate each module except for strict imports. Prevent any possibility of accidental references to external symbols in every module by first importing everything that is actually used by each module, and then passing an empty table to `std.strict`, which resets the module environment; disabling access to any symbols not already imported as locals. * specs/spec_helper.lua (deprecation): New luaproc wrapper to assemble and run a lua script suitable for checking whether deprecation warnings are being emitted correctly. * specs/debug_spec.yaml, specs/functional_spec.yaml, specs/list_spec.yaml, specs/object_spec.yaml, specs/strbuf_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Current implementation of `specl.inprocess.capture` relies on being able to inject alternative io functions, which does not work now that all std modules cache the io symbols they need immediately - convert all relevant examples to use `deprecation` spec_helper instead. * lib/std.lua.in, lib/std/base.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/object.lua, lib/std/operator.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua, lib/std/tuple.lua: Cache external symbols in module locals, and then isolate the module environment with std.strict. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 35 +++- lib/std/base.lua | 344 ++++++++++++++++++++++--------------- lib/std/container.lua | 76 ++++---- lib/std/debug.lua | 90 ++++++++-- lib/std/functional.lua | 55 ++++-- lib/std/io.lua | 62 +++++-- lib/std/list.lua | 42 ++++- lib/std/math.lua | 27 ++- lib/std/object.lua | 27 ++- lib/std/operator.lua | 34 +++- lib/std/optparse.lua | 49 +++++- lib/std/package.lua | 48 +++++- lib/std/set.lua | 70 +++++--- lib/std/strbuf.lua | 33 +++- lib/std/string.lua | 72 ++++++-- lib/std/table.lua | 50 ++++-- lib/std/tree.lua | 59 +++++-- lib/std/tuple.lua | 61 +++++-- specs/debug_spec.yaml | 2 +- specs/functional_spec.yaml | 77 ++++----- specs/list_spec.yaml | 245 +++++++++++++------------- specs/object_spec.yaml | 17 +- specs/spec_helper.lua | 35 ++++ specs/strbuf_spec.yaml | 23 ++- specs/string_spec.yaml | 42 +++-- specs/table_spec.yaml | 45 ++--- 26 files changed, 1180 insertions(+), 540 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index c23d520..8789baa 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -20,16 +20,45 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local _G = _G + +local ipairs = ipairs +local pairs = pairs +local pcall = pcall +local rawset = rawset +local require = require +local setfenv = setfenv +local setmetatable = setmetatable +local type = type + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end - local std = require "std.base" + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + local M, monkeys diff --git a/lib/std/base.lua b/lib/std/base.lua index 73b05c8..d43c884 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -22,77 +22,97 @@ @module std.base ]] -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG -if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end -end +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local dirsep = string.match (package.config, "^(%S+)\n") + +local error = error +local getmetatable = getmetatable +local load = load or loadstring +local next = next +local pairs = pairs +local rawget = rawget +local require = require +local select = select +local setfenv = setfenv +local setmetatable = setmetatable +local tonumber = tonumber +local tostring = tostring +local type = type + +local coroutine = { + wrap = coroutine.wrap, + yield = coroutine.yield, +} +local math = { + huge = math.huge, + min = math.min, +} -local dirsep = string.match (package.config, "^(%S+)\n") -local loadstring = rawget (_G, "loadstring") or load -local _concat = table.concat -local _format = string.format -local _tostring = tostring -local _type = type +local io = { + type = io.type, +} +local string = { + find = string.find, + format = string.format, +} -local function raise (bad, to, name, i, extramsg, level) - level = level or 1 - local s = _format ("bad %s #%d %s '%s'", bad, i, to, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) -end +local table = { + concat = table.concat, + insert = table.insert, + maxn = table.maxn, + sort = table.sort, + unpack = table.unpack or unpack, +} -local function argerror (name, i, extramsg, level) - level = level or 1 - raise ("argument", "to", name, i, extramsg, level + 1) -end +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- -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 _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG --- 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 +if _DEBUG.strict then + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local function getmetamethod (x, n) - local m = (getmetatable (x) or {})[n] - if callable (m) then return m end -end +--[[ ============================ ]]-- +--[[ Enhanced Core Lua functions. ]]-- +--[[ ============================ ]]-- -local function catfile (...) - return _concat ({...}, dirsep) +-- Forward declarations for Helper functions below. + +local argerror, getmetamethod, len, vcompare + +-- 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 function assert (expect, fmt, arg1, ...) + local msg = (arg1 ~= nil) and string.format (fmt, arg1, ...) or fmt or "" + return expect or error (msg, 2) end --- Lua < 5.2 doesn't call `__len` automatically! -local function len (t) - local m = getmetamethod (t, "__len") - return m and m (t) or #t +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 + table.insert (t, pos, v) + return t end @@ -110,25 +130,64 @@ end local _pairs = pairs +-- Respect __pairs metamethod, even in Lua 5.1. +local function pairs (t) + return (getmetamethod (t, "__pairs") or _pairs) (t) +end + + 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 + for k in pairs (t) do + if type (k) == "number" and k > n then n = k end end return n end -local _unpack = table.unpack or unpack +local _require = require -local function unpack (t, i, j) - if j == nil then - -- respect __len, and then maxn if nil j was passed - local m = getmetamethod (t, "__len") - j = m and m (t) or maxn (t) +local function require (module, min, too_big, pattern) + local m = _require (module) + local v = tostring (type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") + if min then + assert (vcompare (v, min) >= 0, "require '" .. module .. + "' with at least version " .. min .. ", but found version " .. v) end - -- use the __contents metatable instead of t when present - return _unpack ( (getmetatable (t) or {}).__contents or t, i or 1, j) + 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 + + + +--[[ ============================ ]]-- +--[[ 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 table.concat ({...}, dirsep) end @@ -154,14 +213,6 @@ local function compare (l, m) end -local _pairs = pairs - --- Respect __pairs metamethod, even in Lua 5.1. -local function pairs (t) - return (getmetamethod (t, "__pairs") or _pairs) (t) -end - - local function copy (dest, src) if src == nil then dest, src = {}, dest end for k, v in pairs (src) do dest[k] = v end @@ -201,7 +252,7 @@ end local function eval (s) - return loadstring ("return " .. s)() + return load ("return " .. s)() end @@ -210,18 +261,6 @@ local function ielems (l) 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 invert (t) local i = {} for k, v in pairs (t) do @@ -246,28 +285,20 @@ end -- Sort numbers first then asciibetically local function keysort (a, b) - if _type (a) == "number" then - return _type (b) ~= "number" or a < b + if type (a) == "number" then + return type (b) ~= "number" or a < b else - return _type (b) ~= "number" and _tostring (a) < _tostring (b) + return type (b) ~= "number" and tostring (a) < tostring (b) end end -local _sort = table.sort - -local function sortkeys (t) - _sort (t, keysort) - return t -end - - local function last (t) return t[len (t)] end local function leaves (it, tr) local function visit (n) - if _type (n) == "table" then + if type (n) == "table" then for _, v in it (n) do visit (v) end @@ -290,7 +321,7 @@ local function mapfields (obj, src, map) local k, v = next (src) while k do local key, dst = map[k] or k, obj - local kind = _type (key) + local kind = type (key) if kind == "string" and key:sub (1, 1) == "_" then mt[key] = v elseif next (map) and kind == "number" and len (dst) + 1 < key then @@ -337,6 +368,18 @@ local function npairs (t) end +local function unpack (t, i, j) + if j == nil then + -- respect __len, and then maxn if nil j was passed + local m = getmetamethod (t, "__len") + j = m and m (t) or maxn (t) + end + -- use the __contents metatable instead of t when present + -- FIXME: get rid of this! + return table.unpack ((getmetatable (t) or {}).__contents or t, i or 1, j) +end + + local function collect (ifn, ...) local argt, r = {...}, {} if not callable (ifn) then @@ -385,14 +428,14 @@ local fallbacks = { __index = { open = function (x) return "{" end, close = function (x) return "}" end, - elem = _tostring, + elem = tostring, pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. "=" .. vstr end, sep = function (x, kp, vp, kn, vn) return kp ~= nil and kn ~= nil and "," or "" end, sort = function (keys) return keys end, term = function (x) - return _type (x) ~= "table" or getmetamethod (x, "__tostring") + return type (x) ~= "table" or getmetamethod (x, "__tostring") end, }, } @@ -438,14 +481,20 @@ local function render (x, fns, roots) buf[#buf + 1] = sep (x, kp, vp) -- buffer << trailing separator buf[#buf + 1] = fns.close (x) -- buffer << table close - return _concat (buf) -- stringify buffer + return table.concat (buf) -- stringify buffer end end local function toqstring (x) - if _type (x) ~= "string" then return _tostring (x) end - return _format ("%q", x) + if type (x) ~= "string" then return tostring (x) end + return string.format ("%q", x) +end + + +local function sortkeys (t) + table.sort (t, keysort) + return t end @@ -461,7 +510,7 @@ local function mnemonic (...) for i = 1, n do buf[i] = render (seq[i], mnemonic_vtable) end - return _concat (buf, ",") + return table.concat (buf, ",") end @@ -476,7 +525,7 @@ local pickle_vtable = { return true elseif type (x) ~= "table" then -- don't know what to do with this :( - error ("cannot pickle " .. _tostring (x)) + error ("cannot pickle " .. tostring (x)) end end, @@ -495,9 +544,9 @@ local pickle_vtable = { -- common types local type_x = type (x) if type_x == "string" then - return _format ("%q", x) + return string.format ("%q", x) elseif type_x == "number" or type_x == "boolean" then - return _tostring (x) + return tostring (x) end -- pickling metamethod @@ -516,6 +565,16 @@ local function pickle (x) end +local function raise (bad, to, name, i, extramsg, level) + level = level or 1 + local s = string.format ("bad %s #%d %s '%s'", bad, i, to, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) +end + + local function ripairs (t) local oob = 1 while t[oob] ~= nil do @@ -552,41 +611,19 @@ local function split (s, sep) else patt = "(.-)" .. (sep or "%s+") end - local b, lens = 0, len (s) - while b <= lens do + local b, slen = 0, len (s) + while b <= slen do local e, n, m = string.find (s, patt, b + 1) - insert (r, m or s:sub (b + 1, lens)) - b = n or lens + 1 + insert (r, m or s:sub (b + 1, slen)) + b = n or slen + 1 end return r end -local function vcompare (a, b) - return compare (split (a, "%."), split (b, "%.")) -end - - -local _require = require - -local function require (module, min, too_big, pattern) - local m = _require (module) - local v = _tostring (_type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") - 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 - - local tostring_vtable = { pair = function (x, kp, vp, k, v, kstr, vstr) - if k == 1 or _type (k) == "number" and k -1 == kp then + if k == 1 or type (k) == "number" and k -1 == kp then return vstr end return kstr .. "=" .. vstr @@ -597,11 +634,45 @@ local tostring_vtable = { } -local function tostring (x) - return render (x, tostring_vtable) +--[[ ================= ]]-- +--[[ Helper functions. ]]-- +--[[ ================= ]]-- + +-- The bare minumum of functions required to support implementation of +-- Enhanced Core Lua functions, with forward declarations near the start +-- of the file. + + +argerror = function (name, i, extramsg, level) + level = level or 1 + raise ("argument", "to", name, i, extramsg, level + 1) +end + + +-- Lua < 5.2 doesn't call `__len` automatically! +len = function (t) + local m = getmetamethod (t, "__len") + return m and m (t) or #t +end + + +getmetamethod = function (x, n) + local m = (getmetatable (x) or {})[n] + if callable (m) then return m end +end + + +vcompare = function (a, b) + return compare (split (a, "%."), split (b, "%.")) 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. @@ -622,10 +693,11 @@ return { require = require, ripairs = ripairs, rnpairs = rnpairs, - tostring = tostring, + + tostring = function (x) return render (x, tostring_vtable) end, type = function (x) - return (getmetatable (x) or {})._type or io.type (x) or _type (x) + return (getmetatable (x) or {})._type or io.type (x) or type (x) end, base = { diff --git a/lib/std/container.lua b/lib/std/container.lua index 6466a96..4e75ddb 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -29,30 +29,46 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local getmetatable = getmetatable +local next = next +local require = require +local select = select +local setfenv = setfenv +local setmetatable = setmetatable +local string_find = string.find +local string_sub = string.sub +local table_concat = table.concat +local type = type + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local _concat = table.concat -local _find = string.find -local _lower = string.lower -local _sub = string.sub -local _type = type - -local _DEBUG = require "std.debug_init"._DEBUG - -local std = require "std.base" -local debug = require "std.debug" +local std = require "std.base" +local debug = require "std.debug" -local copy = std.base.copy -local ipairs, tostring = std.ipairs, std.tostring -local mapfields = std.object.mapfields -local pickle = std.string.pickle -local render = std.string.render +local copy = std.base.copy +local ipairs = std.ipairs +local mapfields = std.object.mapfields +local pickle = std.string.pickle +local render = std.string.render +local tostring = std.tostring @@ -92,13 +108,13 @@ end local tostring_vtable = { pair = function (x, kp, vp, k, v, kstr, vstr) - if k == 1 or _type (k) == "number" and k -1 == kp then return vstr end + if k == 1 or type (k) == "number" and k -1 == kp then return vstr end return kstr .. "=" .. vstr end, sep = function (x, kp, vp, kn, vn) if kp == nil or kn == nil then return "" end - if _type (kp) == "number" and kn ~= kp + 1 then return "; " end + if type (kp) == "number" and kn ~= kp + 1 then return "; " end return ", " end, @@ -179,7 +195,7 @@ local prototype = { k, v = next (self, k) end - if _type (mt._init) == "function" then + if type (mt._init) == "function" then obj = mt._init (obj, ...) else obj = (self.mapfields or mapfields) (obj, (...), mt._init) @@ -189,19 +205,19 @@ local prototype = { if next (getmetatable (obj) or {}) then local new_mt = getmetatable (obj) local new_type = new_mt._type or "" - local i = _find ("." .. new_type, "%.[^%.]*$") + local i = string_find ("." .. new_type, "%.[^%.]*$") if i > 1 then -- expand long-form type. - new_mt._type = _sub (new_type, i) - new_mt._module = _sub (new_type, 1, i -2) + new_mt._type = string_sub (new_type, i) + new_mt._module = string_sub (new_type, 1, i -2) end -- Merge fields. obj_mt = instantiate (mt, getmetatable (obj)) -- Merge object methods. - if _type (obj_mt.__index) == "table" and - _type ((mt or {}).__index) == "table" + if type (obj_mt.__index) == "table" and + type ((mt or {}).__index) == "table" then obj_mt.__index = instantiate (mt.__index, obj_mt.__index) end @@ -230,7 +246,7 @@ local prototype = { -- @usage -- assert (tostring (list) == 'Cons {car="head", cdr=Cons {car="tail"}}') __tostring = function (self) - return _concat { + return table_concat { -- Pass a shallow copy to render to avoid triggering __tostring -- again and blowing the stack. getmetatable (self)._type, @@ -257,9 +273,9 @@ local prototype = { -- @see std.string.pickle __pickle = function (self) local mt = getmetatable (self) - if _type (mt._module) == "string" then + if type (mt._module) == "string" then -- object with _module set - return _concat { + return table_concat { 'require "', mt._module, '".prototype ', @@ -267,7 +283,7 @@ local prototype = { } end -- rely on caller preloading `local ObjectName = require "obj".prototype` - return _concat { + return table_concat { mt._type, " ", pickle (copy (self)), } end, @@ -284,7 +300,7 @@ if _DEBUG.argcheck then -- A function initialised object can be passed arguments of any -- type, so only argcheck non-function initialised objects. - if _type (mt._init) ~= "function" then + if type (mt._init) ~= "function" then local name, n = mt._type, select ("#", ...) -- Don't count `self` as an argument for error messages, because -- it just refers back to the object being called: `prototype {"x"}. diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 5a7ae3b..4b5d3f9 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -29,16 +29,50 @@ ]] -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local debug = debug +local error = error +local getfenv = getfenv +local getmetatable = getmetatable +local next = next +local pcall = pcall +local require = require +local setfenv = setfenv +local setmetatable = setmetatable +local type = type + +local io = { + stderr = io.stderr, + type = io.type, +} + +local math = { + floor = math.floor, + huge = math.huge, + max = math.max, +} + +local string = { + format = string.format, + match = string.match, +} + +local table = { + concat = table.concat, + remove = table.remove, + sort = table.sort, +} + -if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end -end +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- -local debug_init = require "std.debug_init" -local std = require "std.base" --- Control std.debug function behaviour. -- To declare debugging state, set _DEBUG either to `false` to disable all @@ -57,17 +91,37 @@ local std = require "std.base" -- before use **in stdlib internals** -- @see std.strict -- @usage _DEBUG = { argcheck = false, level = 9, strict = false } -local _DEBUG = debug_init._DEBUG +local _DEBUG = require "std.debug_init"._DEBUG + +local _ENV = _G + +if _DEBUG.strict then + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end +end + + +local std = require "std.base" + +local argerror = std.debug.argerror +local copy = std.base.copy +local insert = std.table.insert +local ipairs = std.ipairs +local last = std.base.last +local len = std.operator.len +local maxn = std.table.maxn +local pairs = std.pairs +local raise = std.base.raise +local split = std.string.split +local stdtype = std.type +local tostring = std.tostring +local unpack = std.table.unpack + + -local ipairs, pairs, stdtype, tostring = - std.ipairs, std.pairs, std.type, std.tostring -local copy, last, raise = std.base.copy, std.base.last, std.base.raise -local argerror = std.debug.argerror -local len = std.operator.len -local split = std.string.split -local insert, maxn, unpack = - std.table.insert, std.table.maxn, std.table.unpack -local type = type +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- local M @@ -131,7 +185,7 @@ local function setfenv (fn, env) end -local _getfenv = rawget (_G, "getfenv") +local _getfenv = getfenv local getfenv = function (fn) fn = fn or 1 diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 82a02e1..dffd0df 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,24 +8,57 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local load = load or loadstring +local next = next +local pcall = pcall +local require = require +local select = select +local setfenv = setfenv +local setmetatable = setmetatable + +local table = { + remove = table.remove, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end local std = require "std.base" -local ielems, ipairs, ireverse, npairs, pairs = - std.ielems, std.ipairs, std.ireverse, std.npairs, std.pairs -local copy = std.base.copy -local callable, reduce = std.functional.callable, std.functional.reduce -local len = std.operator.len -local render = std.string.render -local unpack = std.table.unpack -local loadstring = rawget (_G, "loadstring") or load +local callable = std.functional.callable +local copy = std.base.copy +local ielems = std.ielems +local ipairs = std.ipairs +local ireverse = std.ireverse +local len = std.operator.len +local npairs = std.npairs +local pairs = std.pairs +local reduce = std.functional.reduce +local render = std.string.render +local unpack = std.table.unpack + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- local function bind (fn, bound) @@ -200,7 +233,7 @@ local lambda = memoize (function (s) local ok, fn if expr then - ok, fn = pcall (loadstring (expr)) + ok, fn = pcall (load (expr)) end -- Diagnose invalid input. diff --git a/lib/std/io.lua b/lib/std/io.lua index 5fbb82c..0758862 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,26 +11,64 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local _G = _G + +local arg = arg +local error = error +local getmetatable = getmetatable +local io = io +local rawget = rawget +local require = require +local setfenv = setfenv +local setmetatable = debug.setmetatable +local type = type + +local string = { + format = string.format, +} + +local table = { + concat = table.concat, +} + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local std = require "std.base" -local debug = require "std.debug" +local std = require "std.base" +local debug = require "std.debug" + +local argerror = debug.argerror +local catfile = std.io.catfile +local dirsep = std.package.dirsep +local insert = std.table.insert +local ipairs = std.ipairs +local leaves = std.tree.leaves +local len = std.operator.len +local pairs = std.pairs +local split = std.string.split +local tostring = std.tostring + -local argerror, setmetatable = debug.argerror, debug.setmetatable -local ipairs, pairs = std.ipairs, std.pairs -local catfile = std.io.catfile -local len = std.operator.len -local dirsep = std.package.dirsep -local split = std.string.split -local insert = std.table.insert -local leaves = std.tree.leaves +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- local M, monkeys diff --git a/lib/std/list.lua b/lib/std/list.lua index e7afdcf..79ee8dd 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -17,22 +17,48 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local require = require +local setfenv = setfenv + +local math = { + ceil = math.ceil, + max = math.max, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local std = require "std.base" +local std = require "std.base" +local Object = require "std.object".prototype + +local compare = std.list.compare +local ipairs = std.ipairs +local len = std.operator.len +local pairs = std.pairs +local unpack = std.table.unpack + + -local Object = require "std.object".prototype +--[[ ================= ]]-- +--[[ Implementatation. ]]-- +--[[ ================= ]]-- -local ipairs, pairs = std.ipairs, std.pairs -local compare = std.list.compare -local len = std.operator.len -local unpack = std.table.unpack local M, prototype diff --git a/lib/std/math.lua b/lib/std/math.lua index 5ebe116..c07b0ad 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,15 +11,36 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local math = math +local require = require +local setfenv = setfenv + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local std = require "std.base" +local std = require "std.base" + + +--[[ ================= ]]-- +--[[ Implementatation. ]]-- +--[[ ================= ]]-- + local M diff --git a/lib/std/object.lua b/lib/std/object.lua index d906559..97b5698 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -22,17 +22,38 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local require = require +local setfenv = setfenv + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end local container = require "std.container" local debug = require "std.debug" -local std = require "std.base" +local std = require "std.base" + + + +--[[ ================= ]]-- +--[[ Implementatation. ]]-- +--[[ ================= ]]-- + local Container = container.prototype diff --git a/lib/std/operator.lua b/lib/std/operator.lua index d364976..1750e11 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -4,20 +4,44 @@ @functional std.operator ]] + +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local require = require +local setfenv = setfenv +local type = type + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local std = require "std.base" +local std = require "std.base" -local pairs, stdtype, tostring = - std.pairs, std.type, std.tostring +local pairs = std.pairs +local stdtype = std.type +local tostring = std.tostring local serialize = std.base.mnemonic + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + local function eqv (a, b) -- If they are the same primitive value, or they share a metatable -- with an __eq metamethod that says they are equivalent, we're done! diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 4a70ee1..97e6d97 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -22,22 +22,55 @@ ]=] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local assert = assert +local error = error +local print = print +local require = require +local setfenv = setfenv +local setmetatable = setmetatable +local tostring = tostring +local type = type + +local io = { + open = io.open, + stderr = io.stderr, +} + +local os = { + exit = os.exit, +} + +local string = { + len = string.len, +} + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local std = require "std.base" +local std = require "std.base" -local Object = require "std.object".prototype +local Object = require "std.object".prototype -local ipairs, pairs = std.ipairs, std.pairs -local last = std.base.last -local len = std.operator.len -local insert = std.table.insert +local insert = std.table.insert +local ipairs = std.ipairs +local last = std.base.last +local len = std.operator.len +local pairs = std.pairs diff --git a/lib/std/package.lua b/lib/std/package.lua index dd41a21..217c557 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -36,20 +36,56 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local package = package +local require = require +local setfenv = setfenv + +local string = { + match = string.match, +} + +local table = { + concat = table.concat, + insert = table.insert, + remove = table.remove, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end local std = require "std.base" -local ipairs, pairs = std.ipairs, std.pairs -local catfile = std.io.catfile -local escape_pattern, split = std.string.escape_pattern, std.string.split -local invert, unpack = std.table.invert, std.table.unpack +local catfile = std.io.catfile +local escape_pattern = std.string.escape_pattern +local invert = std.table.invert +local ipairs = std.ipairs +local pairs = std.pairs +local split = std.string.split +local unpack = std.table.unpack + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + local M diff --git a/lib/std/set.lua b/lib/std/set.lua index f614d8f..1be002a 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -22,25 +22,53 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local getmetatable = getmetatable +local next = next +local rawget = rawget +local rawset = rawset +local require = require +local setfenv = setfenv +local setmetatable = setmetatable +local type = type + +local table = { + concat = table.concat, + sort = table.sort, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local _concat = table.concat -local _sort = table.sort -local _type = type +local std = require "std.base" +local Container = require "std.container".prototype + +local pairs = std.pairs +local pickle = std.string.pickle +local tostring = std.tostring +local stdtype = std.type + -local std = require "std.base" -local Container = require "std.container".prototype -local pairs = std.pairs -local pickle = std.string.pickle -local tostring = std.tostring -local type = std.type +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- local prototype -- forward declaration @@ -57,7 +85,7 @@ local prototype -- forward declaration -- whose values are true. -local elems = std.pairs +local elems = pairs local function insert (set, e) @@ -170,7 +198,7 @@ prototype = Container { _init = function (new, t) local mt = {} for k, v in pairs (t) do - local type_k = _type (k) + local type_k = type (k) if type_k == "number" then insert (new, v) elseif type_k == "string" and k:sub (1, 1) == "_" then @@ -249,8 +277,8 @@ prototype = Container { for k in pairs (self) do keys[#keys + 1] = tostring (k) end - _sort (keys) - return type (self) .. " {" .. _concat (keys, ", ") .. "}" + table.sort (keys) + return stdtype (self) .. " {" .. table.concat (keys, ", ") .. "}" end, --- Return a loadable serialization of this object, where possible. @@ -262,20 +290,20 @@ prototype = Container { for k in pairs (self) do keys[#keys + 1] = pickle (k) end - _sort (keys) - if _type (mt._module) == "string" then + table.sort (keys) + if type (mt._module) == "string" then -- object with _module set - return _concat { + return table.concat { 'require "', mt._module, '".prototype {', - _concat (keys, ","), + table.concat (keys, ","), "}", } end -- rely on caller preloading `local ObjName = require "module".prototype` - return _concat { - mt._type, " {", _concat (keys, ","), "}" + return table.concat { + mt._type, " {", table.concat (keys, ","), "}" } end, } diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index ad330d6..be6df2a 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -27,11 +27,32 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local require = require +local setfenv = setfenv +local tostring = tostring +local type = type + +local table = { + concat = table.concat, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end @@ -43,6 +64,12 @@ local Object = require "std.object".prototype local ielems, insert = std.ielems, std.table.insert + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + local function __concat (self, x) return insert (self, x) end @@ -141,4 +168,6 @@ local prototype = Object { return std.object.Module { prototype = prototype, + + tostring = M.tostring, } diff --git a/lib/std/string.lua b/lib/std/string.lua index 382de80..fc5eaa2 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -11,33 +11,75 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local assert = assert +local getmetatable = getmetatable +local require = require +local setfenv = setfenv +local string = string +local tonumber = tonumber +local tostring = tostring +local type = type + +local io = { + stderr = io.stderr, +} + +local math = { + abs = math.abs, + floor = math.floor, +} + +local table = { + concat = table.concat, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local std = require "std.base" -local debug = require "std.debug" +local std = require "std.base" +local debug = require "std.debug" -local StrBuf = require "std.strbuf".prototype +local StrBuf = require "std.strbuf".prototype -local sortkeys, toqstring = std.base.sortkeys, std.base.toqstring -local getmetamethod, pairs = std.getmetamethod, std.pairs -local callable = std.functional.callable -local copy, keysort = std.base.copy, std.base.keysort -local len = std.operator.len -local render = std.string.render -local insert = std.table.insert -local type = type -- avoid mutual recursion between debug argument checker and string.__index +local callable = std.functional.callable +local copy = std.base.copy +local getmetamethod = std.getmetamethod +local insert = std.table.insert +local keysort = std.base.keysort +local len = std.operator.len +local pairs = std.pairs +local render = std.string.render +local sortkeys = std.base.sortkeys +local toqstring = std.base.toqstring + +local _tostring = std.tostring -local M +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M -local _tostring = std.tostring local function __concat (s, o) return _tostring (s) .. _tostring (o) diff --git a/lib/std/table.lua b/lib/std/table.lua index e4bba72..ad405a0 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,24 +11,54 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local getmetatable = getmetatable +local next = next +local require = require +local setfenv = setfenv +local setmetatable = setmetatable +local table = table +local type = type + +local math = { + ceil = math.ceil, + min = math.min, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local core = _G.table +local std = require "std.base" +local debug = require "std.debug" + +local argerror = debug.argerror +local ipairs = std.ipairs +local pairs = std.pairs +local collect = std.functional.collect +local len = std.operator.len +local leaves = std.tree.leaves + -local std = require "std.base" -local debug = require "std.debug" -local argerror = debug.argerror -local ipairs, pairs = std.ipairs, std.pairs -local collect = std.functional.collect -local len = std.operator.len -local leaves = std.tree.leaves +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- local M, monkeys diff --git a/lib/std/tree.lua b/lib/std/tree.lua index ec375b5..f10ecd0 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -24,25 +24,62 @@ ]] +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local getmetatable = getmetatable +local rawget = rawget +local rawset = rawset +local require = require +local setfenv = setfenv +local setmetatable = setmetatable +local type = type + +local coroutine = { + yield = coroutine.yield, + wrap = coroutine.wrap, +} + +local table = { + remove = table.remove, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end +local std = require "std.base" +local operator = require "std.operator" + +local Container = require "std.container".prototype + +local ielems = std.ielems +local ipairs = std.ipairs +local last = std.base.last +local len = std.operator.len +local leaves = std.tree.leaves +local pairs = std.pairs +local reduce = std.functional.reduce +local stdtype = std.type + -local std = require "std.base" -local operator = require "std.operator" -local Container = require "std.container".prototype +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- -local ielems, ipairs, pairs, stdtype = - std.ielems, std.ipairs, std.pairs, std.type -local last = std.base.last -local reduce = std.functional.reduce -local len = std.operator.len -local leaves = std.tree.leaves local prototype -- forward declaration diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 41d1d2f..e5d0e9a 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -25,24 +25,55 @@ @prototype std.tuple ]] + +--[[ ============================== ]]-- +--[[ Cache all external references. ]]-- +--[[ ============================== ]]-- + + +local error = error +local getmetatable = getmetatable +local require = require +local select = select +local setfenv = setfenv +local setmetatable = setmetatable +local type = type + +local string = { + format = string.format, +} + +local table = { + concat = table.concat, +} + + + +--[[ ====================================== ]]-- +--[[ Empty environment, with strict access. ]]-- +--[[ ====================================== ]]-- + + local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG if _DEBUG.strict then - _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end + _ENV = require "std.strict" {} + if setfenv then setfenv (1, _ENV) end end -local _concat = table.concat -local _format = string.format -local _type = type +local Container = require "std.container".prototype +local std = require "std.base" + +local pickle = std.string.pickle +local stdtype = std.type +local toqstring = std.base.toqstring + -local Container = require "std.container".prototype -local std = require "std.base" -local pickle = std.string.pickle -local type = std.type -local toqstring = std.base.toqstring +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- --- Stringify tuple values, as a memoization key. @@ -147,7 +178,7 @@ local prototype = Container { -- -- 'Tuple ("nil", nil, false)' -- print (Tuple ("nil", nil, false)) __tostring = function (self) - return _format ("%s (%s)", type (self), argstr (self)) + return string.format ("%s (%s)", stdtype (self), argstr (self)) end, --- Return a loadable serialization of this object, where possible. @@ -159,11 +190,11 @@ local prototype = Container { for i = 1, self.n do vals[i] = pickle (self[i]) end - if _type (mt._module) == "string" then - return _format ('require "%s".prototype (%s)', - mt._module, _concat (vals, ",")) + if type (mt._module) == "string" then + return string.format ('require "%s".prototype (%s)', + mt._module, table.concat (vals, ",")) end - return _format ("%s (%s)", mt._type, _concat (vals, ",")) + return string.format ("%s (%s)", mt._type, table.concat (vals, ",")) end, } diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 4f15af7..7976766 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -620,7 +620,7 @@ specify std.debug: wrapped = f ("inner ()", mkmagic) _, badarg, badresult = init (M, "", "inner") - id = function (...) return unpack {...} end + id = function (...) return ... end - it returns the wrapped function: expect (wrapped).not_to_be (inner) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index f42a8b6..c285731 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -9,6 +9,11 @@ before: "foldr", "id", "lambda", "map", "map_with", "memoize", "nop", "op", "product", "reduce", "zip", "zip_with" } + setdebug { deprecate = false } + + deprecate_on = bind (deprecation, {"nil", this_module}) + deprecate_off = bind (deprecation, {false, this_module}) + M = require (this_module) @@ -211,10 +216,8 @@ specify std.functional: f = M.eval - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {"42"})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {"42"})).not_to_contain_error "was deprecated" + expect (deprecate_on ("eval", 42)).to_contain_error "was deprecated" + expect (deprecate_off ("eval", 42)).not_to_contain_error "was deprecated" - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable "=" code. @@ -266,11 +269,9 @@ specify std.functional: f = M.fold - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {M.id, 1, ipairs, {}})). + expect (deprecate_on ("fold", "M.id, 1, ipairs, {}")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {M.id, 1, ipairs, {}})). + expect (deprecate_off ("fold", "M.id, 1, ipairs, {}")). not_to_contain_error "was deprecated" - it works with an empty table: @@ -537,11 +538,9 @@ specify std.functional: f = M.op["[]"] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{2}, 1})). + expect (deprecate_on ("op['[]']", "{2}, 1")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{2}, 1})). + expect (deprecate_off ("op['[]']", "{2}, 1")). not_to_contain_error "was deprecated" - it dereferences a table: @@ -554,11 +553,9 @@ specify std.functional: f = M.op["+"] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {2, 1})). + expect (deprecate_on ("op['+']", "2, 1")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {2, 1})). + expect (deprecate_off ("op['+']", "2, 1")). not_to_contain_error "was deprecated" - it returns the sum of its arguments: @@ -569,11 +566,9 @@ specify std.functional: f = M.op["-"] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {2, 1})). + expect (deprecate_on ("op['-']", "2, 1")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {2, 1})). + expect (deprecate_off ("op['-']", "2, 1")). not_to_contain_error "was deprecated" - it returns the difference of its arguments: @@ -584,11 +579,9 @@ specify std.functional: f = M.op["*"] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {2, 1})). + expect (deprecate_on ("op['*']", "2, 1")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {2, 1})). + expect (deprecate_off ("op['*']", "2, 1")). not_to_contain_error "was deprecated" - it returns the product of its arguments: @@ -599,11 +592,9 @@ specify std.functional: f = M.op["/"] - it writes a deprecation warning on: - setdebug { deprecate = "nil" } - expect (capture (f, {2, 1})). + expect (deprecate_on ("op['/']", "2, 1")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {2, 1})). + expect (deprecate_off ("op['/']", "2, 1")). not_to_contain_error "was deprecated" - it returns the quotient of its arguments: @@ -614,11 +605,9 @@ specify std.functional: f = M.op["and"] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {true, false})). + expect (deprecate_on ("op['and']", "true, false")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {true, false})). + expect (deprecate_off ("op['and']", "true, false")). not_to_contain_error "was deprecated" - it returns the logical and of its arguments: @@ -637,11 +626,9 @@ specify std.functional: f = M.op["or"] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {true, false})). + expect (deprecate_on ("op['or']", "true, false")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {true, false})). + expect (deprecate_off ("op['or']", "true, false")). not_to_contain_error "was deprecated" - it returns the logical or of its arguments: @@ -660,11 +647,9 @@ specify std.functional: f = M.op["not"] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {true})). + expect (deprecate_on ("op['not']", true)). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {true})). + expect (deprecate_off ("op['not']", true)). not_to_contain_error "was deprecated" - it returns the logical not of its argument: @@ -679,11 +664,9 @@ specify std.functional: f = M.op["=="] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {2, 1})). + expect (deprecate_on ("op['==']", "2, 1")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {2, 1})). + expect (deprecate_off ("op['==']", "2, 1")). not_to_contain_error "was deprecated" - it returns true if the arguments are equal: @@ -698,11 +681,9 @@ specify std.functional: f = M.op["~="] - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {2, 1})). + expect (deprecate_on ("op['~=']", "2, 1")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {2, 1})). + expect (deprecate_off ("op['~=']", "2, 1")). not_to_contain_error "was deprecated" - it returns false if the arguments are equal: diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 85bb28c..f166cac 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -4,6 +4,11 @@ before: List = M.prototype l = List {"foo", "bar", "baz"} + setdebug { deprecate = false } + + deprecate_on = bind (deprecation, {"nil", this_module}) + deprecate_off = bind (deprecation, {false, this_module}) + specify std.list: - context when required: @@ -289,10 +294,10 @@ specify std.list: f = M.depair - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("depair", "P {P {1, 'first'}}")). + to_contain_error "was deprecated" + expect (deprecate_off ("depair", "P {P {1, 'first'}}")). + not_to_contain_error "was deprecated" - it returns a primitive table: expect (objtype (f (l))).to_be "table" @@ -309,10 +314,10 @@ specify std.list: f = M.elems - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{}})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{}})).not_to_contain_error "was deprecated" + expect (deprecate_on ("elems", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("elems", "{}")). + not_to_contain_error "was deprecated" - it is an iterator over List members: t = {} @@ -328,10 +333,10 @@ specify std.list: f = l.elems - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("elems", "", "{1, 2}")). + to_contain_error "was deprecated" + expect (deprecate_off ("elems", "", "{1, 2}")). + not_to_contain_error "was deprecated" - it is an iterator over List members: t = {} @@ -349,10 +354,10 @@ specify std.list: f = M.enpair - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {t})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {t})).not_to_contain_error "was deprecated" + expect (deprecate_on ("enpair", "{1,2,three=4}")). + to_contain_error "was deprecated" + expect (deprecate_off ("enpair", "{1,2,three=4}")). + not_to_contain_error "was deprecated" - context as a module function: - it returns a List object: @@ -374,10 +379,11 @@ specify std.list: f = M.filter - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {p, l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {p, l})).not_to_contain_error "was deprecated" + argstr="function (e) return true end, P {1, 2, 5}" + expect (deprecate_on ("filter", argstr)). + to_contain_error "was deprecated" + expect (deprecate_off ("filter", argstr)). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (p, l))).to_be "List" @@ -391,10 +397,11 @@ specify std.list: f = l.filter - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, p})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, p})).not_to_contain_error "was deprecated" + argstr="function (e) return true end" + expect (deprecate_on ("filter", argstr, "{1, 2, 5}")). + to_contain_error "was deprecated" + expect (deprecate_off ("filter", argstr, "{1, 2, 5}")). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l, p))).to_be "List" @@ -413,10 +420,10 @@ specify std.list: f = M.flatten - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("flatten", "P {1, 2, 5}")). + to_contain_error "was deprecated" + expect (deprecate_off ("flatten", "P {1, 2, 5}")). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l))).to_be "List" @@ -432,10 +439,10 @@ specify std.list: f = l.flatten - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("flatten", "", "{1, 2, 5}")). + to_contain_error "was deprecated" + expect (deprecate_off ("flatten", "", "{1, 2, 5}")). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l))).to_be "List" @@ -457,12 +464,11 @@ specify std.list: f = M.foldl - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {op.sum, 1, l})). - to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {op.sum, 1, l})). - not_to_contain_error "was deprecated" + argstr = "require 'std.operator'.sum, 1, P {1, 2, 5}" + expect (deprecate_on ("foldl", argstr)). + to_contain_error "was deprecated" + expect (deprecate_off ("foldl", argstr)). + not_to_contain_error "was deprecated" - context with a table: - it works with an empty table: @@ -486,11 +492,10 @@ specify std.list: f = l.foldl - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, op.sum, 1})). + argstr = "require 'std.operator'.sum, 1" + expect (deprecate_on ("foldl", argstr, "{1, 2, 5}")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, op.sum, 1})). + expect (deprecate_off ("foldl", argstr, "{1, 2, 5}")). not_to_contain_error "was deprecated" - it works with an empty List: @@ -512,12 +517,11 @@ specify std.list: f = M.foldr - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {op.sum, 1, {10}})). - to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {op.sum, 1, {10}})). - not_to_contain_error "was deprecated" + argstr = "require 'std.operator'.sum, 1, P {1, 2, 5}" + expect (deprecate_on ("foldl", argstr)). + to_contain_error "was deprecated" + expect (deprecate_off ("foldl", argstr)). + not_to_contain_error "was deprecated" - context with a table: - it works with an empty table: @@ -542,11 +546,10 @@ specify std.list: f = l.foldr - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, op.sum, 1})). + argstr = "require 'std.operator'.sum, 1" + expect (deprecate_on ("foldr", argstr, "{1, 2, 5}")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, op.sum, 1})). + expect (deprecate_off ("foldr", argstr, "{1, 2, 5}")). not_to_contain_error "was deprecated" - it works with an empty List: @@ -564,11 +567,9 @@ specify std.list: f = M.index_key - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {1, List {{1}}})). + expect (deprecate_on ("index_key", "{1, P {{1}}}")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {1, List {{1}}})). + expect (deprecate_off ("index_key", "{1, P {{1}}}")). not_to_contain_error "was deprecated" - it makes a map of matched table field values to table List offsets: @@ -595,10 +596,10 @@ specify std.list: f = l.index_key - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, 1})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, 1})).not_to_contain_error "was deprecated" + expect (deprecate_on ("index_key", 1, "P {1, 2, 5}")). + to_contain_error "was deprecated" + expect (deprecate_off ("index_key", 1, "P {1, 2, 5}")). + not_to_contain_error "was deprecated" - it makes a map of matched table field values to table List offsets: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} @@ -626,11 +627,9 @@ specify std.list: f = M.index_value - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {1, List {{1}}})). + expect (deprecate_on ("index_value", "{1, P {{1}}}")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {1, List {{1}}})). + expect (deprecate_off ("index_value", "{1, P {{1}}}")). not_to_contain_error "was deprecated" - it makes a table of matched table field values to table List references: @@ -659,10 +658,10 @@ specify std.list: f = l.index_value - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, 1})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, 1})).not_to_contain_error "was deprecated" + expect (deprecate_on ("index_value", 1, "P {1, 2, 5}")). + to_contain_error "was deprecated" + expect (deprecate_off ("index_value", 1, "P {1, 2, 5}")). + not_to_contain_error "was deprecated" - it makes a table of matched table field values to table List references: l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} @@ -694,10 +693,11 @@ specify std.list: f, badarg = init (M, this_module, "map") - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {sq, l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {sq, l})).not_to_contain_error "was deprecated" + argstr = "function () return 1 end, P {1, 2, 5}" + expect (deprecate_on ("map", argstr)). + to_contain_error "was deprecated" + expect (deprecate_off ("map", argstr)). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (sq, l))).to_be "List" @@ -717,10 +717,11 @@ specify std.list: f = l.map - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, sq})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, sq})).not_to_contain_error "was deprecated" + argstr = "function () return 1 end" + expect (deprecate_on ("map", argstr, "{1, 2, 5}")). + to_contain_error "was deprecated" + expect (deprecate_off ("map", argstr, "{1, 2, 5}")). + not_to_contain_error "was deprecated" - it returns a List object: m = f (l, sq) @@ -747,10 +748,11 @@ specify std.list: f = M.map_with - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {fn, l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {fn, l})).not_to_contain_error "was deprecated" + argstr = "function () return 1 end, P {P {1, 2}, P {5}}" + expect (deprecate_on ("map_with", argstr)). + to_contain_error "was deprecated" + expect (deprecate_off ("map_with", argstr)). + not_to_contain_error "was deprecated" - it returns a List object: m = f (fn, l) @@ -781,10 +783,11 @@ specify std.list: f = M.project - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {"third", l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {"third", l})).not_to_contain_error "was deprecated" + argstr = "1, P {P {1, 2}, P {5}}" + expect (deprecate_on ("project", argstr)). + to_contain_error "was deprecated" + expect (deprecate_off ("project", argstr)). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f ("third", l))).to_be "List" @@ -800,10 +803,11 @@ specify std.list: f = l.project - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, "third"})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, "third"})).not_to_contain_error "was deprecated" + init = "{{1, 2}, {5}}" + expect (deprecate_on ("project", 1, init)). + to_contain_error "was deprecated" + expect (deprecate_off ("project", 1, init)). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l, "third"))).to_be "List" @@ -821,10 +825,10 @@ specify std.list: f = M.relems - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("relems", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("relems", "{}")). + not_to_contain_error "was deprecated" - it is a reverse iterator over List members: t = {} @@ -840,10 +844,10 @@ specify std.list: f = l.relems - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("relems", "", "{1, 2}")). + to_contain_error "was deprecated" + expect (deprecate_off ("relems", "", "{1, 2}")). + not_to_contain_error "was deprecated" - it is a reverse iterator over List members: t = {} @@ -895,10 +899,10 @@ specify std.list: f = M.reverse - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{}})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{}})).not_to_contain_error "was deprecated" + expect (deprecate_on ("reverse", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("reverse", "{}")). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l))).to_be "List" @@ -917,10 +921,10 @@ specify std.list: f = l.reverse - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("reverse", "", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("reverse", "", "{}")). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l))).to_be "List" @@ -943,10 +947,10 @@ specify std.list: f = M.shape - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{0}, l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{0}, l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("shape", "{0}, P {1, 2, 5}")). + to_contain_error "was deprecated" + expect (deprecate_off ("shape", "{0}, P {1, 2, 5}")). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f ({2, 3}, l))).to_be "List" @@ -973,10 +977,10 @@ specify std.list: f = l.shape - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, {0}})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, {0}})).not_to_contain_error "was deprecated" + expect (deprecate_on ("shape", "{0}", "{1, 2, 5}")). + to_contain_error "was deprecated" + expect (deprecate_off ("shape", "{0}", "{1, 2, 5}")). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l, {2, 3}))).to_be "List" @@ -1090,10 +1094,10 @@ specify std.list: f = M.transpose - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l})).not_to_contain_error "was deprecated" + expect (deprecate_on ("transpose", "P {P {1, 2}, P {5, 9}}")). + to_contain_error "was deprecated" + expect (deprecate_off ("transpose", "P {P {1, 2}, P {5, 9}}")). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l))).to_be "List" @@ -1118,10 +1122,11 @@ specify std.list: f = M.zip_with - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {l, fn})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {l, fn})).not_to_contain_error "was deprecated" + argstr = "function () return 0 end, P {P {1, 2}, P {5}}" + expect (deprecate_on ("transpose", argstr)). + to_contain_error "was deprecated" + expect (deprecate_off ("transpose", argstr)). + not_to_contain_error "was deprecated" - it returns a List object: expect (objtype (f (l, fn))).to_be "List" diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 08f947d..a2c8420 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -1,8 +1,15 @@ before: - object = require "std.object" + this_module = "std.object" + + object = require (this_module) Object = object.prototype obj = Object {"foo", "bar", baz="quux"} + setdebug { deprecate = false } + + deprecate_on = bind (deprecation, {"nil", this_module}) + deprecate_off = bind (deprecation, {false, this_module}) + function copy (t) local r = {} for k, v in pairs (t) do r[k] = v end @@ -51,10 +58,10 @@ specify std.object: fn = Object.prototype - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (fn, {o})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (fn, {o})).not_to_contain_error "was deprecated" + expect (deprecate_on ("prototype", "", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("prototype", "", "{}")). + not_to_contain_error "was deprecated" - context when called from the object module: - it reports the prototype stored in the object's metatable: diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 3e8d17e..376df44 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -142,6 +142,41 @@ function luaproc (code, arg, stdin) 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 `_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([[ + _DEBUG = { deprecate = %s } + M = require "%s" + P = M.prototype + print (M.%s (%s)) + ]], tostring (deprecate), module, fname, tostring (args)) + else + script = string.format([[ + _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 diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index 919a387..ac90975 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -1,7 +1,14 @@ before: - StrBuf = require "std.strbuf".prototype + this_module = "std.strbuf" + + StrBuf = require (this_module).prototype b = StrBuf {"foo", "bar"} + setdebug { deprecate = false } + + deprecate_on = bind (deprecation, {"nil", this_module}) + deprecate_off = bind (deprecation, {false, this_module}) + specify std.strbuf: - describe require: - it does not perturb the global namespace: @@ -49,23 +56,21 @@ specify std.strbuf: - describe tostring: - context as a module function: - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (StrBuf.tostring, {b})). + expect (deprecate_on ("tostring", "P {'foo', 'bar'}")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (StrBuf.tostring, {b})). + expect (deprecate_off ("tostring", "P {'foo', 'bar'}")). not_to_contain_error "was deprecated" + - it returns buffered string: expect (StrBuf.tostring (b)).to_be "foobar" - context as an object method: - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (b.tostring, {b})). + expect (deprecate_on ("tostring", "", "{'foo', 'bar'}")). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (b.tostring, {b})). + expect (deprecate_off ("tostring", "", "{'foo', 'bar'}")). not_to_contain_error "was deprecated" + - it returns buffered string: expect (b:tostring ()).to_be "foobar" diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index c688a78..491876e 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -11,6 +11,11 @@ before: "tfind", "trim", "wrap" } deprecations = { "assert", "require_version", "tostring" } + setdebug { deprecate = false } + + deprecate_on = bind (deprecation, {"nil", this_module}) + deprecate_off = bind (deprecation, {false, this_module}) + M = require (this_module) getmetatable ("").__concat = M.__concat getmetatable ("").__index = M.__index @@ -66,10 +71,10 @@ specify std.string: f = M.assert - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {"std.string"})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" + expect (deprecate_on ("assert", "true")). + to_contain_error "was deprecated" + expect (deprecate_off ("assert", "true")). + not_to_contain_error "was deprecated" - context when it does not trigger: - it has a truthy initial argument: @@ -491,15 +496,14 @@ specify std.string: f = M.render - it writes an argument passing deprecation warning: - around = function () return "|" end - inside = function () return "_" end - between = function () return "," end + around = "function () return '|' end" + inside = "function () return '_' end" + between = "function () return ',' end" + argstr = table.concat ({around, around, inside, inside, between}, ",") - setdebug { deprecate = "nil" } - expect (capture (f, {around, around, inside, inside, between})). + expect (deprecate_on ("tostring", argstr)). to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {around, around, inside, inside, between})). + expect (deprecate_off ("tostring", argstr)). not_to_contain_error "was deprecated" - context with bad arguments: @@ -546,10 +550,10 @@ specify std.string: f = M.require_version - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {"std.string"})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" + expect (deprecate_on ("require_version", '"std.string"')). + to_contain_error "was deprecated" + expect (deprecate_off ("require_version", '"std.string"')). + not_to_contain_error "was deprecated" - it diagnoses non-existent module: expect (f ("module-not-exists", "", "")).to_raise "module-not-exists" @@ -700,10 +704,10 @@ specify std.string: f = M.tostring - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {"std.string"})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated" + expect (deprecate_on ("tostring", 42)). + to_contain_error "was deprecated" + expect (deprecate_off ("tostring", 42)). + not_to_contain_error "was deprecated" - it renders primitives exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 1cbcde8..5b993b8 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -10,6 +10,11 @@ before: | "size", "sort", "unpack", "values" } deprecations = { "len", "okeys", "metamethod", "ripairs", "totable" } + setdebug { deprecate = false } + + deprecate_on = bind (deprecation, {"nil", this_module}) + deprecate_off = bind (deprecation, {false, this_module}) + M = require "std.table" @@ -269,10 +274,8 @@ specify std.table: f = M.len - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{}})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{}})).not_to_contain_error "was deprecated" + expect (deprecate_on ("len", "{}")).to_contain_error "was deprecated" + expect (deprecate_off ("len", "{}")).not_to_contain_error "was deprecated" - context with bad arguments: badargs.diagnose (f, "std.table.len (table)") @@ -437,10 +440,10 @@ specify std.table: f = M.metamethod - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{}, subject})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" + expect (deprecate_on ("metamethod", "{}, 'subject'")). + to_contain_error "was deprecated" + expect (deprecate_off ("metamethod", "{}, 'subject'")). + not_to_contain_error "was deprecated" - it returns nil for missing metamethods: expect (f (obj, "not a method on obj")).to_be (nil) @@ -515,10 +518,10 @@ specify std.table: f = M.okeys - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{}})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{}})).not_to_contain_error "was deprecated" + expect (deprecate_on ("okeys", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("okeys", "{}")). + not_to_contain_error "was deprecated" - context with bad arguments: badargs.diagnose (f, "std.table.okeys (table)") @@ -610,10 +613,10 @@ specify std.table: f = M.ripairs - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{}, subject})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{}, subject})).not_to_contain_error "was deprecated" + expect (deprecate_on ("ripairs", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("ripairs", "{}")). + not_to_contain_error "was deprecated" - it returns a function, the table and a number: fn, t, i = f {1, 2, 3} @@ -700,11 +703,11 @@ specify std.table: f = M.totable - - it writes a deprecation warning: - setdebug { deprecate = "nil" } - expect (capture (f, {{}})).to_contain_error "was deprecated" - setdebug { deprecate = false } - expect (capture (f, {{}})).not_to_contain_error "was deprecated" + - it writes a deprecation warning: | + expect (deprecate_on ("totable", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("totable", "{}")). + not_to_contain_error "was deprecated" - it calls object's __totable metamethod: object = setmetatable ({content = t}, mt) From c1a09198b3a3e31faab5b51c75e03ac1b07d9aba Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 12 Sep 2015 13:22:04 +0100 Subject: [PATCH 598/703] maint: gitignore entire generated doc tree. Closes #100. * .gitignore: Replace all /doc/* entries with a single /doc. Signed-off-by: Gary V. Vaughan --- .gitignore | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 1d2c4f3..9d796a7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,11 +18,7 @@ /config.log /config.status /configure -/doc/classes -/doc/files -/doc/index.html -/doc/ldoc.css -/doc/modules +/doc /lib/std.lua /luarocks /m4/ax_lua.m4 From 20479afcb6d5ef8934401d830f9ae64ea220b187 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 12 Sep 2015 13:25:24 +0100 Subject: [PATCH 599/703] functional: don't pass a string to `load` in Lua 5.1. * lib/std/functional.lua (load): Remove... (loadstring): ...in favour of this. Adjust callers accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index dffd0df..b102eec 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -13,7 +13,7 @@ --[[ ============================== ]]-- -local load = load or loadstring +local loadstring= loadstring or load local next = next local pcall = pcall local require = require @@ -233,7 +233,7 @@ local lambda = memoize (function (s) local ok, fn if expr then - ok, fn = pcall (load (expr)) + ok, fn = pcall (loadstring (expr)) end -- Diagnose invalid input. From 3621b7a7ea5aba48c99586f84ad7025f235d2c03 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 12 Sep 2015 13:37:16 +0100 Subject: [PATCH 600/703] base: don't pass a string to `load` in Lua 5.1. * lib/std/base.lua (load): Remove... (loadstring): ...in favour of this. Adjust callers accordingly. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index d43c884..4fb0a18 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -32,7 +32,7 @@ local dirsep = string.match (package.config, "^(%S+)\n") local error = error local getmetatable = getmetatable -local load = load or loadstring +local loadstring = loadstring or load local next = next local pairs = pairs local rawget = rawget @@ -252,7 +252,7 @@ end local function eval (s) - return load ("return " .. s)() + return loadstring ("return " .. s)() end From d5f415782f670bd5017cbb6b7cdb9a3d9101bf9b Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 20 Sep 2015 20:47:34 +0100 Subject: [PATCH 601/703] tuple: clean up the implementation. * lib/std/tuple.lua (argstr): Folded into... (prototype._init): ...only caller. Simplify accordingly. (intern): Store interned tuples as `{[{elements}] = "argstr"}`, which doesn't interfere with `__newindex` and allows for faster creation and lookup of tuples. (prototype): Set 0-tuple values on prototype instance. (prototype.__tostring): Lookup argstr instead of recalculating it. (prototype.__index): Simplify accordingly. (prototype.__contents): Remove... (prototype.__unpack): ...and provide this metamethod instead. * lib/std/base.lua (unpack): Support `__unpack` metamethod instead of clumsy tuple specific `__contents` proxy. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 21 +++++++++----- lib/std/base.lua | 5 ++-- lib/std/tuple.lua | 70 +++++++++++++++++++++++------------------------ 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/NEWS.md b/NEWS.md index e5c33a3..75a0c21 100644 --- a/NEWS.md +++ b/NEWS.md @@ -91,13 +91,20 @@ tuples: ```lua - local std = require "std" - local t3 = std.tuple (nil, false, nil) - local t3_ = std.tuple (nil, false, nil) - if t3 == t3_ then - for i, v in std.npairs (t3) do print (i, v) end - end - --> 1 nil 2 false 3 nil + local Tuple = require "std.tuple" {} + local t3 = Tuple (nil, false, nil) + local t3_ = Tuple (nil, false, nil) + assert (t3 == t3_) + + local len = require "std.operator".len + assert (len (t3) == 3) + + local t = {} + for i = 1, len (t3) do t = t3[i] end + assert (len (t) == 3) + + local a, b, c = require "std.table".unpack (t3) + assert (a == nil and b == false and c == nil) ``` - New `functional.product` returns a list of combinations made by diff --git a/lib/std/base.lua b/lib/std/base.lua index 4fb0a18..9926d4a 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -374,9 +374,8 @@ local function unpack (t, i, j) local m = getmetamethod (t, "__len") j = m and m (t) or maxn (t) end - -- use the __contents metatable instead of t when present - -- FIXME: get rid of this! - return table.unpack ((getmetatable (t) or {}).__contents or t, i or 1, j) + local fn = getmetamethod (t, "__unpack") or table.unpack + return fn (t, tonumber (i) or 1, tonumber (j)) end diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index e5d0e9a..d7035c8 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -33,6 +33,7 @@ local error = error local getmetatable = getmetatable +local next = next local require = require local select = select local setfenv = setfenv @@ -45,6 +46,7 @@ local string = { local table = { concat = table.concat, + unpack = table.unpack or unpack, } @@ -76,32 +78,17 @@ local toqstring = std.base.toqstring --[[ =============== ]]-- ---- Stringify tuple values, as a memoization key. --- @tparam prototype tuple tuple to process --- @treturn string a comma separated ordered list of stringified *tup* elements -local function argstr (tuple) - local s = {} - for i = 1, tuple.n do - local v = tuple[i] - s[i] = toqstring (v) - end - return table.concat (s, ", ") -end - - -- Maintain a weak functable of all interned tuples. -- @function intern --- @param ... tuple elements --- @treturn table an interned proxied table with ... elements +-- @int n number of elements in *t*, including trailing `nil`s +-- @tparam table t table of elements +-- @treturn table interned *n*-tuple *t* local intern = setmetatable ({}, { __mode = "kv", - __call = function (self, ...) - local t = {n = select ("#", ...), ...} - local k = argstr (t) + __call = function (self, k, t) if self[k] == nil then - -- Use a proxy table so that __newindex always fires - self[k] = setmetatable ({}, { __contents = t, __index = t }) + self[k] = {[t] = k} end return self[k] end, @@ -109,9 +96,9 @@ local intern = setmetatable ({}, { ---[[ ============= ]]-- ---[[ Tuple Object. ]]-- ---[[ ============= ]]-- +--[[ ================== ]]-- +--[[ Type Declarations. ]]-- +--[[ ================== ]]-- --- Tuple prototype object. @@ -133,22 +120,18 @@ local prototype = Container { _type = "std.tuple.Tuple", _init = function (obj, ...) - return intern (...) + local n = select ("#", ...) + local s, t = {}, {n = n, ...} + for i = 1, n do s[i] = toqstring (t[i]) end + return intern (table.concat (s, ", "), t) end, --- Metamethods -- @section metamethods - -- The actual contents of *tup*. - -- This ensures __newindex will trigger for existing elements too. - -- It also informs @{std.table.unpack} that that the elements to unpack are - -- not in the usual place. - __contents = getmetatable (intern ()).__contents, - - - -- Another reference to the proxy table, so that [] operations work as - -- expected. - __index = getmetatable (intern ()).__index, + __index = function (self, k) + return next (self) [k] + end, --- Return the length of this tuple. -- @function prototype:__len @@ -178,7 +161,21 @@ local prototype = Container { -- -- 'Tuple ("nil", nil, false)' -- print (Tuple ("nil", nil, false)) __tostring = function (self) - return string.format ("%s (%s)", stdtype (self), argstr (self)) + local _, argstr = next (self) + return string.format ("%s (%s)", stdtype (self), argstr) + end, + + --- Unpack tuple values between index *i* and *j*, inclusive. + -- @function prototype:__unpack + -- @int[opt=1] i first index to unpack + -- @int[opt=len(t)] j last index to unpack + -- @return ... values at indices *i* through *j*, inclusive + -- @usage + -- t = Tuple (1, 3, 2, 5) + -- --> 3, 2, 5 + -- table.unpack (t, 2) + __unpack = function (self, i, j) + return table.unpack (next (self), i, j) end, --- Return a loadable serialization of this object, where possible. @@ -196,6 +193,9 @@ local prototype = Container { end return string.format ("%s (%s)", mt._type, table.concat (vals, ",")) end, + + -- Prototype is the 0-tuple. + [intern ("", {n = 0})] = "", } From 364071d4acda9cc3edc19fb67d2b70c6b4779250 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 24 Sep 2015 21:39:03 +0100 Subject: [PATCH 602/703] refactor: simplify and speed up module symbol caching. * lib/std/strict.lua (_ENV): Declare all require external symbols. Adjust all callers. * lib/std.lua.in: Cache all external symbols in local variables, flattening the namespace to minimize table lookups at runtime. Remove access to everything else by setting _ENV to an empty table. Use `setfenv` on 5.1 to initialize module environment with _ENV. Adjust all callers. * lib/std/base.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/debug_init/init.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/object.lua, lib/std/operator.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/strict.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua, lib/std/tuple.lua: Likewise. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 42 +++++------ lib/std/base.lua | 123 +++++++++++++------------------- lib/std/container.lua | 41 +++++------ lib/std/debug.lua | 137 +++++++++++++++--------------------- lib/std/debug_init/init.lua | 14 +++- lib/std/functional.lua | 90 ++++++++++------------- lib/std/io.lua | 118 +++++++++++++++---------------- lib/std/list.lua | 58 +++++++-------- lib/std/math.lua | 43 +++++------ lib/std/object.lua | 47 ++++++------- lib/std/operator.lua | 36 ++++------ lib/std/optparse.lua | 83 +++++++++------------- lib/std/package.lua | 86 +++++++++------------- lib/std/set.lua | 69 ++++++++---------- lib/std/strbuf.lua | 52 ++++++-------- lib/std/strict.lua | 12 +++- lib/std/string.lua | 112 ++++++++++++----------------- lib/std/table.lua | 91 +++++++++++------------- lib/std/tree.lua | 84 +++++++++------------- lib/std/tuple.lua | 66 +++++++---------- 20 files changed, 594 insertions(+), 810 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index 8789baa..dd89e32 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -20,37 +20,27 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - -local _G = _G - -local ipairs = ipairs -local pairs = pairs -local pcall = pcall -local rawset = rawset -local require = require -local setfenv = setfenv +local _ENV = _G +local _G = _G +local ipairs = ipairs +local pairs = pairs +local pcall = pcall +local rawset = rawset +local require = require +local setfenv = setfenv or function () end local setmetatable = setmetatable -local type = type - +local type = type ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- +local argscheck = require "std.debug".argscheck +local std = require "std.base" - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - -local std = require "std.base" +setfenv (1, _ENV) @@ -111,7 +101,7 @@ end local function X (decl, fn) - return require "std.debug".argscheck ("std." .. decl, fn) + return argscheck ("std." .. decl, fn) end M = { diff --git a/lib/std/base.lua b/lib/std/base.lua index 9926d4a..3670d06 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,67 +23,42 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - -local dirsep = string.match (package.config, "^(%S+)\n") - -local error = error +local _ENV = _G +local dirsep = string.match (package.config, "^(%S+)\n") +local error = error local getmetatable = getmetatable local loadstring = loadstring or load -local next = next -local pairs = pairs -local rawget = rawget -local require = require -local select = select -local setfenv = setfenv +local next = next +local pairs = pairs +local rawget = rawget +local require = require +local select = select +local setfenv = setfenv or function () end local setmetatable = setmetatable -local tonumber = tonumber -local tostring = tostring -local type = type - -local coroutine = { - wrap = coroutine.wrap, - yield = coroutine.yield, -} - -local math = { - huge = math.huge, - min = math.min, -} - -local io = { - type = io.type, -} - -local string = { - find = string.find, - format = string.format, -} - -local table = { - concat = table.concat, - insert = table.insert, - maxn = table.maxn, - sort = table.sort, - unpack = table.unpack or unpack, -} - - - ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +local tonumber = tonumber +local tostring = tostring +local type = type + +local coroutine_wrap = coroutine.wrap +local coroutine_yield = coroutine.yield +local math_huge = math.huge +local math_min = math.min +local io_type = io.type +local string_find = string.find +local string_format = string.format +local table_concat = table.concat +local table_insert = table.insert +local table_maxn = table.maxn +local table_sort = table.sort +local table_unpack = table.unpack or unpack + + +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end +setfenv (1, _ENV) @@ -101,7 +76,7 @@ local argerror, getmetamethod, len, vcompare local function assert (expect, fmt, arg1, ...) - local msg = (arg1 ~= nil) and string.format (fmt, arg1, ...) or fmt or "" + local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or "" return expect or error (msg, 2) end @@ -111,7 +86,7 @@ local function insert (t, pos, v) if pos < 1 or pos > len (t) + 1 then argerror ("std.table.insert", 2, "position " .. pos .. " out of bounds", 2) end - table.insert (t, pos, v) + table_insert (t, pos, v) return t end @@ -136,7 +111,7 @@ local function pairs (t) end -local maxn = table.maxn or function (t) +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 @@ -187,13 +162,13 @@ end local function catfile (...) - return table.concat ({...}, dirsep) + return table_concat ({...}, dirsep) end local function compare (l, m) local lenl, lenm = len (l), len (m) - for i = 1, math.min (lenl, lenm) do + for i = 1, math_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] @@ -303,10 +278,10 @@ local function leaves (it, tr) visit (v) end else - coroutine.yield (n) + coroutine_yield (n) end end - return coroutine.wrap (visit), tr + return coroutine_wrap (visit), tr end @@ -374,7 +349,7 @@ local function unpack (t, i, j) local m = getmetamethod (t, "__len") j = m and m (t) or maxn (t) end - local fn = getmetamethod (t, "__unpack") or table.unpack + local fn = getmetamethod (t, "__unpack") or table_unpack return fn (t, tonumber (i) or 1, tonumber (j)) end @@ -480,19 +455,19 @@ local function render (x, fns, roots) buf[#buf + 1] = sep (x, kp, vp) -- buffer << trailing separator buf[#buf + 1] = fns.close (x) -- buffer << table close - return table.concat (buf) -- stringify buffer + return table_concat (buf) -- stringify buffer end end local function toqstring (x) if type (x) ~= "string" then return tostring (x) end - return string.format ("%q", x) + return string_format ("%q", x) end local function sortkeys (t) - table.sort (t, keysort) + table_sort (t, keysort) return t end @@ -509,7 +484,7 @@ local function mnemonic (...) for i = 1, n do buf[i] = render (seq[i], mnemonic_vtable) end - return table.concat (buf, ",") + return table_concat (buf, ",") end @@ -532,9 +507,9 @@ local pickle_vtable = { -- math if x ~= x then return "0/0" - elseif x == math.huge then + elseif x == math_huge then return "math.huge" - elseif x == -math.huge then + elseif x == -math_huge then return "-math.huge" elseif x == nil then return "nil" @@ -543,7 +518,7 @@ local pickle_vtable = { -- common types local type_x = type (x) if type_x == "string" then - return string.format ("%q", x) + return string_format ("%q", x) elseif type_x == "number" or type_x == "boolean" then return tostring (x) end @@ -566,7 +541,7 @@ end local function raise (bad, to, name, i, extramsg, level) level = level or 1 - local s = string.format ("bad %s #%d %s '%s'", bad, i, to, name) + local s = string_format ("bad %s #%d %s '%s'", bad, i, to, name) if extramsg ~= nil then s = s .. " (" .. extramsg .. ")" end @@ -612,7 +587,7 @@ local function split (s, sep) end local b, slen = 0, len (s) while b <= slen do - local e, n, m = string.find (s, patt, b + 1) + local e, n, m = string_find (s, patt, b + 1) insert (r, m or s:sub (b + 1, slen)) b = n or slen + 1 end @@ -696,7 +671,7 @@ return { tostring = function (x) return render (x, tostring_vtable) end, type = function (x) - return (getmetatable (x) or {})._type or io.type (x) or type (x) + return (getmetatable (x) or {})._type or io_type (x) or type (x) end, base = { diff --git a/lib/std/container.lua b/lib/std/container.lua index 4e75ddb..fab8be6 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -29,40 +29,24 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - local getmetatable = getmetatable -local next = next -local require = require -local select = select -local setfenv = setfenv +local next = next +local select = select +local setfenv = setfenv or function () end local setmetatable = setmetatable +local type = type + local string_find = string.find local string_sub = string.sub local table_concat = table.concat -local type = type - - - ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then - _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end -end +local _DEBUG = require "std.debug_init"._DEBUG local std = require "std.base" local debug = require "std.debug" +local Module = std.object.Module + local copy = std.base.copy local ipairs = std.ipairs local mapfields = std.object.mapfields @@ -70,6 +54,13 @@ local pickle = std.string.pickle local render = std.string.render local tostring = std.tostring +if _DEBUG.strict then + _ENV = require "std.strict" {} +else + _ENV = {} +end +setfenv (1, _ENV) + --[[ ================= ]]-- @@ -315,7 +306,7 @@ if _DEBUG.argcheck then end -return std.object.Module { +return Module { prototype = setmetatable ({}, prototype), --- Functions diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 4b5d3f9..2afff6c 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -29,51 +29,48 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - -local debug = debug -local error = error -local getfenv = getfenv +local _ENV = _G +local debug = debug +local error = error +local getfenv = getfenv local getmetatable = getmetatable -local next = next -local pcall = pcall -local require = require -local setfenv = setfenv +local next = next +local pcall = pcall +local setfenv = setfenv or function () end local setmetatable = setmetatable -local type = type - -local io = { - stderr = io.stderr, - type = io.type, -} - -local math = { - floor = math.floor, - huge = math.huge, - max = math.max, -} - -local string = { - format = string.format, - match = string.match, -} - -local table = { - concat = table.concat, - remove = table.remove, - sort = table.sort, -} +local type = type + +local io_stderr = io.stderr +local io_type = io.type +local math_floor = math.floor +local math_huge = math.huge +local math_max = math.max +local string_format = string.format +local table_concat = table.concat +local table_remove = table.remove +local table_sort = table.sort + + +local std = require "std.base" + +local argerror = std.debug.argerror +local copy = std.base.copy +local insert = std.table.insert +local ipairs = std.ipairs +local last = std.base.last +local len = std.operator.len +local maxn = std.table.maxn +local merge = std.base.merge +local nop = std.functional.nop +local pairs = std.pairs +local raise = std.base.raise +local split = std.string.split +local stdtype = std.type +local tostring = std.tostring +local unpack = std.table.unpack ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - - --- Control std.debug function behaviour. -- To declare debugging state, set _DEBUG either to `false` to disable all -- runtime debugging; to any "truthy" value (equivalent to enabling everything @@ -93,29 +90,12 @@ local table = { -- @usage _DEBUG = { argcheck = false, level = 9, strict = false } local _DEBUG = require "std.debug_init"._DEBUG -local _ENV = _G - if _DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" - -local argerror = std.debug.argerror -local copy = std.base.copy -local insert = std.table.insert -local ipairs = std.ipairs -local last = std.base.last -local len = std.operator.len -local maxn = std.table.maxn -local pairs = std.pairs -local raise = std.base.raise -local split = std.string.split -local stdtype = std.type -local tostring = std.tostring -local unpack = std.table.unpack +setfenv (1, _ENV) @@ -134,7 +114,7 @@ local function DEPRECATIONMSG (version, name, extramsg, level) local _, where = pcall (function () error ("", level + 3) end) if _DEBUG.deprecate == nil then - return (where .. string.format ("%s was deprecated in release %s, %s.\n", + return (where .. string_format ("%s was deprecated in release %s, %s.\n", name, tostring (version), extramsg)) end @@ -149,7 +129,7 @@ local function DEPRECATED (version, name, extramsg, fn) if not _DEBUG.deprecate then return function (...) - io.stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) + io_stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) return fn (...) end end @@ -328,11 +308,11 @@ end local function concat (alternatives) if len (alternatives) > 1 then local t = copy (alternatives) - local top = table.remove (t) + local top = table_remove (t) t[#t] = t[#t] .. " or " .. top alternatives = t end - return table.concat (alternatives, ", ") + return table_concat (alternatives, ", ") end @@ -345,7 +325,7 @@ local function extramsg_mismatch (expectedtypes, actual, index) elseif actualtype == "string" and actual:sub (1, 1) == ":" then actualtype = actual elseif type (actual) == "table" and next (actual) == nil then - local matchstr = "," .. table.concat (expectedtypes, ",") .. "," + local matchstr = "," .. table_concat (expectedtypes, ",") .. "," if actualtype == "table" and matchstr == ",#list," then actualtype = "empty list" elseif actualtype == "table" or matchstr:match ",#" then @@ -419,7 +399,7 @@ if _DEBUG.argcheck then local function checktype (check, actual) if check == "any" and actual ~= nil then return true - elseif check == "file" and io.type (actual) == "file" then + elseif check == "file" and io_type (actual) == "file" then return true end @@ -439,7 +419,7 @@ if _DEBUG.argcheck then return true end elseif check == "int" then - if actualtype == "number" and actual == math.floor (actual) then + if actualtype == "number" and actual == math_floor (actual) then return true end elseif type (check) == "string" and check:sub (1, 1) == ":" then @@ -600,7 +580,7 @@ if _DEBUG.argcheck then end -- Ensure the longest permutation is first in the list. - table.sort (permutations, function (a, b) return #a > #b end) + table_sort (permutations, function (a, b) return #a > #b end) output = { bad = "result", @@ -616,13 +596,13 @@ if _DEBUG.argcheck then local argt = {...} -- Don't check type of self if fname has a ':' in it. - if fname:find (":") then table.remove (argt, 1) end + if fname:find (":") then table_remove (argt, 1) end -- Diagnose bad inputs. diagnose (argt, input) -- Propagate outer environment to inner function. - local x = math.max -- ??? getfenv(1) fails if we remove this ??? + local x = math_max -- ??? FIXME: getfenv(1) fails if we remove this ??? setfenv (inner, getfenv (1)) -- Execute. @@ -642,7 +622,7 @@ else -- Turn off argument checking if _DEBUG is false, or a table containing -- a false valued `argcheck` field. - argcheck = std.functional.nop + argcheck = nop argscheck = function (decl, inner) return inner end end @@ -653,12 +633,12 @@ local function say (n, ...) if type (n) ~= "number" then level, argt = 1, {n, ...} end - if _DEBUG.level ~= math.huge and + if _DEBUG.level ~= math_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] = tostring (v) end - io.stderr:write (table.concat (t, "\t") .. "\n") + io_stderr:write (table_concat (t, "\t") .. "\n") end end @@ -676,7 +656,7 @@ local function trace (event) if event == "call" then level = level + 1 else - level = math.max (level - 1, 0) + level = math_max (level - 1, 0) end if t.what == "main" then if event == "call" then @@ -690,7 +670,7 @@ local function trace (event) else s = s .. event .. " " .. (t.name or "(C)") .. " [" .. t.what .. "]" end - io.stderr:write (s .. "\n") + io_stderr:write (s .. "\n") end -- Set hooks according to _DEBUG @@ -954,11 +934,6 @@ M = { } -for k, v in pairs (debug) do - M[k] = M[k] or v -end - - --- Metamethods -- @section metamethods @@ -989,4 +964,4 @@ M.toomanyargmsg = DEPRECATED ("41.2.0", "debug.toomanyargmsg", end) -return setmetatable (M, metatable) +return setmetatable (merge (M, debug), metatable) diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index cb0e3c9..670450b 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -1,3 +1,15 @@ +-- Strict mode is always enabled in this module. + +local _ENV = require "std.strict" { + _G = _G, + math_huge = math.huge, + rawget = rawget, + setfenv = setfenv or function () end, + type = type, +} +setfenv (1, _ENV) + + -- Debugging is on by default local M = {} @@ -14,7 +26,7 @@ elseif _DEBUG == false then argcheck = false, call = false, deprecate = false, - level = math.huge, + level = math_huge, strict = false, } diff --git a/lib/std/functional.lua b/lib/std/functional.lua index b102eec..f1f92fc 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,51 +8,44 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - -local loadstring= loadstring or load -local next = next -local pcall = pcall -local require = require -local select = select -local setfenv = setfenv +local _ENV = _G +local loadstring = loadstring or load +local next = next +local pcall = pcall +local select = select +local setfenv = setfenv or function () end local setmetatable = setmetatable -local table = { - remove = table.remove, -} - - - ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +local table_remove = table.remove + + +local std = require "std.base" +local debug = require "std.debug" +local operator = require "std.operator" + +local DEPRECATED = debug.DEPRECATED +local argscheck = debug.argscheck +local callable = std.functional.callable +local collect = std.functional.collect +local copy = std.base.copy +local ielems = std.ielems +local ipairs = std.ipairs +local ireverse = std.ireverse +local len = std.operator.len +local mnemonic = std.base.mnemonic +local nop = std.functional.nop +local npairs = std.npairs +local pairs = std.pairs +local reduce = std.functional.reduce +local render = std.string.render +local unpack = std.table.unpack + +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" - -local callable = std.functional.callable -local copy = std.base.copy -local ielems = std.ielems -local ipairs = std.ipairs -local ireverse = std.ireverse -local len = std.operator.len -local npairs = std.npairs -local pairs = std.pairs -local reduce = std.functional.reduce -local render = std.string.render -local unpack = std.table.unpack +setfenv (1, _ENV) @@ -188,7 +181,7 @@ local function id (...) end -local serialize = std.base.mnemonic +local serialize = mnemonic local function memoize (fn, mnemonic) mnemonic = mnemonic or serialize @@ -308,7 +301,7 @@ local function product (...) else -- Accumulate a list of products, starting by popping the last -- argument and making each member a one element list. - local d = map (lambda '={_1}', ielems, table.remove (argt)) + local d = map (lambda '={_1}', ielems, table_remove (argt)) -- Right associatively fold in remaining argt members. return foldr (_product, d, argt) end @@ -339,7 +332,7 @@ end local function X (decl, fn) - return require "std.debug".argscheck ("std.functional." .. decl, fn) + return argscheck ("std.functional." .. decl, fn) end local M = { @@ -390,7 +383,7 @@ local M = { -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} - collect = X ("collect ([func], any...)", std.functional.collect), + collect = X ("collect ([func], any...)", collect), --- Compose functions. -- @function compose @@ -557,7 +550,7 @@ local M = { -- @see id -- @usage -- if unsupported then vtable["memrmem"] = nop end - nop = std.functional.nop, -- ignores all arguments + nop = nop, -- ignores all arguments --- Functional list product. -- @@ -623,9 +616,6 @@ local M = { --[[ ============= ]]-- -local DEPRECATED = require "std.debug".DEPRECATED - - M.eval = DEPRECATED ("41", "'std.functional.eval'", "use 'std.eval' instead", std.eval) @@ -646,8 +636,6 @@ M.fold = DEPRECATED ("41", "'std.functional.fold'", "use 'std.functional.reduce' instead", fold) -local operator = require "std.operator" - local function DEPRECATEOP (old, new) return DEPRECATED ("41", "'std.functional.op[" .. old .. "]'", "use 'std.operator." .. new .. "' instead", operator[new]) diff --git a/lib/std/io.lua b/lib/std/io.lua index 0758862..10c710d 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,59 +11,53 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - -local _G = _G - -local arg = arg -local error = error +local _ENV = _ENV +local _G = _G +local arg = arg +local error = error local getmetatable = getmetatable -local io = io -local rawget = rawget -local require = require -local setfenv = setfenv +local io = io +local rawget = rawget +local setfenv = setfenv or function () end local setmetatable = debug.setmetatable -local type = type - -local string = { - format = string.format, -} - -local table = { - concat = table.concat, -} - - ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +local type = type + +local io_input = io.input +local io_open = io.open +local io_output = io.output +local io_popen = io.popen +local io_stderr = io.stderr +local io_stdin = io.stdin +local io_type = io.type +local io_write = io.write +local string_format = string.format +local table_concat = table.concat + + +local std = require "std.base" + +local argerror = std.debug.argerror +local argscheck = require "std.debug".argscheck +local catfile = std.io.catfile +local copy = std.base.copy +local dirsep = std.package.dirsep +local insert = std.table.insert +local ipairs = std.ipairs +local leaves = std.tree.leaves +local len = std.operator.len +local merge = std.base.merge +local pairs = std.pairs +local split = std.string.split +local tostring = std.tostring + +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end +setfenv (1, _ENV) -local std = require "std.base" -local debug = require "std.debug" - -local argerror = debug.argerror -local catfile = std.io.catfile -local dirsep = std.package.dirsep -local insert = std.table.insert -local ipairs = std.ipairs -local leaves = std.tree.leaves -local len = std.operator.len -local pairs = std.pairs -local split = std.string.split -local tostring = std.tostring - --[[ =============== ]]-- @@ -76,9 +70,9 @@ local M, monkeys local function input_handle (h) if h == nil then - return io.input () + return io_input () elseif type (h) == "string" then - return io.open (h) + return io_open (h) end return h end @@ -110,9 +104,9 @@ end local function writelines (h, ...) - if io.type (h) ~= "file" then - io.write (h, "\n") - h = io.output () + if io_type (h) ~= "file" then + io_write (h, "\n") + h = io_output () end for v in leaves (ipairs, {...}) do h:write (v, "\n") @@ -122,7 +116,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.io = std.base.copy (namespace.io or {}, monkeys) + namespace.io = copy (namespace.io or {}, monkeys) if namespace.io.stdin then local mt = getmetatable (namespace.io.stdin) or {} @@ -142,9 +136,9 @@ local function process_files (fn) end for i, v in ipairs (arg) do if v == "-" then - io.input (io.stdin) + io_input (io_stdin) else - io.input (v) + io_input (v) end fn (v, i) end @@ -172,12 +166,12 @@ local function warnfmt (msg, ...) end end if #prefix > 0 then prefix = prefix .. " " end - return prefix .. string.format (msg, ...) + return prefix .. string_format (msg, ...) end local function warn (msg, ...) - writelines (io.stderr, warnfmt (msg, ...)) + writelines (io_stderr, warnfmt (msg, ...)) end @@ -188,7 +182,7 @@ end local function X (decl, fn) - return debug.argscheck ("std.io." .. decl, fn) + return argscheck ("std.io." .. decl, fn) end @@ -240,7 +234,7 @@ M = { -- @see catfile -- @usage dirpath = catdir ("", "absolute", "directory") catdir = X ("catdir (string...)", function (...) - return (table.concat ({...}, dirsep):gsub("^$", dirsep)) + return (table_concat ({...}, dirsep):gsub("^$", dirsep)) end), --- Concatenate one or more directories and a filename into a path. @@ -319,7 +313,7 @@ M = { -- @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 (io.popen (c)) end), + shell = X ("shell (string)", function (c) return slurp (io_popen (c)) end), --- Slurp a file handle. -- @function slurp @@ -340,10 +334,10 @@ M = { } -monkeys = std.base.copy ({}, M) -- before deprecations and core merge +monkeys = copy ({}, M) -- before deprecations and core merge -return std.base.merge (M, io) +return merge (M, io) diff --git a/lib/std/list.lua b/lib/std/list.lua index 79ee8dd..fe33ddf 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -17,41 +17,34 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- +local _ENV = _G +local require = require +local setfenv = setfenv or function () end +local math_ceil = math.ceil +local math_max = math.max -local require = require -local setfenv = setfenv - -local math = { - ceil = math.ceil, - max = math.max, -} +local debug = require "std.debug" +local std = require "std.base" +local Module = std.object.Module +local Object = require "std.object".prototype ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- +local DEPRECATED = debug.DEPRECATED +local argscheck = debug.argscheck +local compare = std.list.compare +local ipairs = std.ipairs +local len = std.operator.len +local pairs = std.pairs +local unpack = std.table.unpack -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" -local Object = require "std.object".prototype - -local compare = std.list.compare -local ipairs = std.ipairs -local len = std.operator.len -local pairs = std.pairs -local unpack = std.table.unpack +setfenv (1, _ENV) @@ -118,9 +111,6 @@ end -- with all references to these functions further down. -local DEPRECATED = require "std.debug".DEPRECATED - - local function depair (ls) local t = {} for _, v in ipairs (ls) do @@ -249,7 +239,7 @@ local function shape (s, l) end end if zero then - s[zero] = math.ceil (len (l) / size) + s[zero] = math_ceil (len (l) / size) end local function fill (i, d) if d > len (s) then @@ -271,7 +261,7 @@ end local function transpose (ls) local rs, lenls, dims = prototype {}, len (ls), map (len, ls) if len (dims) > 0 then - for i = 1, math.max (unpack (dims)) do + for i = 1, math_max (unpack (dims)) do rs[i] = prototype {} for j = 1, lenls do rs[i][j] = ls[j][i] @@ -294,7 +284,7 @@ end local function X (decl, fn) - return require "std.debug".argscheck ("std.list." .. decl, fn) + return argscheck ("std.list." .. decl, fn) end @@ -454,7 +444,7 @@ prototype = Object { } -return std.object.Module { +return Module { prototype = prototype, append = prototype.append, diff --git a/lib/std/math.lua b/lib/std/math.lua index c07b0ad..3143b03 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,31 +11,26 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- +local _ENV = _G +local math = math +local setfenv = setfenv or function () end +local math_floor = math.floor -local math = math -local require = require -local setfenv = setfenv +local std = require "std.base" +local argscheck = require "std.debug".argscheck +local copy = std.base.copy +local merge = std.base.merge - ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end +setfenv (1, _ENV) -local std = require "std.base" - --[[ ================= ]]-- --[[ Implementatation. ]]-- @@ -45,28 +40,26 @@ local std = require "std.base" local M -local _floor = math.floor - local function floor (n, p) if p and p ~= 0 then local e = 10 ^ p - return _floor (n * e) / e + return math_floor (n * e) / e else - return _floor (n) + return math_floor (n) end end local function monkey_patch (namespace) namespace = namespace or _G - namespace.math = std.base.copy (namespace.math or {}, M) + namespace.math = copy (namespace.math or {}, M) return M end local function round (n, p) local e = 10 ^ (p or 0) - return _floor (n * e + 0.5) / e + return math_floor (n * e + 0.5) / e end @@ -77,7 +70,7 @@ end local function X (decl, fn) - return require "std.debug".argscheck ("std.math." .. decl, fn) + return argscheck ("std.math." .. decl, fn) end @@ -114,4 +107,4 @@ M = { } -return std.base.merge (M, math) +return merge (M, math) diff --git a/lib/std/object.lua b/lib/std/object.lua index 97b5698..e724a4d 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -22,31 +22,29 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- +local _ENV = _G +local setfenv = setfenv or function () end -local require = require -local setfenv = setfenv +local debug = require "std.debug" +local std = require "std.base" +local Container = require "std.container".prototype +local Module = std.object.Module +local DEPRECATED = debug.DEPRECATED +local argscheck = debug.argscheck +local getmetamethod = std.getmetamethod +local mapfields = std.object.mapfields +local type = std.type ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local container = require "std.container" -local debug = require "std.debug" -local std = require "std.base" +setfenv (1, _ENV) @@ -55,11 +53,10 @@ local std = require "std.base" --[[ ================= ]]-- -local Container = container.prototype local function X (decl, fn) - return debug.argscheck ("std.object." .. decl, fn) + return argscheck ("std.object." .. decl, fn) end @@ -113,7 +110,7 @@ local prototype = Container { -- [1] = Node { ["0"] = states[1], [""] = states.finish }, -- finish = Node {}, -- } - clone = std.getmetamethod (Container, "__call"), + clone = getmetamethod (Container, "__call"), --- Type of this object. -- @function prototype:type @@ -121,7 +118,7 @@ local prototype = Container { -- @see std.type -- @usage -- assert (Object:type () == getmetatable (Object)._type) - type = X ("type (?any)", std.type), + type = X ("type (?any)", type), --- Object Functions @@ -141,16 +138,16 @@ local prototype = Container { -- metatable of private fields (if any), both renamed according to -- *map* -- @see std.container.mapfields - mapfields = X ("mapfields (table, table|object, ?table)", std.object.mapfields), + mapfields = X ("mapfields (table, table|object, ?table)", mapfields), -- Backwards compatibility: - prototype = debug.DEPRECATED ("41.3", "'std.object.prototype'", std.type), + prototype = DEPRECATED ("41.3", "'std.object.prototype'", type), }, } -return std.object.Module { +return Module { prototype = prototype, - type = debug.DEPRECATED ("41.3", "'std.object.type'", std.type), + type = DEPRECATED ("41.3", "'std.object.type'", type), } diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 1750e11..cbd84d9 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -5,35 +5,23 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- +local _ENV = _G +local setfenv = setfenv or function () end +local type = type -local require = require -local setfenv = setfenv -local type = type +local std = require "std.base" +local len = std.operator.len +local serialize = std.base.mnemonic +local tostring = std.tostring ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" - -local pairs = std.pairs -local stdtype = std.type -local tostring = std.tostring -local serialize = std.base.mnemonic +setfenv (1, _ENV) @@ -70,7 +58,7 @@ local M = { -- @tparam object|string|table x operand -- @treturn int length of list part of *t* -- @usage for i = 1, len (t) do process (t[i]) end - len = std.operator.len, + len = len, --- Dereference a table. -- @tparam table t a table diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 97e6d97..59f85d0 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -22,55 +22,38 @@ ]=] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - -local assert = assert -local error = error -local print = print -local require = require -local setfenv = setfenv +local _ENV = _G +local assert = assert +local error = error +local print = print +local require = require +local setfenv = setfenv or function () end local setmetatable = setmetatable -local tostring = tostring -local type = type +local tostring = tostring +local type = type -local io = { - open = io.open, - stderr = io.stderr, -} - -local os = { - exit = os.exit, -} +local io_open = io.open +local io_stderr = io.stderr +local os_exit = os.exit +local string_len = string.len -local string = { - len = string.len, -} +local std = require "std.base" ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- +local Object = require "std.object".prototype -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG +local insert = std.table.insert +local ipairs = std.ipairs +local last = std.base.last +local len = std.operator.len +local pairs = std.pairs -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" - -local Object = require "std.object".prototype - -local insert = std.table.insert -local ipairs = std.ipairs -local last = std.base.last -local len = std.operator.len -local pairs = std.pairs +setfenv (1, _ENV) @@ -116,7 +99,7 @@ local function normalise (self, arglist) insert (normal, opt) end - elseif opt:sub (1, 1) == "-" and string.len (opt) > 2 then + elseif opt:sub (1, 1) == "-" and string_len (opt) > 2 then local orig, split, rest = opt, {} repeat opt, rest = opt:sub (1, 2), opt:sub (3) @@ -131,7 +114,7 @@ local function normalise (self, arglist) -- Split '-xyz' into '-x -yz', and reiterate for '-yz' elseif self[opt].handler ~= optional and self[opt].handler ~= required then - if string.len (rest) > 0 then + if string_len (rest) > 0 then opt = "-" .. rest else opt = nil @@ -333,7 +316,7 @@ end -- parser:on ("-?", parser.version) local function help (self) print (self.helptext) - os.exit (0) + os_exit (0) end @@ -347,7 +330,7 @@ end -- parser:on ("-V", parser.version) local function version (self) print (self.versiontext) - os.exit (0) + os_exit (0) end @@ -410,7 +393,7 @@ end -- @usage -- parser:on ("--config-file", parser.required, parser.file) local function file (self, opt, optarg) - local h, errmsg = io.open (optarg, "r") + local h, errmsg = io_open (optarg, "r") if h == nil then return self:opterr (optarg .. ": " .. errmsg) end @@ -435,9 +418,9 @@ local function opterr (self, msg) local prog = self.program -- Ensure final period. if msg:match ("%.$") == nil then msg = msg .. "." end - io.stderr:write (prog .. ": error: " .. msg .. "\n") - io.stderr:write (prog .. ": Try '" .. prog .. " --help' for help.\n") - os.exit (2) + io_stderr:write (prog .. ": error: " .. msg .. "\n") + io_stderr:write (prog .. ": Try '" .. prog .. " --help' for help.\n") + os_exit (2) end @@ -480,7 +463,7 @@ local function on (self, opts, handler, value) optspec:gsub ("(%S+)", function (opt) -- 'x' => '-x' - if string.len (opt) == 1 then + if string_len (opt) == 1 then opt = "-" .. opt -- 'option-name' => '--option-name' @@ -490,7 +473,7 @@ local function on (self, opts, handler, value) if opt:match ("^%-[^%-]+") ~= nil then -- '-xyz' => '-x -y -z' - for i = 2, string.len (opt) do + for i = 2, string_len (opt) do insert (normal, "-" .. opt:sub (i, i)) end else diff --git a/lib/std/package.lua b/lib/std/package.lua index 217c557..d3c25e7 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -36,49 +36,35 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- +local _ENV = _G +local package = package +local setfenv = setfenv or function () end - -local package = package -local require = require -local setfenv = setfenv - -local string = { - match = string.match, -} - -local table = { - concat = table.concat, - insert = table.insert, - remove = table.remove, -} +local package_config = package.config +local string_match = string.match +local table_concat = table.concat +local table_insert = table.insert +local table_remove = table.remove +local std = require "std.base" ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +local argscheck = require "std.debug".argscheck +local catfile = std.io.catfile +local escape_pattern = std.string.escape_pattern +local invert = std.table.invert +local ipairs = std.ipairs +local merge = std.base.merge +local pairs = std.pairs +local split = std.string.split +local unpack = std.table.unpack + +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" - -local catfile = std.io.catfile -local escape_pattern = std.string.escape_pattern -local invert = std.table.invert -local ipairs = std.ipairs -local pairs = std.pairs -local split = std.string.split -local unpack = std.table.unpack +setfenv (1, _ENV) @@ -87,10 +73,6 @@ local unpack = std.table.unpack --[[ =============== ]]-- -local M - - - --- Make named constants for `package.config` -- (undocumented in 5.1; see luaconf.h for C equivalents). -- @table package @@ -100,7 +82,7 @@ local M -- @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 dirsep, pathsep, path_mark, execdir, igmark = - string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") + string_match (package_config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") local function pathsub (path) @@ -128,7 +110,7 @@ end local function normalize (...) - local i, paths, pathstrings = 1, {}, table.concat ({...}, pathsep) + local i, paths, pathstrings = 1, {}, table_concat ({...}, pathsep) for _, path in ipairs (split (pathstrings, pathsep)) do path = pathsub (path): gsub (catfile ("^[^", "]"), catfile (".", "%0")): @@ -166,13 +148,13 @@ local function normalize (...) paths[path], i = i, i + 1 end end - return table.concat (invert (paths), pathsep) + return table_concat (invert (paths), pathsep) end local function insert (pathstrings, ...) local paths = split (pathstrings, pathsep) - table.insert (paths, ...) + table_insert (paths, ...) return normalize (unpack (paths)) end @@ -187,8 +169,8 @@ end local function remove (pathstrings, pos) local paths = split (pathstrings, pathsep) - table.remove (paths, pos) - return table.concat (paths, pathsep) + table_remove (paths, pos) + return table_concat (paths, pathsep) end @@ -199,11 +181,11 @@ end local function X (decl, fn) - return require "std.debug".argscheck ("std.package." .. decl, fn) + return argscheck ("std.package." .. decl, fn) end -M = { +local M = { --- Look for a path segment match of *patt* in *pathstrings*. -- @function find -- @string pathstrings `pathsep` delimited path elements @@ -268,11 +250,7 @@ M.path_mark = path_mark M.pathsep = pathsep -for k, v in pairs (package) do - M[k] = M[k] or v -end - -return M +return merge (M, package) --- Types diff --git a/lib/std/set.lua b/lib/std/set.lua index 1be002a..38e047e 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -22,47 +22,36 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - +local _ENV = _G local getmetatable = getmetatable -local next = next -local rawget = rawget -local rawset = rawset -local require = require -local setfenv = setfenv +local next = next +local rawget = rawget +local rawset = rawset +local setfenv = setfenv or function () end local setmetatable = setmetatable -local type = type - -local table = { - concat = table.concat, - sort = table.sort, -} +local type = type +local table_concat = table.concat +local table_sort = table.sort ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- +local std = require "std.base" +local Container = require "std.container".prototype +local Module = std.object.Module -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG +local argscheck = require "std.debug".argscheck +local pairs = std.pairs +local pickle = std.string.pickle +local tostring = std.tostring +local std_type = std.type -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" -local Container = require "std.container".prototype - -local pairs = std.pairs -local pickle = std.string.pickle -local tostring = std.tostring -local stdtype = std.type +setfenv (1, _ENV) @@ -174,7 +163,7 @@ end local function X (decl, fn) - return require "std.debug".argscheck ("std.set." .. decl, fn) + return argscheck ("std.set." .. decl, fn) end @@ -277,8 +266,8 @@ prototype = Container { for k in pairs (self) do keys[#keys + 1] = tostring (k) end - table.sort (keys) - return stdtype (self) .. " {" .. table.concat (keys, ", ") .. "}" + table_sort (keys) + return std_type (self) .. " {" .. table_concat (keys, ", ") .. "}" end, --- Return a loadable serialization of this object, where possible. @@ -290,26 +279,26 @@ prototype = Container { for k in pairs (self) do keys[#keys + 1] = pickle (k) end - table.sort (keys) + table_sort (keys) if type (mt._module) == "string" then -- object with _module set - return table.concat { + return table_concat { 'require "', mt._module, '".prototype {', - table.concat (keys, ","), + table_concat (keys, ","), "}", } end -- rely on caller preloading `local ObjName = require "module".prototype` - return table.concat { - mt._type, " {", table.concat (keys, ","), "}" + return table_concat { + mt._type, " {", table_concat (keys, ","), "}" } end, } -return std.object.Module { +return Module { prototype = prototype, --- Functions diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index be6df2a..2392205 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -27,41 +27,31 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- +local _ENV = _G +local setfenv = setfenv or function () end +local tostring = tostring +local type = type - -local require = require -local setfenv = setfenv -local tostring = tostring -local type = type - -local table = { - concat = table.concat, -} +local table_concat = table.concat +local std = require "std.base" +local debug = require "std.debug" ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- +local Module = std.object.Module +local Object = require "std.object".prototype +local DEPRECATED = debug.DEPRECATED +local argscheck = debug.argscheck +local ielems = std.ielems +local insert = std.table.insert -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" -local debug = require "std.debug" - -local Object = require "std.object".prototype - -local ielems, insert = std.ielems, std.table.insert +setfenv (1, _ENV) @@ -78,7 +68,7 @@ end local function __tostring (self) local strs = {} for e in ielems (self) do strs[#strs + 1] = tostring (e) end - return table.concat (strs) + return table_concat (strs) end @@ -88,7 +78,7 @@ end local function X (decl, fn) - return debug.argscheck ("std.strbuf." .. decl, fn) + return argscheck ("std.strbuf." .. decl, fn) end --- StrBuf prototype object. @@ -127,8 +117,6 @@ local M = { --[[ ============= ]]-- -local DEPRECATED = debug.DEPRECATED - M.tostring = DEPRECATED ("41.1", "std.strbuf.tostring", "use 'tostring (strbuf)' instead", X ("tostring (StrBuf)", __tostring)) @@ -166,7 +154,7 @@ local prototype = Object { } -return std.object.Module { +return Module { prototype = prototype, tostring = M.tostring, diff --git a/lib/std/strict.lua b/lib/std/strict.lua index 0be4fce..2f832fb 100644 --- a/lib/std/strict.lua +++ b/lib/std/strict.lua @@ -20,13 +20,21 @@ @module std.strict ]] -local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget +local _ENV = { + debug_getinfo = debug.getinfo, + error = error, + rawset = rawset, + rawget = rawget, + setfenv = setfenv or function () end, + setmetatable = setmetatable, +} +setfenv (1, _ENV) --- What kind of variable declaration is this? -- @treturn string "C", "Lua" or "main" local function what () - local d = getinfo (3, "S") + local d = debug_getinfo (3, "S") return d and d.what or "C" end diff --git a/lib/std/string.lua b/lib/std/string.lua index fc5eaa2..bd1a4bb 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -11,65 +11,51 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - -local assert = assert +local _ENV = _G +local assert = assert local getmetatable = getmetatable -local require = require -local setfenv = setfenv -local string = string -local tonumber = tonumber -local tostring = tostring -local type = type - -local io = { - stderr = io.stderr, -} - -local math = { - abs = math.abs, - floor = math.floor, -} - -local table = { - concat = table.concat, -} - +local setfenv = setfenv or function () end +local string = string +local tonumber = tonumber +local tostring = tostring +local type = type +local io_stderr = io.stderr +local math_abs = math.abs +local math_floor = math.floor ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- +local std = require "std.base" +local debug = require "std.debug" -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG +local StrBuf = require "std.strbuf".prototype -if _DEBUG.strict then +local DEPRECATED = debug.DEPRECATED +local DEPRECATIONMSG = debug.DEPRECATIONMSG +local argscheck = debug.argscheck +local callable = std.functional.callable +local copy = std.base.copy +local escape_pattern = std.string.escape_pattern +local getmetamethod = std.getmetamethod +local insert = std.table.insert +local keysort = std.base.keysort +local len = std.operator.len +local merge = std.base.merge +local pairs = std.pairs +local pickle = std.string.pickle +local render = std.string.render +local sortkeys = std.base.sortkeys +local split = std.string.split +local toqstring = std.base.toqstring + +local _tostring = std.tostring + +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local std = require "std.base" -local debug = require "std.debug" - -local StrBuf = require "std.strbuf".prototype - -local callable = std.functional.callable -local copy = std.base.copy -local getmetamethod = std.getmetamethod -local insert = std.table.insert -local keysort = std.base.keysort -local len = std.operator.len -local pairs = std.pairs -local render = std.string.render -local sortkeys = std.base.sortkeys -local toqstring = std.base.toqstring - -local _tostring = std.tostring +setfenv (1, _ENV) @@ -150,7 +136,7 @@ end local function ordinal_suffix (n) - n = math.abs (n) % 100 + n = math_abs (n) % 100 local d = n % 10 if d == 1 and n ~= 11 then return "st" @@ -165,7 +151,7 @@ end local function pad (s, w, p) - p = string.rep (p or " ", math.abs (w)) + p = string.rep (p or " ", math_abs (w)) if w < 0 then return string.sub (p .. s, w) end @@ -212,7 +198,7 @@ local function numbertosi (n) local t = _format ("% #.2e", n) local _, _, m, e = t:find(".(.%...)e(.+)") local man, exp = tonumber (m), tonumber (e) - local siexp = math.floor (exp / 3) + local siexp = math_floor (exp / 3) local shift = exp - siexp * 3 local s = SIprefix[siexp] or "e" .. tostring (siexp) man = man * (10 ^ shift) @@ -288,11 +274,8 @@ end --[[ ================= ]]-- -local DEPRECATIONMSG = require "std.debug".DEPRECATIONMSG - - local function X (decl, fn) - return debug.argscheck ("std.string." .. decl, fn) + return argscheck ("std.string." .. decl, fn) end M = { @@ -343,7 +326,7 @@ M = { -- @string s any string -- @treturn string *s* with active pattern characters escaped -- @usage substr = inputstr:match (escape_pattern (literal)) - escape_pattern = X ("escape_pattern (string)", std.string.escape_pattern), + 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 @@ -426,7 +409,7 @@ M = { -- @usage -- freeze = std.functional.memoize (pickle) -- thaw = function (x) return std.eval (x) end - pickle = X ("pickle (?any)", std.string.pickle), + pickle = X ("pickle (?any)", pickle), --- Pretty-print a table, or other object. -- @function prettytostring @@ -456,7 +439,7 @@ M = { render = X ("render (?any, ?table|func, ?func, ?func, ?func, ?func, ?table)", function (x, opencb, closecb, elemcb, paircb, sepcb, roots) if type (opencb) == "function" then - io.stderr:write (DEPRECATIONMSG ("41.3", + io_stderr:write (DEPRECATIONMSG ("41.3", "multiple function arguments to 'std.string.render'", "pass a table of named functions as the second parameter instead", 2)) opencb = { @@ -487,7 +470,7 @@ M = { -- @string[opt="%s+"] sep separator pattern -- @return list of strings -- @usage words = split "a very short sentence" - split = X ("split (string, ?string)", std.string.split), + split = X ("split (string, ?string)", split), --- Do `string.find`, returning a table of captures. -- @function tfind @@ -543,9 +526,6 @@ M = { --[[ ============= ]]-- -local DEPRECATED = debug.DEPRECATED - - M.assert = DEPRECATED ("41", "'std.string.assert'", "use 'std.assert' instead", std.assert) @@ -559,7 +539,7 @@ M.tostring = DEPRECATED ("41", "'std.string.tostring'", -return std.base.merge (M, string) +return merge (M, string) diff --git a/lib/std/table.lua b/lib/std/table.lua index ad405a0..7e14e1f 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,49 +11,44 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - +local _ENV = _G local getmetatable = getmetatable -local next = next -local require = require -local setfenv = setfenv +local next = next +local setfenv = setfenv or function () end local setmetatable = setmetatable -local table = table -local type = type - -local math = { - ceil = math.ceil, - min = math.min, -} - - - ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- - - -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +local table = table +local type = type + +local math_ceil = math.ceil +local math_min = math.min + + +local std = require "std.base" +local debug = require "std.debug" + +local DEPRECATED = debug.DEPRECATED +local argscheck = debug.argscheck +local argerror = std.debug.argerror +local collect = std.functional.collect +local copy = std.base.copy +local insert = std.table.insert +local invert = std.table.invert +local ipairs = std.ipairs +local leaves = std.tree.leaves +local len = std.operator.len +local maxn = std.table.maxn +local merge = std.base.merge +local pairs = std.pairs +local unpack = std.table.unpack + +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end +setfenv (1, _ENV) -local std = require "std.base" -local debug = require "std.debug" - -local argerror = debug.argerror -local ipairs = std.ipairs -local pairs = std.pairs -local collect = std.functional.collect -local len = std.operator.len -local leaves = std.tree.leaves - --[[ =============== ]]-- @@ -160,7 +155,7 @@ local function shape (dims, t) end end if zero then - dims[zero] = math.ceil (len (t) / size) + dims[zero] = math_ceil (len (t) / size) end local function fill (i, d) if d > len (dims) then @@ -199,7 +194,7 @@ end local function monkey_patch (namespace) namespace = namespace or _G - namespace.table = std.base.copy (namespace.table or {}, monkeys) + namespace.table = copy (namespace.table or {}, monkeys) return M end @@ -209,7 +204,7 @@ local _remove = table.remove local function remove (t, pos) local lent = len (t) pos = pos or lent - if pos < math.min (1, lent) or pos > lent + 1 then -- +1? whu? that's what 5.2.3 does!?! + if pos < math_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) @@ -232,7 +227,7 @@ end local function X (decl, fn) - return debug.argscheck ("std.table." .. decl, fn) + return argscheck ("std.table." .. decl, fn) end M = { @@ -251,7 +246,7 @@ M = { -- @usage -- --> {1, "x", 2, 3, "y"} -- insert (insert ({1, 2, 3}, 2, "x"), "y") - insert = X ("insert (table, [int], any)", std.table.insert), + insert = X ("insert (table, [int], any)", insert), --- Largest integer key in a table. -- @function maxn @@ -260,7 +255,7 @@ M = { -- @usage -- --> 42 -- maxn {"a", b="c", 99, [42]="x", "x", [5]=67} - maxn = X ("maxn (table)", std.table.maxn), + maxn = X ("maxn (table)", maxn), --- Enhance core *table.remove* to respect `__len` when *pos* is omitted. -- Also, diagnose out of bounds *pos* arguments consistently on any supported @@ -290,7 +285,7 @@ M = { -- @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)", std.table.unpack), + unpack = X ("unpack (table, ?int, ?int)", unpack), --- Accessor Functions @@ -448,7 +443,7 @@ M = { -- @usage -- --> {a=1, b=2, c=3} -- invert {"a", "b", "c"} - invert = X ("invert (table)", std.table.invert), + invert = X ("invert (table)", invert), --- Make the list of keys in table. -- @function keys @@ -499,7 +494,7 @@ M = { } -monkeys = std.base.copy ({}, M) -- before deprecations and core merge +monkeys = copy ({}, M) -- before deprecations and core merge --[[ ============= ]]-- @@ -507,8 +502,6 @@ monkeys = std.base.copy ({}, M) -- before deprecations and core merge --[[ ============= ]]-- -local DEPRECATED = debug.DEPRECATED - M.len = DEPRECATED ("41.3", "'std.table.len'", "use 'std.operator.len' instead", X ("len (table)", std.operator.len)) @@ -550,7 +543,7 @@ M.totable = DEPRECATED ("41", "'std.table.totable'", -return std.base.merge (M, table) +return merge (M, table) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index f10ecd0..f6f4281 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -24,55 +24,41 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - +local _ENV = _G local getmetatable = getmetatable -local rawget = rawget -local rawset = rawset -local require = require -local setfenv = setfenv +local rawget = rawget +local rawset = rawset +local setfenv = setfenv or function () end local setmetatable = setmetatable -local type = type +local type = type -local coroutine = { - yield = coroutine.yield, - wrap = coroutine.wrap, -} - -local table = { - remove = table.remove, -} +local coroutine_yield = coroutine.yield +local coroutine_wrap = coroutine.wrap +local table_remove = table.remove +local std = require "std.base" +local operator = require "std.operator" ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- +local Container = require "std.container".prototype +local Module = std.object.Module +local argscheck = require "std.debug".argscheck +local ielems = std.ielems +local ipairs = std.ipairs +local last = std.base.last +local leaves = std.tree.leaves +local len = std.operator.len +local pairs = std.pairs +local reduce = std.functional.reduce +local std_type = std.type -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG - -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - -local std = require "std.base" -local operator = require "std.operator" - -local Container = require "std.container".prototype - -local ielems = std.ielems -local ipairs = std.ipairs -local last = std.base.last -local len = std.operator.len -local leaves = std.tree.leaves -local pairs = std.pairs -local reduce = std.functional.reduce -local stdtype = std.type +setfenv (1, _ENV) @@ -95,18 +81,18 @@ local function _nodes (it, tr) local p = {} local function visit (n) if type (n) == "table" then - coroutine.yield ("branch", p, n) + coroutine_yield ("branch", p, n) for i, v in it (n) do p[#p + 1] = i visit (v) - table.remove (p) + table_remove (p) end - coroutine.yield ("join", p, n) + coroutine_yield ("join", p, n) else - coroutine.yield ("leaf", p, n) + coroutine_yield ("leaf", p, n) end end - return coroutine.wrap (visit), tr + return coroutine_wrap (visit), tr end @@ -155,7 +141,7 @@ end local function X (decl, fn) - return require "std.debug".argscheck ("std.tree." .. decl, fn) + return argscheck ("std.tree." .. decl, fn) end @@ -192,7 +178,7 @@ prototype = Container { -- @usage -- del_other_window = keymap[{"C-x", "4", KEY_DELETE}] __index = function (tr, i) - if stdtype (i) == "table" then + if std_type (i) == "table" then return reduce (operator.get, tr, ielems, i) else return rawget (tr, i) @@ -206,9 +192,9 @@ prototype = Container { -- @usage -- function bindkey (keylist, fn) keymap[keylist] = fn end __newindex = function (tr, i, v) - if stdtype (i) == "table" then + if std_type (i) == "table" then for n = 1, len (i) - 1 do - if stdtype (tr[i[n]]) ~= "Tree" then + if std_type (tr[i[n]]) ~= "Tree" then rawset (tr, i[n], prototype {}) end tr = tr[i[n]] @@ -221,7 +207,7 @@ prototype = Container { } -return std.object.Module { +return Module { prototype = prototype, --- Functions diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index d7035c8..c9d18c3 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -26,50 +26,36 @@ ]] ---[[ ============================== ]]-- ---[[ Cache all external references. ]]-- ---[[ ============================== ]]-- - - -local error = error +local _ENV = _ENV +local error = error local getmetatable = getmetatable -local next = next -local require = require -local select = select -local setfenv = setfenv +local next = next +local select = select +local setfenv = setfenv or function () end local setmetatable = setmetatable -local type = type - -local string = { - format = string.format, -} +local type = type -local table = { - concat = table.concat, - unpack = table.unpack or unpack, -} +local string_format = string.format +local table_concat = table.concat +local table_unpack = table.unpack or unpack +local std = require "std.base" ---[[ ====================================== ]]-- ---[[ Empty environment, with strict access. ]]-- ---[[ ====================================== ]]-- +local Container = require "std.container".prototype +local Module = std.object.Module +local pickle = std.string.pickle +local std_type = std.type +local toqstring = std.base.toqstring -local _ENV, _DEBUG = _G, require "std.debug_init"._DEBUG -if _DEBUG.strict then +if require "std.debug_init"._DEBUG.strict then _ENV = require "std.strict" {} - if setfenv then setfenv (1, _ENV) end +else + _ENV = {} end - - -local Container = require "std.container".prototype -local std = require "std.base" - -local pickle = std.string.pickle -local stdtype = std.type -local toqstring = std.base.toqstring +setfenv (1, _ENV) @@ -123,7 +109,7 @@ local prototype = Container { local n = select ("#", ...) local s, t = {}, {n = n, ...} for i = 1, n do s[i] = toqstring (t[i]) end - return intern (table.concat (s, ", "), t) + return intern (table_concat (s, ", "), t) end, --- Metamethods @@ -162,7 +148,7 @@ local prototype = Container { -- print (Tuple ("nil", nil, false)) __tostring = function (self) local _, argstr = next (self) - return string.format ("%s (%s)", stdtype (self), argstr) + return string_format ("%s (%s)", std_type (self), argstr) end, --- Unpack tuple values between index *i* and *j*, inclusive. @@ -175,7 +161,7 @@ local prototype = Container { -- --> 3, 2, 5 -- table.unpack (t, 2) __unpack = function (self, i, j) - return table.unpack (next (self), i, j) + return table_unpack (next (self), i, j) end, --- Return a loadable serialization of this object, where possible. @@ -188,10 +174,10 @@ local prototype = Container { vals[i] = pickle (self[i]) end if type (mt._module) == "string" then - return string.format ('require "%s".prototype (%s)', - mt._module, table.concat (vals, ",")) + return string_format ('require "%s".prototype (%s)', + mt._module, table_concat (vals, ",")) end - return string.format ("%s (%s)", mt._type, table.concat (vals, ",")) + return string_format ("%s (%s)", mt._type, table_concat (vals, ",")) end, -- Prototype is the 0-tuple. @@ -199,6 +185,6 @@ local prototype = Container { } -return std.object.Module { +return Module { prototype = prototype, } From aa278bdff11c705f7e2ecc33abfae7ed9f8cceb8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 24 Sep 2015 22:06:00 +0100 Subject: [PATCH 603/703] refactor: fix symbol leak and error mismatch on 5.1. * lib/std/container.lua (_ENV): Local variable, otherwise global _ENV is used, which is not special in Lua 5.1 and thus leaks into the global environment after `require "std.container"`. * specs/io_spec.yaml (std.io): Adjust 5.1 error message to match flattened io_input variable name. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 1 + specs/io_spec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index fab8be6..e7b428e 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -29,6 +29,7 @@ ]] +local _ENV = _G local getmetatable = getmetatable local next = next local select = select diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index d35b5db..15acb17 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -202,8 +202,8 @@ specify std.io: 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 + "cannot open file 'not-an-existing-file'", -- Lua 5.2 + "bad argument #1 to 'io_input' (not-an-existing-file:", -- Lua 5.1 } end } From edb7373f3b27e82d1ffe9b51170a6e1fc45ba7a5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 24 Sep 2015 22:34:27 +0100 Subject: [PATCH 604/703] refactor: hoist module _ENV setting into std.base function. * lib/std/base.lua (setenvtable): New function encapsulating return of a table suitable for assigning to `_ENV` and Lua 5.1 equivalence by calling setfenv if available. * lib/std.lua.in, lib/std/base.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/object.lua, lib/std/operator.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua, lib/std/tuple.lua (_ENV): use it. (setfenv): Remove. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 9 +-------- lib/std/base.lua | 16 +++++++++------- lib/std/container.lua | 29 +++++++++++------------------ lib/std/debug.lua | 17 +++++------------ lib/std/functional.lua | 9 +-------- lib/std/io.lua | 10 +--------- lib/std/list.lua | 11 +---------- lib/std/math.lua | 9 +-------- lib/std/object.lua | 12 +----------- lib/std/operator.lua | 9 +-------- lib/std/optparse.lua | 9 +-------- lib/std/package.lua | 9 +-------- lib/std/set.lua | 9 +-------- lib/std/strbuf.lua | 9 +-------- lib/std/string.lua | 14 +++----------- lib/std/table.lua | 9 +-------- lib/std/tree.lua | 9 +-------- lib/std/tuple.lua | 10 +--------- 18 files changed, 42 insertions(+), 167 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index dd89e32..240d513 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -20,14 +20,12 @@ ]] -local _ENV = _G local _G = _G local ipairs = ipairs local pairs = pairs local pcall = pcall local rawset = rawset local require = require -local setfenv = setfenv or function () end local setmetatable = setmetatable local type = type @@ -35,12 +33,7 @@ local type = type local argscheck = require "std.debug".argscheck local std = require "std.base" -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/base.lua b/lib/std/base.lua index 3670d06..2ad49b4 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,7 +23,6 @@ ]] -local _ENV = _G local dirsep = string.match (package.config, "^(%S+)\n") local error = error local getmetatable = getmetatable @@ -52,13 +51,15 @@ local table_maxn = table.maxn local table_sort = table.sort local table_unpack = table.unpack or unpack - -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} +local function setenvtable (env) + if require "std.debug_init"._DEBUG.strict then + env = require "std.strict" (env) + end + setfenv (2, env) + return env end -setfenv (1, _ENV) + +local _ENV = setenvtable {} @@ -681,6 +682,7 @@ return { merge = merge, mnemonic = mnemonic, raise = raise, + setenvtable = setenvtable, sortkeys = sortkeys, toqstring = toqstring, }, diff --git a/lib/std/container.lua b/lib/std/container.lua index e7b428e..52a558b 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -29,11 +29,9 @@ ]] -local _ENV = _G local getmetatable = getmetatable local next = next local select = select -local setfenv = setfenv or function () end local setmetatable = setmetatable local type = type @@ -42,25 +40,20 @@ local string_sub = string.sub local table_concat = table.concat -local _DEBUG = require "std.debug_init"._DEBUG -local std = require "std.base" -local debug = require "std.debug" +local _DEBUG = require "std.debug_init"._DEBUG +local std = require "std.base" +local debug = require "std.debug" -local Module = std.object.Module +local Module = std.object.Module -local copy = std.base.copy -local ipairs = std.ipairs -local mapfields = std.object.mapfields -local pickle = std.string.pickle -local render = std.string.render -local tostring = std.tostring +local copy = std.base.copy +local ipairs = std.ipairs +local mapfields = std.object.mapfields +local pickle = std.string.pickle +local render = std.string.render +local tostring = std.tostring -if _DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 2afff6c..33a6ba8 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -29,17 +29,16 @@ ]] -local _ENV = _G local debug = debug local error = error local getfenv = getfenv local getmetatable = getmetatable local next = next local pcall = pcall -local setfenv = setfenv or function () end local setmetatable = setmetatable local type = type +local debug_setfenv = debug.setfenv local io_stderr = io.stderr local io_type = io.type local math_floor = math.floor @@ -90,12 +89,8 @@ local unpack = std.table.unpack -- @usage _DEBUG = { argcheck = false, level = 9, strict = false } local _DEBUG = require "std.debug_init"._DEBUG -if _DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) + +local _ENV = std.base.setenvtable {} @@ -136,16 +131,14 @@ local function DEPRECATED (version, name, extramsg, fn) end -local _setfenv = debug.setfenv - local function setfenv (fn, env) -- Unwrap functable: if type (fn) == "table" then fn = fn.call or (getmetatable (fn) or {}).__call end - if _setfenv then - return _setfenv (fn, env) + if debug_setfenv then + return debug_setfenv (fn, env) else -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html diff --git a/lib/std/functional.lua b/lib/std/functional.lua index f1f92fc..49d616e 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,12 +8,10 @@ ]] -local _ENV = _G local loadstring = loadstring or load local next = next local pcall = pcall local select = select -local setfenv = setfenv or function () end local setmetatable = setmetatable local table_remove = table.remove @@ -40,12 +38,7 @@ local reduce = std.functional.reduce local render = std.string.render local unpack = std.table.unpack -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/io.lua b/lib/std/io.lua index 10c710d..9a56ef3 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,14 +11,12 @@ ]] -local _ENV = _ENV local _G = _G local arg = arg local error = error local getmetatable = getmetatable local io = io local rawget = rawget -local setfenv = setfenv or function () end local setmetatable = debug.setmetatable local type = type @@ -50,13 +48,7 @@ local pairs = std.pairs local split = std.string.split local tostring = std.tostring -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) - +local _ENV = std.base.setenvtable {} diff --git a/lib/std/list.lua b/lib/std/list.lua index fe33ddf..4569599 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -17,10 +17,6 @@ ]] -local _ENV = _G -local require = require -local setfenv = setfenv or function () end - local math_ceil = math.ceil local math_max = math.max @@ -39,12 +35,7 @@ local len = std.operator.len local pairs = std.pairs local unpack = std.table.unpack -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/math.lua b/lib/std/math.lua index 3143b03..94de65f 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,9 +11,7 @@ ]] -local _ENV = _G local math = math -local setfenv = setfenv or function () end local math_floor = math.floor @@ -23,12 +21,7 @@ local argscheck = require "std.debug".argscheck local copy = std.base.copy local merge = std.base.merge -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/object.lua b/lib/std/object.lua index e724a4d..76d8fc4 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -22,10 +22,6 @@ ]] -local _ENV = _G -local setfenv = setfenv or function () end - - local debug = require "std.debug" local std = require "std.base" @@ -38,13 +34,7 @@ local getmetamethod = std.getmetamethod local mapfields = std.object.mapfields local type = std.type - -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/operator.lua b/lib/std/operator.lua index cbd84d9..6b54322 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -5,8 +5,6 @@ ]] -local _ENV = _G -local setfenv = setfenv or function () end local type = type @@ -16,12 +14,7 @@ local len = std.operator.len local serialize = std.base.mnemonic local tostring = std.tostring -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 59f85d0..0f685b9 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -22,12 +22,10 @@ ]=] -local _ENV = _G local assert = assert local error = error local print = print local require = require -local setfenv = setfenv or function () end local setmetatable = setmetatable local tostring = tostring local type = type @@ -48,12 +46,7 @@ local last = std.base.last local len = std.operator.len local pairs = std.pairs -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/package.lua b/lib/std/package.lua index d3c25e7..7e74523 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -36,9 +36,7 @@ ]] -local _ENV = _G local package = package -local setfenv = setfenv or function () end local package_config = package.config local string_match = string.match @@ -59,12 +57,7 @@ local pairs = std.pairs local split = std.string.split local unpack = std.table.unpack -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/set.lua b/lib/std/set.lua index 38e047e..b31c662 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -22,12 +22,10 @@ ]] -local _ENV = _G local getmetatable = getmetatable local next = next local rawget = rawget local rawset = rawset -local setfenv = setfenv or function () end local setmetatable = setmetatable local type = type @@ -46,12 +44,7 @@ local pickle = std.string.pickle local tostring = std.tostring local std_type = std.type -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 2392205..4485ec6 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -27,8 +27,6 @@ ]] -local _ENV = _G -local setfenv = setfenv or function () end local tostring = tostring local type = type @@ -46,12 +44,7 @@ local argscheck = debug.argscheck local ielems = std.ielems local insert = std.table.insert -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/string.lua b/lib/std/string.lua index bd1a4bb..9b8ec8b 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -11,10 +11,8 @@ ]] -local _ENV = _G local assert = assert local getmetatable = getmetatable -local setfenv = setfenv or function () end local string = string local tonumber = tonumber local tostring = tostring @@ -46,16 +44,10 @@ local pickle = std.string.pickle local render = std.string.render local sortkeys = std.base.sortkeys local split = std.string.split +local std_tostring = std.tostring local toqstring = std.base.toqstring -local _tostring = std.tostring - -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} @@ -68,7 +60,7 @@ local M local function __concat (s, o) - return _tostring (s) .. _tostring (o) + return std_tostring (s) .. std_tostring (o) end diff --git a/lib/std/table.lua b/lib/std/table.lua index 7e14e1f..99e45dc 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,10 +11,8 @@ ]] -local _ENV = _G local getmetatable = getmetatable local next = next -local setfenv = setfenv or function () end local setmetatable = setmetatable local table = table local type = type @@ -41,12 +39,7 @@ local merge = std.base.merge local pairs = std.pairs local unpack = std.table.unpack -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/tree.lua b/lib/std/tree.lua index f6f4281..53971cb 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -24,11 +24,9 @@ ]] -local _ENV = _G local getmetatable = getmetatable local rawget = rawget local rawset = rawset -local setfenv = setfenv or function () end local setmetatable = setmetatable local type = type @@ -53,12 +51,7 @@ local pairs = std.pairs local reduce = std.functional.reduce local std_type = std.type -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index c9d18c3..0d88508 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -26,12 +26,10 @@ ]] -local _ENV = _ENV local error = error local getmetatable = getmetatable local next = next local select = select -local setfenv = setfenv or function () end local setmetatable = setmetatable local type = type @@ -49,13 +47,7 @@ local pickle = std.string.pickle local std_type = std.type local toqstring = std.base.toqstring - -if require "std.debug_init"._DEBUG.strict then - _ENV = require "std.strict" {} -else - _ENV = {} -end -setfenv (1, _ENV) +local _ENV = std.base.setenvtable {} From 1701cf3cdf6c3e0f30c3ae1fded882403dda5710 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 26 Sep 2015 13:06:58 +0100 Subject: [PATCH 605/703] maturity: split code maturity functions out of std.debug. * lib/std/debug.lua (DEPRECATED, DEPRECATIONMSG): Move from here... * lib/std/maturity.lua (DEPRECATED, DEPRECATIONMSG): New module. ...to here. Adjust all callers. * specs/debug_spec.yaml (DEPRECATED, DEPRECATIONMSG): Move from here... * specs/maturity_spec.yaml (DEPRECATED, DEPRECATIONMSG): New module. ...to here. * specs/specs.mk (specl_SPECS): Add specs/maturity_spec.yaml. * local.mk (dist_luastd_DATA): Add maturity.lua. (dist_docmodules_DATA): Add modules/maturity.html. * build-aux/config.ld.in (file): Add lib/std/maturity.lua. * NEWS.md (New features, Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 9 ++++ build-aux/config.ld.in | 1 + lib/std/debug.lua | 61 +--------------------- lib/std/functional.lua | 2 +- lib/std/list.lua | 2 +- lib/std/maturity.lua | 91 +++++++++++++++++++++++++++++++++ lib/std/object.lua | 2 +- lib/std/strbuf.lua | 2 +- lib/std/string.lua | 5 +- lib/std/table.lua | 2 +- local.mk | 2 + specs/debug_spec.yaml | 108 ++------------------------------------- specs/maturity_spec.yaml | 105 +++++++++++++++++++++++++++++++++++++ specs/specs.mk | 1 + 14 files changed, 222 insertions(+), 171 deletions(-) create mode 100644 lib/std/maturity.lua create mode 100644 specs/maturity_spec.yaml diff --git a/NEWS.md b/NEWS.md index 75a0c21..c6647b7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -121,6 +121,9 @@ - Passing the result of `functional.lambda` to `tostring` returns the original lambda string. + - New `std.maturity` module now contains the `DEPRECATED` and + `DEPRECATIONMSG` functions previously found in `std.debug`. + ### Deprecations - We used to have an object module method, `std.object.type`, which @@ -170,6 +173,12 @@ ### Incompatible changes + - `std.debug.DEPRECATED` and `std.debug.DEPRECATIONMSG` have moved to + a new module `std.maturity`. Deprecation DEPRECATED with multi-level + deprecation warnings was more confusing than simply moving the + functions into their own module, so there is no deprecation warning + to prompt you to update call-sites. + - Deprecated multi-argument `functional.bind` has been removed. - Deprecated methods `list:depair`, `list:map_with`, `list:transpose` and diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 59ed91d..8dd5e76 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -66,6 +66,7 @@ file = { "../lib/std/operator.lua", -- Other Modules + "../lib/std/maturity.lua", "../lib/std/optparse.lua", "../lib/std/strict.lua", } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 33a6ba8..e215721 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -30,7 +30,6 @@ local debug = debug -local error = error local getfenv = getfenv local getmetatable = getmetatable local next = next @@ -44,7 +43,6 @@ local io_type = io.type local math_floor = math.floor local math_huge = math.huge local math_max = math.max -local string_format = string.format local table_concat = table.concat local table_remove = table.remove local table_sort = table.sort @@ -52,6 +50,7 @@ local table_sort = table.sort local std = require "std.base" +local DEPRECATED = require "std.maturity".DEPRECATED local argerror = std.debug.argerror local copy = std.base.copy local insert = std.table.insert @@ -102,35 +101,6 @@ local _ENV = std.base.setenvtable {} local M --- Return a deprecation message if _DEBUG.deprecate is `nil`, otherwise "". -local function DEPRECATIONMSG (version, name, extramsg, level) - if level == nil then level, extramsg = extramsg, nil end - extramsg = extramsg or "and will be removed entirely in a future release" - - local _, where = pcall (function () error ("", level + 3) end) - if _DEBUG.deprecate == nil then - return (where .. string_format ("%s was deprecated in release %s, %s.\n", - name, tostring (version), extramsg)) - end - - return "" -end - - --- Define deprecated functions when _DEBUG.deprecate is not "truthy", --- and write `DEPRECATIONMSG` output to stderr. -local function DEPRECATED (version, name, extramsg, fn) - if fn == nil then fn, extramsg = extramsg, nil end - - if not _DEBUG.deprecate then - return function (...) - io_stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) - return fn (...) - end - end -end - - local function setfenv (fn, env) -- Unwrap functable: if type (fn) == "table" then @@ -674,35 +644,6 @@ end M = { - --- API Maturity - -- @section maturity - - --- Provide a deprecated function definition according to _DEBUG.deprecate. - -- You can check whether your covered code uses deprecated functions by - -- setting `_DEBUG.deprecate` to `true` before loading any stdlib modules, - -- or silence deprecation warnings by setting `_DEBUG.deprecate = false`. - -- @function DEPRECATED - -- @string version first deprecation release version - -- @string name function name for automatic warning message - -- @string[opt] extramsg additional warning text - -- @func fn deprecated function - -- @return a function to show the warning on first call, and hand off to *fn* - -- @usage - -- M.op = DEPRECATED ("41", "'std.functional.op'", std.operator) - DEPRECATED = DEPRECATED, - - --- Format a deprecation warning message. - -- @function DEPRECATIONMSG - -- @string version first deprecation release version - -- @string name function name for automatic warning message - -- @string[opt] extramsg additional warning text - -- @int level call stack level to blame for the error - -- @treturn string deprecation warning message, or empty string - -- @usage - -- io.stderr:write (DEPRECATIONMSG ("42", "multi-argument 'module.fname'", 2)) - DEPRECATIONMSG = DEPRECATIONMSG, - - --- Gradual Typing -- @section typing diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 49d616e..b821f1f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -21,7 +21,7 @@ local std = require "std.base" local debug = require "std.debug" local operator = require "std.operator" -local DEPRECATED = debug.DEPRECATED +local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local callable = std.functional.callable local collect = std.functional.collect diff --git a/lib/std/list.lua b/lib/std/list.lua index 4569599..89b00c7 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -27,7 +27,7 @@ local std = require "std.base" local Module = std.object.Module local Object = require "std.object".prototype -local DEPRECATED = debug.DEPRECATED +local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local compare = std.list.compare local ipairs = std.ipairs diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua new file mode 100644 index 0000000..50a4799 --- /dev/null +++ b/lib/std/maturity.lua @@ -0,0 +1,91 @@ +--[[-- + API Maturity. + + Rather than suddenly changing or removing APIs between releases of a + library use these functions to support deprecated calls for a time + first, while issuing warnings to the caller. + + The verbosity of APIs deprecated with these functions is controlled by + the global `_DEBUG` variable, which must be set before any `stdlib` + modules are loaded. This declaration will disable deprecation, so + that deprecated APIs will behave normally: + + _DEBUG = { deprecate = false } + + Alternatively, without affecting the global environment, the following + style causes deprecated APIs to be undefined so that you can easily + check whether your code is still using deprecated calls: + + local init = require "std.debug_init" + init._DEBUG.deprecate = true + + Not setting `_DEBUG.deprecate` will warn on every call to deprecated + APIs. + + @module std.maturity +]] + +local error = error +local pcall = pcall +local tostring = tostring + +local io_stderr = io.stderr +local string_format = string.format + +local _DEBUG = require "std.debug_init"._DEBUG + +local _ENV = require "std.base".base.setenvtable {} + + +local function DEPRECATIONMSG (version, name, extramsg, level) + if level == nil then level, extramsg = extramsg, nil end + extramsg = extramsg or "and will be removed entirely in a future release" + + local _, where = pcall (function () error ("", level + 3) end) + if _DEBUG.deprecate == nil then + return (where .. string_format ("%s was deprecated in release %s, %s.\n", + name, tostring (version), extramsg)) + end + + return "" +end + + +local function DEPRECATED (version, name, extramsg, fn) + if fn == nil then fn, extramsg = extramsg, nil end + + if not _DEBUG.deprecate then + return function (...) + io_stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) + return fn (...) + end + end +end + + +return { + --- Provide a deprecated function definition according to _DEBUG.deprecate. + -- You can check whether your covered code uses deprecated functions by + -- setting `_DEBUG.deprecate` to `true` before loading any stdlib modules, + -- or silence deprecation warnings by setting `_DEBUG.deprecate = false`. + -- @function DEPRECATED + -- @string version first deprecation release version + -- @string name function name for automatic warning message + -- @string[opt] extramsg additional warning text + -- @func fn deprecated function + -- @return a function to show the warning on first call, and hand off to *fn* + -- @usage + -- M.op = DEPRECATED ("41", "'std.functional.op'", std.operator) + DEPRECATED = DEPRECATED, + + --- Format a deprecation warning message. + -- @function DEPRECATIONMSG + -- @string version first deprecation release version + -- @string name function name for automatic warning message + -- @string[opt] extramsg additional warning text + -- @int level call stack level to blame for the error + -- @treturn string deprecation warning message, or empty string + -- @usage + -- io.stderr:write (DEPRECATIONMSG ("42", "multi-argument 'module.fname'", 2)) + DEPRECATIONMSG = DEPRECATIONMSG, +} diff --git a/lib/std/object.lua b/lib/std/object.lua index 76d8fc4..71e4414 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -28,7 +28,7 @@ local std = require "std.base" local Container = require "std.container".prototype local Module = std.object.Module -local DEPRECATED = debug.DEPRECATED +local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local getmetamethod = std.getmetamethod local mapfields = std.object.mapfields diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 4485ec6..f711588 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -39,7 +39,7 @@ local debug = require "std.debug" local Module = std.object.Module local Object = require "std.object".prototype -local DEPRECATED = debug.DEPRECATED +local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local ielems = std.ielems local insert = std.table.insert diff --git a/lib/std/string.lua b/lib/std/string.lua index 9b8ec8b..36ea649 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -25,11 +25,12 @@ local math_floor = math.floor local std = require "std.base" local debug = require "std.debug" +local maturity = require "std.maturity" local StrBuf = require "std.strbuf".prototype -local DEPRECATED = debug.DEPRECATED -local DEPRECATIONMSG = debug.DEPRECATIONMSG +local DEPRECATED = maturity.DEPRECATED +local DEPRECATIONMSG = maturity.DEPRECATIONMSG local argscheck = debug.argscheck local callable = std.functional.callable local copy = std.base.copy diff --git a/lib/std/table.lua b/lib/std/table.lua index 99e45dc..78f8630 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -24,7 +24,7 @@ local math_min = math.min local std = require "std.base" local debug = require "std.debug" -local DEPRECATED = debug.DEPRECATED +local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local argerror = std.debug.argerror local collect = std.functional.collect diff --git a/local.mk b/local.mk index 33361fd..b00d490 100644 --- a/local.mk +++ b/local.mk @@ -75,6 +75,7 @@ dist_luastd_DATA = \ lib/std/io.lua \ lib/std/list.lua \ lib/std/math.lua \ + lib/std/maturity.lua \ lib/std/object.lua \ lib/std/operator.lua \ lib/std/optparse.lua \ @@ -156,6 +157,7 @@ dist_docfunctional_DATA += \ $(NOTHING_ELSE) dist_docmodules_DATA += \ + $(docmodules).maturity.html \ $(docmodules).optparse.html \ $(docmodules).strict.html \ $(NOTHING_ELSE) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 7976766..891278e 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -3,10 +3,10 @@ before: | this_module = "std.debug" global_table = "_G" - extend_base = { "DEPRECATED", "DEPRECATIONMSG", "argcheck", "argerror", - "argscheck", "extramsg_mismatch", "extramsg_toomany", - "getfenv", "parsetypes", "resulterror", "setfenv", "say", - "toomanyargmsg", "typesplit", "trace", "_setdebug" } + extend_base = { "argcheck", "argerror", "argscheck", "extramsg_mismatch", + "extramsg_toomany", "getfenv", "parsetypes", "resulterror", + "setfenv", "say", "toomanyargmsg", "typesplit", "trace", + "_setdebug" } M = require (this_module) @@ -32,106 +32,6 @@ specify std.debug: to_equal {} -- describe DEPRECATED: - - before: | - function runscript (body, name, args) - return luaproc ( - "require '" .. this_module .. "'.DEPRECATED ('0', '" .. - (name or "runscript") .. "', function (...)" .. - " " .. body .. - " end) " .. - "('" .. table.concat (args or {}, "', '") .. "')" - ) - end - - f, badarg = init (M, this_module, "DEPRECATED") - - - it returns a function: - expect (type (f ("0", "deprecated", nop))).to_be "function" - expect (f ("0", "deprecated", nop)).not_to_be (nop) - - context with deprecated function: - - it executes the deprecated function: - expect (runscript 'error "oh noes!"').to_contain_error "oh noes!" - - it passes arguments to the deprecated function: - expect (runscript ("print (table.concat ({...}, ', '))", nil, - {"foo", "bar", "baz"})).to_output "foo, bar, baz\n" - - it returns deprecated function results: | - script = [[ - DEPRECATED = require "std.debug".DEPRECATED - fn = DEPRECATED ("0", "fn", function () return "foo", "bar", "baz" end) - print (fn ()) - ]] - expect (luaproc (script)).to_output "foo\tbar\tbaz\n" - - it writes a warning to stderr: - expect (runscript 'error "oh noes!"'). - to_match_error "deprecated.*, and will be removed" - - it writes the version string to stderr: - expect (runscript 'error "oh noes!"'). - to_contain_error "in release 0" - - it writes the call location to stderr: | - expect (runscript 'error "oh noes!"'). - to_match_error "^%S+:1: " - - context with _DEBUG: - - before: | - script = [[ - DEPRECATED = require "]] .. this_module .. [[".DEPRECATED - fn = DEPRECATED ("0", "fn", function () io.stderr:write "oh noes!\n" end) - fn () -- line 3 - fn () -- line 4 - ]] - - it warns every call by default: - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" - - it does not warn at all with _DEBUG set to false: - script = "_DEBUG = false " .. script - expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - - it does not define the function with _DEBUG set to true: | - script = "_DEBUG = true " .. script - expect (luaproc (script)).to_contain_error.any_of { - ":3: attempt to call global 'fn'", - ":3: attempt to call a nil value (global 'fn')", - } - - it warns on every call with _DEBUG.deprecate unset: - script = "_DEBUG = {} " .. script - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" - - it does not warn at all with _DEBUG.deprecate set to false: - script = "_DEBUG = { deprecate = false } " .. script - expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - - it warns on every call with _DEBUG.deprecate set to true: | - script = "_DEBUG = { deprecate = true } " .. script - expect (luaproc (script)).to_contain_error.any_of { - ":3: attempt to call global 'fn'", - ":3: attempt to call a nil value (global 'fn')", - } - - -- describe DEPRECATIONMSG: - - before: | - function mkscript (lvl) - return [[ - DEPRECATIONMSG = require "]] .. this_module .. [[".DEPRECATIONMSG - function fn () - io.stderr:write (DEPRECATIONMSG ("42", "spec file", ]] .. lvl .. [[)) - end - fn () -- line 5 - fn () -- line 6 - ]] - end - - f = M.DEPRECATIONMSG - - - it contains deprecating the release version: - expect (f ("41", "foo", 2)).to_contain "41" - - it contains the deprecation function name: - expect (f ("41", "some.module.fname", 2)).to_contain "some.module.fname" - - it appends an optional extra message: - expect (f ("41", "wuh", "ah boo", 2)).to_contain ", ah boo." - - it blames the given stack level: - expect (luaproc (mkscript (1))).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (mkscript (2))).to_match_error "^%S+:5:.*deprecated" - - - describe resulterror: - before: | function mkstack (level) diff --git a/specs/maturity_spec.yaml b/specs/maturity_spec.yaml new file mode 100644 index 0000000..0fc8a9a --- /dev/null +++ b/specs/maturity_spec.yaml @@ -0,0 +1,105 @@ +before: + this_module = "std.maturity" + + M = require (this_module) + + +specify std.maturity: +- describe DEPRECATED: + - before: | + function runscript (body, name, args) + return luaproc ( + "require '" .. this_module .. "'.DEPRECATED ('0', '" .. + (name or "runscript") .. "', function (...)" .. + " " .. body .. + " end) " .. + "('" .. table.concat (args or {}, "', '") .. "')" + ) + end + + f, badarg = init (M, this_module, "DEPRECATED") + + - it returns a function: + expect (type (f ("0", "deprecated", nop))).to_be "function" + expect (f ("0", "deprecated", nop)).not_to_be (nop) + - context with deprecated function: + - it executes the deprecated function: + expect (runscript 'error "oh noes!"').to_contain_error "oh noes!" + - it passes arguments to the deprecated function: + expect (runscript ("print (table.concat ({...}, ', '))", nil, + {"foo", "bar", "baz"})).to_output "foo, bar, baz\n" + - it returns deprecated function results: | + script = [[ + DEPRECATED = require "]] .. this_module .. [[".DEPRECATED + fn = DEPRECATED ("0", "fn", function () return "foo", "bar", "baz" end) + print (fn ()) + ]] + expect (luaproc (script)).to_output "foo\tbar\tbaz\n" + - it writes a warning to stderr: + expect (runscript 'error "oh noes!"'). + to_match_error "deprecated.*, and will be removed" + - it writes the version string to stderr: + expect (runscript 'error "oh noes!"'). + to_contain_error "in release 0" + - it writes the call location to stderr: | + expect (runscript 'error "oh noes!"'). + to_match_error "^%S+:1: " + - context with _DEBUG: + - before: | + script = [[ + DEPRECATED = require "]] .. this_module .. [[".DEPRECATED + fn = DEPRECATED ("0", "fn", function () io.stderr:write "oh noes!\n" end) + fn () -- line 3 + fn () -- line 4 + ]] + - it warns every call by default: + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" + - it does not warn at all with _DEBUG set to false: + script = "_DEBUG = false " .. script + expect (luaproc (script)).not_to_match_error "%d:.*deprecated" + - it does not define the function with _DEBUG set to true: | + script = "_DEBUG = true " .. script + expect (luaproc (script)).to_contain_error.any_of { + ":3: attempt to call global 'fn'", + ":3: attempt to call a nil value (global 'fn')", + } + - it warns on every call with _DEBUG.deprecate unset: + script = "_DEBUG = {} " .. script + expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" + - it does not warn at all with _DEBUG.deprecate set to false: + script = "_DEBUG = { deprecate = false } " .. script + expect (luaproc (script)).not_to_match_error "%d:.*deprecated" + - it warns on every call with _DEBUG.deprecate set to true: | + script = "_DEBUG = { deprecate = true } " .. script + expect (luaproc (script)).to_contain_error.any_of { + ":3: attempt to call global 'fn'", + ":3: attempt to call a nil value (global 'fn')", + } + + +- describe DEPRECATIONMSG: + - before: | + function mkscript (lvl) + return [[ + DEPRECATIONMSG = require "]] .. this_module .. [[".DEPRECATIONMSG + function fn () + io.stderr:write (DEPRECATIONMSG ("42", "fname", "extra", ]] .. lvl .. [[)) + end + fn () -- line 5 + fn () -- line 6 + ]] + end + + f = M.DEPRECATIONMSG + + - it contains deprecating the release version: + expect (luaproc (mkscript (2))).to_match_error "42" + - it contains the deprecation function name: + expect (luaproc (mkscript (2))).to_match_error "fname" + - it appends an optional extra message: + expect (luaproc (mkscript (2))).to_match_error ", extra." + - it blames the given stack level: + expect (luaproc (mkscript (1))).to_match_error "^%S+:3:.*deprecated" + expect (luaproc (mkscript (2))).to_match_error "^%S+:5:.*deprecated" diff --git a/specs/specs.mk b/specs/specs.mk index ac08f26..86158f0 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -19,6 +19,7 @@ specl_SPECS = \ $(srcdir)/specs/io_spec.yaml \ $(srcdir)/specs/list_spec.yaml \ $(srcdir)/specs/math_spec.yaml \ + $(srcdir)/specs/maturity_spec.yaml \ $(srcdir)/specs/object_spec.yaml \ $(srcdir)/specs/operator_spec.yaml \ $(srcdir)/specs/optparse_spec.yaml \ From 88c639bd08451f88d2b935f373c2e02b874407e6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 26 Sep 2015 23:44:32 +0100 Subject: [PATCH 606/703] refactor: move deprecated functions do std.delete-after modules. * specs/table_spec.yaml (std.table.len, std.table.okeys): Don't specify argschecks for deprecated APIs. * lib/std/debug.lua, lib/std/functional.lua, lib/std/list.lua, lib/std/object.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua: Move all deprecated code from here... * lib/std/delete-after/2016-01-03.lua: New file. ...to here for APIs first deprecated in v41.0.0. * lib/std/delete-after/2016-01-31.lua: New file. ...to here for APIs first deprecated in v41.1.1. * lib/std/delete-after/2016-03-08.lua: New file. ...to here for APIs first deprecated in v41.2.0. * lib/std/delete-after/a-year.lua: New file. ...to here for APIs deprecated in the upcoming release. * local.mk (luastddeletedir, dist_luastddelete_DATA): Add new files accordingly. * NEWS.md (New features, Deprecations): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 15 + lib/std/debug.lua | 17 +- lib/std/delete-after/2016-01-03.lua | 432 +++++++++++++++++++++++++++ lib/std/delete-after/2016-01-31.lua | 87 ++++++ lib/std/delete-after/2016-03-08.lua | 83 ++++++ lib/std/delete-after/a-year.lua | 93 ++++++ lib/std/functional.lua | 50 +--- lib/std/list.lua | 436 +++++++--------------------- lib/std/object.lua | 133 +++++---- lib/std/strbuf.lua | 99 +++---- lib/std/string.lua | 24 +- lib/std/table.lua | 51 +--- local.mk | 9 + specs/table_spec.yaml | 6 - 14 files changed, 963 insertions(+), 572 deletions(-) create mode 100644 lib/std/delete-after/2016-01-03.lua create mode 100644 lib/std/delete-after/2016-01-31.lua create mode 100644 lib/std/delete-after/2016-03-08.lua create mode 100644 lib/std/delete-after/a-year.lua diff --git a/NEWS.md b/NEWS.md index c6647b7..a5c29ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -21,6 +21,15 @@ if rawget (_G, "setfenv") then setfenv (1, _ENV) end ``` + - All support for deprecated APIs has been moved out of the module + sources, so that it needn't be loaded in production code that has + been properly updated not to use deprecated calls, i.e: + + ```lua + local _DEBUG = require "std.debug_init"._DEBUG + _DEBUG.deprecate = true + ``` + - For orthogonality with core Lua `type`, we now export the `std.object.type` function as `std.type`. @@ -126,6 +135,12 @@ ### Deprecations + - Deprecated functions no longer support argument checks, partially to + simplify the deprecation plumbing, but also because if you are + developing code with argument checking on, then you are already + getting deprecation messages that tell you not to keep using these + functions. + - We used to have an object module method, `std.object.type`, which often got imported using: diff --git a/lib/std/debug.lua b/lib/std/debug.lua index e215721..31ce4b1 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -48,9 +48,9 @@ local table_remove = table.remove local table_sort = table.sort +local deprecated = require "std.delete-after.2016-03-08" local std = require "std.base" -local DEPRECATED = require "std.maturity".DEPRECATED local argerror = std.debug.argerror local copy = std.base.copy local insert = std.table.insert @@ -884,18 +884,9 @@ local metatable = { } - ---[[ =========== ]]-- ---[[ Deprecated. ]]-- ---[[ =========== ]]-- - - -M.toomanyargmsg = DEPRECATED ("41.2.0", "debug.toomanyargmsg", - "use 'debug.extramsg_toomany' instead", - function (name, expect, actual) - local s = "bad argument #%d to '%s' (no more than %d argument%s expected, got %d)" - return s:format (expect + 1, name, expect, expect == 1 and "" or "s", actual) - end) +if deprecated then + M = merge (M, deprecated.debug) +end return setmetatable (merge (M, debug), metatable) diff --git a/lib/std/delete-after/2016-01-03.lua b/lib/std/delete-after/2016-01-03.lua new file mode 100644 index 0000000..984129a --- /dev/null +++ b/lib/std/delete-after/2016-01-03.lua @@ -0,0 +1,432 @@ +--[[-- + Provide at least one year of support for deprecated APIs, or at + least one release cycle if that is longer. + + When `_DEBUG.deprecate` is `true` we don`t even load this support, in + which case `require`ing this module returns `false`. + + Otherwise, return a table of all functions deprecated in the given + `RELEASE` and earlier, going back at least one year. The table is + keyed on the original module to enable merging deprecated APIs back + into their previous namespaces - this is handled automatically by the + documented modules according to the contents of `_DEBUG`. + + In some release after the date of this module, it will be removed and + these APIs will not be available any longer. +]] + + +local RELEASE = "41.0.0" + +local M = false + +if not require "std.debug_init"._DEBUG.deprecate then + + local getmetatable = getmetatable + local pairs = pairs + local type = type + + local coroutine_yield = coroutine.yield + local coroutine_wrap = coroutine.wrap + local math_ceil = math.ceil + local math_max = math.max + local table_unpack = table.unpack or unpack + + local _, deprecated = { + -- Adding anything else here will probably cause a require loop. + maturity = require "std.maturity", + std = require "std.base", + } + + -- Merge in deprecated APIs from previous release if still available. + _.ok, deprecated = pcall (require, "std.delete-after.2015-05.01") + if not _.ok then deprecated = {} end + + + -- Dangerous :-o Hope we don't need to deprecate anything in + -- std.operator any time in the next year or so... + local operator = require "std.operator" + + local DEPRECATED = _.maturity.DEPRECATED + local eval = _.std.eval + local ielems = _.std.ielems + local ireverse = _.std.ireverse + local ripairs = _.std.ripairs + local std_assert = _.std.assert + local std_ipairs = _.std.ipairs + local std_pairs = _.std.pairs + local std_require = _.std.require + local std_tostring = _.std.tostring + + -- Only the above symbols are used below this line. + local _, _ENV = nil, _.std.base.setenvtable {} + + + --[[ ========== ]]-- + --[[ Death Row! ]]-- + --[[ ========== ]]-- + + local function callable (x) + if type (x) == "function" then return x end + return (getmetatable (x) or {}).__call + end + + + local function getmetamethod (x, n) + local m = (getmetatable (x) or {})[n] + if callable (m) then return m end + end + + + local function len (t) + local m = getmetamethod (t, "__len") + return m and m (t) or #t + end + + + local function depair (proto, ls) + local t = {} + for _, v in std_ipairs (ls) do + t[v[1]] = v[2] + end + return t + end + + + local function elems (proto, ...) + return ielems (...) + end + + + local function enpair (proto, t) + local ls = proto {} + for i, v in std_pairs (t) do + ls[#ls + 1] = proto {i, v} + end + return ls + end + + + local function filter (proto, pfn, l) + local r = proto {} + for _, e in std_ipairs (l) do + if pfn (e) then + r[#r + 1] = e + end + end + return r + end + + + local function fold (fn, d, ifn, ...) + local nextfn, state, k = ifn (...) + local t = {nextfn (state, k)} + + local r = d + while t[1] ~= nil do + r = fn (r, t[#t]) + t = {nextfn (state, t[1])} + end + return r + end + + + local function reduce (fn, d, ifn, ...) + local argt = {...} + if not callable (ifn) then + ifn, argt = std_pairs, {ifn, ...} + end + + local nextfn, state, k = ifn (table_unpack (argt)) + local t = {nextfn (state, k)} -- table of iteration 1 + + local r = d -- initialise accumulator + while t[1] ~= nil do -- until iterator returns nil + k = t[1] + r = fn (r, table_unpack (t)) -- pass all iterator results to fn + t = {nextfn (state, k)} -- maintain loop invariant + end + return r + end + + + local function foldl (proto, fn, d, t) + if t == nil then + local tail = {} + for i = 2, len (d) do tail[#tail + 1] = d[i] end + d, t = d[1], tail + end + return reduce (fn, d, ielems, t) + end + + + local function foldr (proto, fn, d, t) + if t == nil then + local u, last = {}, len (d) + for i = 1, last - 1 do u[#u + 1] = d[i] end + d, t = d[last], u + end + return reduce (function (x, y) return fn (y, x) end, d, ielems, ireverse (t)) + end + + + local function index_key (proto, f, l) + local r = {} + for i, v in std_ipairs (l) do + local k = v[f] + if k then + r[k] = i + end + end + return r + end + + + local function index_value (proto, f, l) + local r = {} + for i, v in std_ipairs (l) do + local k = v[f] + if k then + r[k] = v + end + end + return r + 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 + coroutine_yield (n) + end + end + return coroutine_wrap (visit), tr + end + + + local function flatten (proto, l) + local r = proto {} + for v in leaves (std_ipairs, l) do + r[#r + 1] = v + end + return r + end + + + local function map (proto, fn, l) + local r = proto {} + for _, e in std_ipairs (l) do + local v = fn (e) + if v ~= nil then + r[#r + 1] = v + end + end + return r + end + + + local function map_with (proto, fn, ls) + return map (proto, function (...) return fn (table_unpack (...)) end, ls) + end + + + local function project (proto, x, l) + return map (proto, function (t) return t[x] end, l) + end + + + local function relems (proto, l) return ielems (ireverse (l)) end + + + local function reverse (proto, l) return proto (ireverse (l)) end + + + local function shape (proto, s, l) + l = flatten (proto, l) + -- Check the shape and calculate the size of the zero, if any + local size = 1 + local zero + for i, v in std_ipairs (s) do + if v == 0 then + if zero then -- bad shape: two zeros + return nil + else + zero = i + end + else + size = size * v + end + end + if zero then + s[zero] = math_ceil (len (l) / size) + end + local function fill (i, d) + if d > len (s) then + return l[i], i + 1 + else + local r = proto {} + for j = 1, s[d] do + local e + e, i = fill (i, d + 1) + r[#r + 1] = e + end + return r, i + end + end + return (fill (1, 1)) + end + + + local function totable (x) + local m = getmetamethod (x, "__totable") + if m then + return m (x) + elseif type (x) == "table" then + return x + elseif type (x) == "string" then + local t = {} + x:gsub (".", function (c) t[#t + 1] = c end) + return t + else + return nil + end + end + + + local function transpose (proto, ls) + local rs, lenls, dims = proto {}, len (ls), map (proto, len, ls) + if len (dims) > 0 then + for i = 1, math_max (table_unpack (dims)) do + rs[i] = proto {} + for j = 1, lenls do + rs[i][j] = ls[j][i] + end + end + end + return rs + end + + + local function zip_with (proto, ls, fn) + return map_with (proto, fn, transpose (proto, ls)) + end + + + -- Ensure deprecated APIs observe _DEBUG warning standards. + local function X (old, new, fn) + if fn ~= nil then new = "use '" .. new .. "' instead" end + return DEPRECATED (RELEASE, "'std." .. old .. "'", new, fn) + end + + local function DEPRECATEOP (old, new) + return X ("functional.op[" .. old .. "]", "std.operator." .. new, operator[new]) + end + + local function acyclic_merge (dest, src) + for k, v in pairs (src) do + if type (v) == "table" then + dest[k] = dest[k] or {} + if type (dest[k]) == "table" then acyclic_merge (dest[k], v) end + else + dest[k] = dest[k] or v + end + end + return dest + end + + M = acyclic_merge ({ + functional = { + eval = X ("functional.eval", "std.eval", eval), + fold = X ("functional.fold", "std.functional.reduce", fold), + op = { + ["[]"] = DEPRECATEOP ("[]", "get"), + ["+"] = DEPRECATEOP ("+", "sum"), + ["-"] = DEPRECATEOP ("-", "diff"), + ["*"] = DEPRECATEOP ("*", "prod"), + ["/"] = DEPRECATEOP ("/", "quot"), + ["and"] = DEPRECATEOP ("and", "conj"), + ["or"] = DEPRECATEOP ("or", "disj"), + ["not"] = DEPRECATEOP ("not", "neg"), + ["=="] = DEPRECATEOP ("==", "eq"), + ["~="] = DEPRECATEOP ("~=", "neq"), + }, + }, + + list = { + depair = X ("list.depair", depair), + elems = X ("list.elems", "std.ielems", elems), + enpair = X ("list.enpair", enpair), + filter = X ("list.filter", "std.functional.filter", filter), + flatten = X ("list.flatten", "std.functional.flatten", flatten), + foldl = X ("list.foldl", "std.functional.foldl", foldl), + foldr = X ("list.foldr", "std.functional.foldr", foldr), + index_key = DEPRECATED (RELEASE, "'std.list.index_key'", + "compose 'std.functional.filter' and 'std.table.invert' instead", + index_key), + index_value = DEPRECATED (RELEASE, "'std.list.index_value'", + "compose 'std.functional.filter' and 'std.table.invert' instead", + index_value), + map = X ("list.map", "std.functional.map", map), + map_with = X ("list.map_with'", "std.functional.map_with", map_with), + project = X ("list.project", "std.table.project", project), + relems = DEPRECATED (RELEASE, "'std.list.relems'", + "compose 'std.ielems' and 'std.ireverse' instead", relems), + reverse = DEPRECATED (RELEASE, "'std.list.reverse'", + "compose 'std.list' and 'std.ireverse' instead", reverse), + shape = X ("list.shape", "std.table.shape", shape), + transpose = X ("list.transpose", "std.functional.zip", transpose), + zip_with = X ("list.zip_with", "std.functional.zip_with", zip_with), + }, + + string = { + assert = X ("string.assert", "std.assert", std_assert), + require_version = X ("string.require_version", "std.require", std_require), + tostring = X ("string.tostring", "std.tostring", std_tostring), + }, + + table = { + metamethod = X ("table.metamethod", "std.getmetamethod", getmetamethod), + ripairs = X ("table.ripairs", "std.ripairs", ripairs), + totable = X ("table.totable", "std.pairs", totable), + }, + + methods = { + list = { + elems = X ("list:elems", "std.ielems", elems), + enpair = X ("list:enpair", enpair), + filter = X ("std.list:filter", "std.functional.filter", + function (proto, self, p) return filter (proto, p, self) end), + flatten = X ("list:flatten", "std.functional.flatten", flatten), + foldl = X ("list:foldl", "std.functional.foldl", function (proto, self, fn, e) + if e ~= nil then return foldl (proto, fn, e, self) end + return foldl (proto, fn, self) + end), + foldr = X ("list:foldr", "std.functional.foldr", function (proto, self, fn, e) + if e ~= nil then return foldr (proto, fn, e, self) end + return foldr (proto, fn, self) + end), + index_key = X ("list:index_key", + function (proto, self, fn) return index_key (proto, fn, self) end), + index_value = X ("list:index_value", + function (proto, self, fn) return index_value (proto, fn, self) end), + map = X ("list:map", "std.functional.map", + function (proto, self, fn) return map (proto, fn, self) end), + project = X ("list:project", "std.table.project", + function (proto, self, x) return project (proto, x, self) end), + relems = X ("list:relems", relems), + reverse = DEPRECATED (RELEASE, "'std.list:reverse'", + "compose 'std.list' and 'std.ireverse' instead", reverse), + shape = X ("list:shape", "std.table.shape", + function (proto, t, l) return shape (proto, l, t) end), + }, + }, + }, + deprecated) + +end + +return M diff --git a/lib/std/delete-after/2016-01-31.lua b/lib/std/delete-after/2016-01-31.lua new file mode 100644 index 0000000..6a41e26 --- /dev/null +++ b/lib/std/delete-after/2016-01-31.lua @@ -0,0 +1,87 @@ +--[[-- + Provide at least one year of support for deprecated APIs, or at + least one release cycle if that is longer. + + When `_DEBUG.deprecate` is `true` we don`t even load this support, in + which case `require`ing this module returns `false`. + + Otherwise, return a table of all functions deprecated in the given + `RELEASE` and earlier, going back at least one year. The table is + keyed on the original module to enable merging deprecated APIs back + into their previous namespaces - this is handled automatically by the + documented modules according to the contents of `_DEBUG`. + + In some release after the date of this module, it will be removed and + these APIs will not be available any longer. +]] + + +local RELEASE = "41.1.1" + + +local M = false + +if not require "std.debug_init"._DEBUG.deprecate then + + local pairs = pairs + local tostring = tostring + local type = type + local table_concat = table.concat + + local _, deprecated = { + -- Adding anything else here will probably cause a require loop. + maturity = require "std.maturity", + std = require "std.base", + } + + -- Merge in deprecated APIs from previous release if still available. + _.ok, deprecated = pcall (require, "std.delete-after.2016-01-03") + if not _.ok then deprecated = {} end + + + local DEPRECATED = _.maturity.DEPRECATED + local std_ipairs = _.std.ipairs + + -- Only the above symbols are used below this line. + local _, _ENV = nil, _.std.base.setenvtable {} + + + --[[ ========== ]]-- + --[[ Death Row! ]]-- + --[[ ========== ]]-- + + local function strbuf_tostring (strbuf) + local strs = {} + for _, v in std_ipairs (strbuf) do strs[#strs + 1] = tostring (v) end + return table_concat (strs) + end + + + -- Ensure deprecated APIs observe _DEBUG warning standards. + local function X (old, new, fn) + return DEPRECATED (RELEASE, "'std." .. old .. "'", "use '" .. new .. "' instead", fn) + end + + local function acyclic_merge (dest, src) + for k, v in pairs (src) do + if type (v) == "table" then + dest[k] = dest[k] or {} + if type (dest[k]) == "table" then acyclic_merge (dest[k], v) end + else + dest[k] = dest[k] or v + end + end + return dest + end + + M = acyclic_merge ({ + strbuf = { + tostring = X ("strbuf.tostring", "tostring (strbuf)", strbuf_tostring) + }, + }, + deprecated) + +end + + +return M diff --git a/lib/std/delete-after/2016-03-08.lua b/lib/std/delete-after/2016-03-08.lua new file mode 100644 index 0000000..d92e99c --- /dev/null +++ b/lib/std/delete-after/2016-03-08.lua @@ -0,0 +1,83 @@ +--[[-- + Provide at least one year of support for deprecated APIs, or at + least one release cycle if that is longer. + + When `_DEBUG.deprecate` is `true` we don`t even load this support, in + which case `require`ing this module returns `false`. + + Otherwise, return a table of all functions deprecated in the given + `RELEASE` and earlier, going back at least one year. The table is + keyed on the original module to enable merging deprecated APIs back + into their previous namespaces - this is handled automatically by the + documented modules according to the contents of `_DEBUG`. + + In some release after the date of this module, it will be removed and + these APIs will not be available any longer. +]] + + +local RELEASE = "41.2.0" + + +local M = false + +if not require "std.debug_init"._DEBUG.deprecate then + + local pairs = pairs + local type = type + local string_format = string.format + + local _, deprecated = { + -- Adding anything else here will probably cause a require loop. + maturity = require "std.maturity", + std = require "std.base", + } + + -- Merge in deprecated APIs from previous release if still available. + _.ok, deprecated = pcall (require, "std.delete-after.2016-01-31") + if not _.ok then deprecated = {} end + + + local DEPRECATED = _.maturity.DEPRECATED + + -- Only the above symbols are used below this line. + local _, _ENV = nil, _.std.base.setenvtable {} + + + --[[ ========== ]]-- + --[[ Death Row! ]]-- + --[[ ========== ]]-- + + local function toomanyargmsg (name, expect, actual) + local s = "bad argument #%d to '%s' (no more than %d argument%s expected, got %d)" + return string_format (s, expect + 1, name, expect, expect == 1 and "" or "s", actual) + end + + + -- Ensure deprecated APIs observe _DEBUG warning standards. + local function X (old, new, fn) + return DEPRECATED (RELEASE, "'std." .. old .. "'", "use '" .. new .. "' instead", fn) + end + + local function acyclic_merge (dest, src) + for k, v in pairs (src) do + if type (v) == "table" then + dest[k] = dest[k] or {} + if type (dest[k]) == "table" then acyclic_merge (dest[k], v) end + else + dest[k] = dest[k] or v + end + end + return dest + end + + M = acyclic_merge ({ + debug = { + toomanyargmsg = X ("debug.toomanyargmsg", "std.debug.extramsg_toomany", toomanyargmsg), + }, + }, + deprecated) + +end + +return M diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua new file mode 100644 index 0000000..56d7ce9 --- /dev/null +++ b/lib/std/delete-after/a-year.lua @@ -0,0 +1,93 @@ +--[[-- + Provide at least one year of support for deprecated APIs, or at + least one release cycle if that is longer. + + When `_DEBUG.deprecate` is `true` we don`t even load this support, in + which case `require`ing this module returns `false`. + + Otherwise, return a table of all functions deprecated in the given + `RELEASE` and earlier, going back at least one year. The table is + keyed on the original module to enable merging deprecated APIs back + into their previous namespaces - this is handled automatically by the + documented modules according to the contents of `_DEBUG`. + + In some release after the date of this module, it will be removed and + these APIs will not be available any longer. +]] + + +local RELEASE = "upcoming" + + +local M = false + +if not require "std.debug_init"._DEBUG.deprecate then + + local pairs = pairs + local type = type + + local _, deprecated = { + -- Adding anything else here will probably cause a require loop. + maturity = require "std.maturity", + std = require "std.base", + } + + -- Merge in deprecated APIs from previous release if still available. + _.ok, deprecated = pcall (require, "std.delete-after.2016-03-08") + if not _.ok then deprecated = {} end + + + local DEPRECATED = _.maturity.DEPRECATED + local len = _.std.operator.len + local std_pairs = _.std.pairs + local sortkeys = _.std.base.sortkeys + local std_type = _.std.type + + -- Only the above symbols are used below this line. + local _, _ENV = nil, _.std.base.setenvtable {} + + + --[[ ========== ]]-- + --[[ Death Row! ]]-- + --[[ ========== ]]-- + + local function okeys (t) + local r = {} + for k in std_pairs (t) do r[#r + 1] = k end + return sortkeys (r) + end + + + -- Ensure deprecated APIs observe _DEBUG warning standards. + local function X (old, new, fn) + return DEPRECATED (RELEASE, "'std." .. old .. "'", "use '" .. new .. "' instead", fn) + end + + local function acyclic_merge (dest, src) + for k, v in pairs (src) do + if type (v) == "table" then + dest[k] = dest[k] or {} + if type (dest[k]) == "table" then acyclic_merge (dest[k], v) end + else + dest[k] = dest[k] or v + end + end + return dest + end + + M = acyclic_merge ({ + object = { + prototype = X ("object.prototype", "std.type", std_type), + type = X ("object.type", "std.type", std_type), + }, + + table = { + len = X ("table.len", "std.operator.len", len), + okeys = DEPRECATED (RELEASE, "'std.table.okeys'", "compose 'std.table.keys' and 'std.table.sort' instead", okeys), + }, + }, + deprecated) + +end + +return M diff --git a/lib/std/functional.lua b/lib/std/functional.lua index b821f1f..e0e500f 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -17,12 +17,10 @@ local setmetatable = setmetatable local table_remove = table.remove +local deprecated = require "std.delete-after.2016-01-03" local std = require "std.base" -local debug = require "std.debug" -local operator = require "std.operator" -local DEPRECATED = require "std.maturity".DEPRECATED -local argscheck = debug.argscheck +local argscheck = require "std.debug".argscheck local callable = std.functional.callable local collect = std.functional.collect local copy = std.base.copy @@ -30,6 +28,7 @@ local ielems = std.ielems local ipairs = std.ipairs local ireverse = std.ireverse local len = std.operator.len +local merge = std.base.merge local mnemonic = std.base.mnemonic local nop = std.functional.nop local npairs = std.npairs @@ -603,49 +602,10 @@ local M = { } - ---[[ ============= ]]-- ---[[ Deprecations. ]]-- ---[[ ============= ]]-- - - -M.eval = DEPRECATED ("41", "'std.functional.eval'", - "use 'std.eval' instead", std.eval) - - -local function fold (fn, d, ifn, ...) - local nextfn, state, k = ifn (...) - local t = {nextfn (state, k)} - - local r = d - while t[1] ~= nil do - r = fn (r, t[#t]) - t = {nextfn (state, t[1])} - end - return r -end - -M.fold = DEPRECATED ("41", "'std.functional.fold'", - "use 'std.functional.reduce' instead", fold) - - -local function DEPRECATEOP (old, new) - return DEPRECATED ("41", "'std.functional.op[" .. old .. "]'", - "use 'std.operator." .. new .. "' instead", operator[new]) +if deprecated then + M = merge (M, deprecated.functional) end -M.op = { - ["[]"] = DEPRECATEOP ("[]", "get"), - ["+"] = DEPRECATEOP ("+", "sum"), - ["-"] = DEPRECATEOP ("-", "diff"), - ["*"] = DEPRECATEOP ("*", "prod"), - ["/"] = DEPRECATEOP ("/", "quot"), - ["and"] = DEPRECATEOP ("and", "conj"), - ["or"] = DEPRECATEOP ("or", "disj"), - ["not"] = DEPRECATEOP ("not", "neg"), - ["=="] = DEPRECATEOP ("==", "eq"), - ["~="] = DEPRECATEOP ("~=", "neq"), -} return M diff --git a/lib/std/list.lua b/lib/std/list.lua index 89b00c7..7bea01d 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -22,16 +22,17 @@ local math_max = math.max local debug = require "std.debug" +local deprecated = require "std.delete-after.2016-01-03" local std = require "std.base" local Module = std.object.Module local Object = require "std.object".prototype -local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local compare = std.list.compare local ipairs = std.ipairs local len = std.operator.len +local merge = std.base.merge local pairs = std.pairs local unpack = std.table.unpack @@ -44,7 +45,7 @@ local _ENV = std.base.setenvtable {} --[[ ================= ]]-- -local M, prototype +local prototype local function append (l, x) @@ -93,182 +94,6 @@ end ---[[ ============= ]]-- ---[[ Deprecations. ]]-- ---[[ ============= ]]-- - - --- This entire section can be deleted any time after 2016.01.03, along --- with all references to these functions further down. - - -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 ls = prototype {} - for i, v in pairs (t) do - ls[#ls + 1] = prototype {i, v} - end - return ls -end - - -local function filter (pfn, l) - local r = prototype {} - for _, e in ipairs (l) do - if pfn (e) then - r[#r + 1] = e - end - end - return r -end - - -local function flatten (l) - local r = prototype {} - for v in std.tree.leaves (ipairs, l) do - r[#r + 1] = v - end - return r -end - - -local function foldl (fn, d, t) - if t == nil then - local tail = {} - for i = 2, len (d) do tail[#tail + 1] = d[i] end - d, t = d[1], tail - end - return std.functional.reduce (fn, d, std.ielems, t) -end - - -local function foldr (fn, d, t) - if t == nil then - local u, last = {}, len (d) - for i = 1, last - 1 do u[#u + 1] = d[i] end - d, t = d[last], u - end - return std.functional.reduce ( - function (x, y) return fn (y, x) end, d, std.ielems, std.ireverse (t)) -end - - -local function index_key (f, l) - local r = {} - for i, v in ipairs (l) do - local k = v[f] - if k then - r[k] = i - end - end - return r -end - - -local function index_value (f, l) - local r = {} - for i, v in ipairs (l) do - local k = v[f] - if k then - r[k] = v - end - end - return r -end - - -local function map (fn, l) - local r = prototype {} - for _, e in ipairs (l) do - local v = fn (e) - if v ~= nil then - r[#r + 1] = v - end - end - return r -end - - -local function map_with (fn, ls) - return map (function (...) return fn (unpack (...)) end, ls) -end - - -local function project (x, l) - return map (function (t) return t[x] end, l) -end - - -local function relems (l) return std.ielems (std.ireverse (l)) end - - -local function reverse (l) return prototype (std.ireverse (l)) end - - -local function shape (s, l) - l = flatten (l) - -- Check the shape and calculate the size of the zero, if any - local size = 1 - local zero - for i, v in ipairs (s) do - if v == 0 then - if zero then -- bad shape: two zeros - return nil - else - zero = i - end - else - size = size * v - end - end - if zero then - s[zero] = math_ceil (len (l) / size) - end - local function fill (i, d) - if d > len (s) then - return l[i], i + 1 - else - local r = prototype {} - for j = 1, s[d] do - local e - e, i = fill (i, d + 1) - r[#r + 1] = e - end - return r, i - end - end - return (fill (1, 1)) -end - - -local function transpose (ls) - local rs, lenls, dims = prototype {}, len (ls), map (len, ls) - if len (dims) > 0 then - for i = 1, math_max (unpack (dims)) do - rs[i] = prototype {} - for j = 1, lenls do - rs[i][j] = ls[j][i] - end - end - end - return rs -end - - -local function zip_with (ls, fn) - return map_with (fn, transpose (ls)) -end - - - --[[ ================== ]]-- --[[ Type Declarations. ]]-- --[[ ================== ]]-- @@ -279,6 +104,78 @@ local function X (decl, fn) end +local methods = { + --- Methods + -- @section methods + + --- Append an item to a list. + -- @function prototype:append + -- @param x item + -- @treturn prototype new list with *x* appended + -- @usage + -- --> List {"shorter", "longer"} + -- longer = (List {"shorter"}):append "longer" + append = X ("append (List, any)", append), + + --- Compare two lists element-by-element, from left-to-right. + -- @function prototype:compare + -- @tparam prototype|table m another list, or table + -- @return -1 if *l* is less than *m*, 0 if they are the same, and 1 + -- if *l* is greater than *m* + -- @usage + -- if list1:compare (list2) == 0 then print "same" end + compare = X ("compare (List, List|table)", compare), + + --- Concatenate the elements from any number of lists. + -- @function prototype:concat + -- @tparam prototype|table ... additional lists, or list-like tables + -- @treturn prototype new list with elements from arguments + -- @usage + -- --> List {"shorter", "short", "longer", "longest"} + -- longest = (List {"shorter"}):concat ({"short", "longer"}, {"longest"}) + concat = X ("concat (List, List|table...)", concat), + + --- Prepend an item to a list. + -- @function prototype:cons + -- @param x item + -- @treturn prototype new list with *x* followed by elements of *l* + -- @usage + -- --> List {"x", 1, 2, 3} + -- consed = (List {1, 2, 3}):cons "x" + cons = X ("cons (List, any)", function (l, x) return prototype {x, unpack (l)} end), + + --- Repeat a list. + -- @function prototype:rep + -- @int n number of times to repeat + -- @treturn prototype *n* copies of *l* appended together + -- @usage + -- --> List {1, 2, 3, 1, 2, 3, 1, 2, 3} + -- repped = (List {1, 2, 3}):rep (3) + rep = X ("rep (List, int)", rep), + + --- Return a sub-range of a list. + -- (The equivalent of @{string.sub} on strings; negative list indices + -- count from the end of the list.) + -- @function prototype:sub + -- @int[opt=1] from start of range + -- @int[opt=#l] to end of range + -- @treturn prototype new list containing elements between *from* and *to* + -- inclusive + -- @usage + -- --> List {3, 4, 5} + -- subbed = (List {1, 2, 3, 4, 5, 6}):sub (3, 5) + sub = X ("sub (List, ?int, ?int)", sub), + + --- Return a list with its first element removed. + -- @function prototype:tail + -- @treturn prototype new list with all but the first element of *l* + -- @usage + -- --> List {3, {4, 5}, 6, 7} + -- tailed = (List {{1, 2}, 3, {4, 5}, 6, 7}):tail () + tail = X ("tail (List)", function (l) return sub (l, 2) end), +} + + --- List prototype object. -- @object prototype -- @string[opt="List"] _type object name @@ -287,7 +184,7 @@ end -- @usage -- local List = require "std.list".prototype -- assert (std.type (List) == "List") -prototype = Object { +local List = { _type = "std.list.List", --- Metamethods @@ -325,159 +222,38 @@ prototype = Object { -- min = list1 <= list2 and list1 or list2 __le = function (list1, list2) return compare (list1, list2) <= 0 end, - __index = { - --- Methods - -- @section methods - - --- Append an item to a list. - -- @function prototype:append - -- @param x item - -- @treturn prototype new list with *x* appended - -- @usage - -- --> List {"shorter", "longer"} - -- longer = (List {"shorter"}):append "longer" - append = X ("append (List, any)", append), - - --- Compare two lists element-by-element, from left-to-right. - -- @function prototype:compare - -- @tparam prototype|table m another list, or table - -- @return -1 if *l* is less than *m*, 0 if they are the same, and 1 - -- if *l* is greater than *m* - -- @usage - -- if list1:compare (list2) == 0 then print "same" end - compare = X ("compare (List, List|table)", compare), - - --- Concatenate the elements from any number of lists. - -- @function prototype:concat - -- @tparam prototype|table ... additional lists, or list-like tables - -- @treturn prototype new list with elements from arguments - -- @usage - -- --> List {"shorter", "short", "longer", "longest"} - -- longest = (List {"shorter"}):concat ({"short", "longer"}, {"longest"}) - concat = X ("concat (List, List|table...)", concat), - - --- Prepend an item to a list. - -- @function prototype:cons - -- @param x item - -- @treturn prototype new list with *x* followed by elements of *l* - -- @usage - -- --> List {"x", 1, 2, 3} - -- consed = (List {1, 2, 3}):cons "x" - cons = X ("cons (List, any)", function (l, x) return prototype {x, unpack (l)} end), - - --- Repeat a list. - -- @function prototype:rep - -- @int n number of times to repeat - -- @treturn prototype *n* copies of *l* appended together - -- @usage - -- --> List {1, 2, 3, 1, 2, 3, 1, 2, 3} - -- repped = (List {1, 2, 3}):rep (3) - rep = X ("rep (List, int)", rep), - - --- Return a sub-range of a list. - -- (The equivalent of @{string.sub} on strings; negative list indices - -- count from the end of the list.) - -- @function prototype:sub - -- @int[opt=1] from start of range - -- @int[opt=#l] to end of range - -- @treturn prototype new list containing elements between *from* and *to* - -- inclusive - -- @usage - -- --> List {3, 4, 5} - -- subbed = (List {1, 2, 3, 4, 5, 6}):sub (3, 5) - sub = X ("sub (List, ?int, ?int)", sub), - - --- Return a list with its first element removed. - -- @function prototype:tail - -- @treturn prototype new list with all but the first element of *l* - -- @usage - -- --> List {3, {4, 5}, 6, 7} - -- tailed = (List {{1, 2}, 3, {4, 5}, 6, 7}):tail () - tail = X ("tail (List)", function (l) return sub (l, 2) end), - - enpair = DEPRECATED ("41", "'std.list:enpair'", enpair), - elems = DEPRECATED ("41", "'std.list:elems'", - "use 'std.ielems' instead", std.ielems), - filter = DEPRECATED ("41", "'std.list:filter'", - "use 'std.functional.filter' instead", - function (self, p) return filter (p, self) end), - flatten = DEPRECATED ("41", "'std.list:flatten'", - "use 'std.functional.flatten' instead", flatten), - foldl = DEPRECATED ("41", "'std.list:foldl'", - "use 'std.functional.foldl' instead", - function (self, fn, e) - if e ~= nil then return foldl (fn, e, self) end - return foldl (fn, self) - end), - foldr = DEPRECATED ("41", "'std.list:foldr'", - "use 'std.functional.foldr' instead", - function (self, fn, e) - if e ~= nil then return foldr (fn, e, self) end - return foldr (fn, self) - end), - index_key = DEPRECATED ("41", "'std.list:index_key'", - function (self, fn) return index_key (fn, self) end), - index_value = DEPRECATED ("41", "'std.list:index_value'", - function (self, fn) return index_value (fn, self) end), - map = DEPRECATED ("41", "'std.list:map'", - "use 'std.functional.map' instead", - function (self, fn) return map (fn, self) end), - project = DEPRECATED ("41", "'std.list:project'", - "use 'std.table.project' instead", - function (self, x) return project (x, self) end), - relems = DEPRECATED ("41", "'std.list:relems'", relems), - reverse = DEPRECATED ("41", "'std.list:reverse'", - "compose 'std.list' and 'std.ireverse' instead", reverse), - shape = DEPRECATED ("41", "'std.list:shape'", - "use 'std.table.shape' instead", - function (t, l) return shape (l, t) end), - }, + __index = methods, } -return Module { +-- Lots of scope to tidy and simplify once we don't need to merge in the +-- deprecated functions below. +local M = {} + +if deprecated then + local function bindfns (dest, src) + for k, v in pairs (src) do + dest[k] = dest[k] or function (...) return v (prototype, ...) end + end + return dest + end + + methods = bindfns (methods, deprecated.methods.list) + M = bindfns (M, deprecated.list) +end + + +prototype = Object (List) + + +return Module (merge ({ prototype = prototype, - append = prototype.append, - compare = prototype.compare, - concat = prototype.concat, - cons = prototype.cons, - rep = prototype.rep, - sub = prototype.sub, - tail = prototype.tail, - - depair = DEPRECATED ("41", "'std.list.depair'", depair), - enpair = DEPRECATED ("41", "'std.list.enpair'", enpair), - elems = DEPRECATED ("41", "'std.list.elems'", - "use 'std.ielems' instead", std.ielems), - filter = DEPRECATED ("41", "'std.list.filter'", - "use 'std.functional.filter' instead", filter), - flatten = DEPRECATED ("41", "'std.list.flatten'", - "use 'std.functional.flatten' instead", flatten), - foldl = DEPRECATED ("41", "'std.list.foldl'", - "use 'std.functional.foldl' instead", foldl), - foldr = DEPRECATED ("41", "'std.list.foldr'", - "use 'std.functional.foldr' instead", foldr), - index_key = DEPRECATED ("41", "'std.list.index_key'", - "compose 'std.functional.filter' and 'std.table.invert' instead", - index_key), - index_value = DEPRECATED ("41", "'std.list.index_value'", - "compose 'std.functional.filter' and 'std.table.invert' instead", - index_value), - map = DEPRECATED ("41", "'std.list.map'", - "use 'std.functional.map' instead", map), - map_with = DEPRECATED ("41", "'std.list.map_with'", - "use 'std.functional.map_with' instead", map_with), - project = DEPRECATED ("41", "'std.list.project'", - "use 'std.table.project' instead", project), - relems = DEPRECATED ("41", "'std.list.relems'", - "compose 'std.ielems' and 'std.ireverse' instead", relems), - reverse = DEPRECATED ("41", "'std.list.reverse'", - "compose 'std.list' and 'std.ireverse' instead", reverse), - shape = DEPRECATED ("41", "'std.list.shape'", - "use 'std.table.shape' instead", shape), - transpose = DEPRECATED ("41", "'std.list.transpose'", - "use 'std.functional.zip' instead", transpose), - zip_with = DEPRECATED ("41", "'std.list.zip_with'", - "use 'std.functional.zip_with' instead", zip_with), -} + append = methods.append, + compare = methods.compare, + concat = methods.concat, + cons = methods.cons, + rep = methods.rep, + sub = methods.sub, + tail = methods.tail, +}, M)) diff --git a/lib/std/object.lua b/lib/std/object.lua index 71e4414..26fe3ee 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -23,15 +23,16 @@ local debug = require "std.debug" +local deprecated = require "std.delete-after.a-year" local std = require "std.base" local Container = require "std.container".prototype local Module = std.object.Module -local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local getmetamethod = std.getmetamethod local mapfields = std.object.mapfields +local merge = std.base.merge local type = std.type local _ENV = std.base.setenvtable {} @@ -43,13 +44,71 @@ local _ENV = std.base.setenvtable {} --[[ ================= ]]-- - - local function X (decl, fn) return argscheck ("std.object." .. decl, fn) end +--- Methods +-- @section methods + +local methods = { + --- Return a clone of this object and its metatable. + -- + -- This function is useful if you need to override the normal use of + -- the `__call` metamethod for object cloning, without losing the + -- ability to clone an object. + -- @function prototype:clone + -- @param ... arguments to prototype's *\_init*, often a single table + -- @treturn prototype a clone of this object, with shared or merged + -- metatable as appropriate + -- @see std.container.__call + -- @usage + -- local Node = Object { _type = "Node" } + -- -- A trivial FSA to recognize powers of 10, either "0" or a "1" + -- -- followed by zero or more "0"s can transition to state 'finish' + -- local states; states = { + -- start = Node { ["1"] = states[1], ["0"] = states.finish }, + -- [1] = Node { ["0"] = states[1], [""] = states.finish }, + -- finish = Node {}, + -- } + clone = getmetamethod (Container, "__call"), + + --- Type of this object. + -- @function prototype:type + -- @treturn string type of this object. + -- @see std.type + -- @usage + -- assert (Object:type () == getmetatable (Object)._type) + type = X ("type (?any)", type), + + + --- Object Functions + -- @section objfunctions + + --- Return *new* with references to the fields of *src* merged in. + -- + -- You can change the value of this function in an object, and that + -- new function will be called during cloning instead of the + -- standard @{std.container.mapfields} implementation. + -- @function prototype.mapfields + -- @tparam table new partially instantiated clone container + -- @tparam table src @{clone} argument table that triggered cloning + -- @tparam[opt={}] table map key renaming specification in the form + -- `{old_key=new_key, ...}` + -- @treturn table merged public fields from *new* and *src*, with a + -- metatable of private fields (if any), both renamed according to + -- *map* + -- @see std.container.mapfields + mapfields = X ("mapfields (table, table|object, ?table)", mapfields), +} + + +if deprecated then + methods = merge (methods, deprecated.object) +end + + --- Object prototype. -- @object prototype -- @string[opt="Object"] _type object name @@ -77,67 +136,17 @@ local prototype = Container { -- @usage -- for k, v in std.pairs (anobject) do process (k, v) end - __index = { - --- Methods - -- @section methods - - --- Return a clone of this object and its metatable. - -- - -- This function is useful if you need to override the normal use of - -- the `__call` metamethod for object cloning, without losing the - -- ability to clone an object. - -- @function prototype:clone - -- @param ... arguments to prototype's *\_init*, often a single table - -- @treturn prototype a clone of this object, with shared or merged - -- metatable as appropriate - -- @see std.container.__call - -- @usage - -- local Node = Object { _type = "Node" } - -- -- A trivial FSA to recognize powers of 10, either "0" or a "1" - -- -- followed by zero or more "0"s can transition to state 'finish' - -- local states; states = { - -- start = Node { ["1"] = states[1], ["0"] = states.finish }, - -- [1] = Node { ["0"] = states[1], [""] = states.finish }, - -- finish = Node {}, - -- } - clone = getmetamethod (Container, "__call"), - - --- Type of this object. - -- @function prototype:type - -- @treturn string type of this object. - -- @see std.type - -- @usage - -- assert (Object:type () == getmetatable (Object)._type) - type = X ("type (?any)", type), - - - --- Object Functions - -- @section objfunctions - - --- Return *new* with references to the fields of *src* merged in. - -- - -- You can change the value of this function in an object, and that - -- new function will be called during cloning instead of the - -- standard @{std.container.mapfields} implementation. - -- @function prototype.mapfields - -- @tparam table new partially instantiated clone container - -- @tparam table src @{clone} argument table that triggered cloning - -- @tparam[opt={}] table map key renaming specification in the form - -- `{old_key=new_key, ...}` - -- @treturn table merged public fields from *new* and *src*, with a - -- metatable of private fields (if any), both renamed according to - -- *map* - -- @see std.container.mapfields - mapfields = X ("mapfields (table, table|object, ?table)", mapfields), - - -- Backwards compatibility: - prototype = DEPRECATED ("41.3", "'std.object.prototype'", type), - }, + __index = methods, } -return Module { +local M = { prototype = prototype, - - type = DEPRECATED ("41.3", "'std.object.type'", type), } + +if deprecated then + M = merge (M, deprecated.object) +end + + +return Module (M) diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index f711588..ebe4099 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -35,14 +35,15 @@ local table_concat = table.concat local std = require "std.base" local debug = require "std.debug" +local deprecated = require "std.delete-after.2016-01-31" local Module = std.object.Module local Object = require "std.object".prototype -local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local ielems = std.ielems local insert = std.table.insert +local merge = std.base.merge local _ENV = std.base.setenvtable {} @@ -74,20 +75,7 @@ local function X (decl, fn) return argscheck ("std.strbuf." .. decl, fn) end ---- StrBuf prototype object. --- @object prototype --- @string[opt="StrBuf"] _type object name --- @see std.object.prototype --- @usage --- local StrBuf = require "std.strbuf".prototype --- local a = StrBuf {1, 2, 3} --- local b = StrBuf {a, "five", "six"} --- a = a .. 4 --- b = b:concat "seven" --- print (a, b) --> 1234 1234fivesixseven --- os.exit (0) - -local M = { +local methods = { --- Methods -- @section methods @@ -103,52 +91,61 @@ local M = { concat = X ("concat (StrBuf, any)", __concat), } +if deprecated then + methods = merge (methods, deprecated.strbuf) +end ---[[ ============= ]]-- ---[[ Deprecations. ]]-- ---[[ ============= ]]-- - - -M.tostring = DEPRECATED ("41.1", "std.strbuf.tostring", - "use 'tostring (strbuf)' instead", - X ("tostring (StrBuf)", __tostring)) - --[[ ================== ]]-- --[[ Type Declarations. ]]-- --[[ ================== ]]-- -local prototype = Object { - _type = "std.strbuf.StrBuf", - - --- Metamethods - -- @section metamethods - - __index = M, - - --- Support concatenation to StrBuf objects. - -- @function prototype:__concat - -- @param x a string, or object that can be coerced to a string - -- @treturn prototype modified *buf* - -- @see concat - -- @usage - -- buf = buf .. x - __concat = __concat, +--- StrBuf prototype object. +-- @object prototype +-- @string[opt="StrBuf"] _type object name +-- @see std.object.prototype +-- @usage +-- local StrBuf = require "std.strbuf".prototype +-- local a = StrBuf {1, 2, 3} +-- local b = StrBuf {a, "five", "six"} +-- a = a .. 4 +-- b = b:concat "seven" +-- print (a, b) --> 1234 1234fivesixseven +-- os.exit (0) - --- Support fast conversion to Lua string. - -- @function prototype:__tostring - -- @treturn string concatenation of buffer contents - -- @see tostring - -- @usage - -- str = tostring (buf) - __tostring = __tostring, +local M = { + prototype = Object { + _type = "std.strbuf.StrBuf", + + --- Metamethods + -- @section metamethods + + __index = methods, + + --- Support concatenation to StrBuf objects. + -- @function prototype:__concat + -- @param x a string, or object that can be coerced to a string + -- @treturn prototype modified *buf* + -- @see concat + -- @usage + -- buf = buf .. x + __concat = __concat, + + --- Support fast conversion to Lua string. + -- @function prototype:__tostring + -- @treturn string concatenation of buffer contents + -- @see tostring + -- @usage + -- str = tostring (buf) + __tostring = __tostring, + }, } +if deprecated then + M = merge (M, deprecated.strbuf) +end -return Module { - prototype = prototype, - tostring = M.tostring, -} +return Module (M) diff --git a/lib/std/string.lua b/lib/std/string.lua index 36ea649..44451a6 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -23,13 +23,13 @@ local math_abs = math.abs local math_floor = math.floor -local std = require "std.base" local debug = require "std.debug" +local deprecated = require "std.delete-after.2016-01-03" local maturity = require "std.maturity" +local std = require "std.base" local StrBuf = require "std.strbuf".prototype -local DEPRECATED = maturity.DEPRECATED local DEPRECATIONMSG = maturity.DEPRECATIONMSG local argscheck = debug.argscheck local callable = std.functional.callable @@ -513,23 +513,9 @@ M = { } - ---[[ ============= ]]-- ---[[ Deprecations. ]]-- ---[[ ============= ]]-- - - -M.assert = DEPRECATED ("41", "'std.string.assert'", - "use 'std.assert' instead", std.assert) - - -M.require_version = DEPRECATED ("41", "'std.string.require_version'", - "use 'std.require' instead", std.require) - - -M.tostring = DEPRECATED ("41", "'std.string.tostring'", - "use 'std.tostring' instead", std.tostring) - +if deprecated then + M = merge (M, deprecated.string) +end return merge (M, string) diff --git a/lib/std/table.lua b/lib/std/table.lua index 78f8630..f78b6da 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -21,10 +21,10 @@ local math_ceil = math.ceil local math_min = math.min -local std = require "std.base" local debug = require "std.debug" +local deprecated = require "std.delete-after.a-year" +local std = require "std.base" -local DEPRECATED = require "std.maturity".DEPRECATED local argscheck = debug.argscheck local argerror = std.debug.argerror local collect = std.functional.collect @@ -490,50 +490,9 @@ M = { monkeys = copy ({}, M) -- before deprecations and core merge ---[[ ============= ]]-- ---[[ Deprecations. ]]-- ---[[ ============= ]]-- - - - -M.len = DEPRECATED ("41.3", "'std.table.len'", - "use 'std.operator.len' instead", X ("len (table)", std.operator.len)) - - -M.metamethod = DEPRECATED ("41", "'std.table.metamethod'", - "use 'std.getmetamethod' instead", std.getmetamethod) - - -M.okeys = DEPRECATED ("41.3", "'std.table.okeys'", - "compose 'std.table.keys' and 'std.table.sort' instead", - X ("okeys (table)", function (t) - local r = {} - for k in pairs (t) do r[#r + 1] = k end - return std.base.sortkeys (r) - end)) - - -M.ripairs = DEPRECATED ("41", "'std.table.ripairs'", - "use 'std.ripairs' instead", std.ripairs) - - -M.totable = DEPRECATED ("41", "'std.table.totable'", - "use 'std.pairs' instead", - function (x) - local m = std.getmetamethod (x, "__totable") - if m then - return m (x) - elseif type (x) == "table" then - return x - elseif type (x) == "string" then - local t = {} - x:gsub (".", function (c) t[#t + 1] = c end) - return t - else - return nil - end - end) - +if deprecated then + M = merge (M, deprecated.table) +end return merge (M, table) diff --git a/local.mk b/local.mk index b00d490..c85e826 100644 --- a/local.mk +++ b/local.mk @@ -89,6 +89,15 @@ dist_luastd_DATA = \ lib/std/tuple.lua \ $(NOTHING_ELSE) +luastddeletedir = $(luastddir)/delete-after + +dist_luastddelete_DATA = \ + lib/std/delete-after/2016-01-03.lua \ + lib/std/delete-after/2016-01-31.lua \ + lib/std/delete-after/2016-03-08.lua \ + lib/std/delete-after/a-year.lua \ + $(NOTHING_ELSE) + # For bugwards compatibility with LuaRocks 2.1, while ensuring that # `require "std.debug_init"` continues to work, we have to install # the former `$(luadir)/std/debug_init.lua` to `debug_init/init.lua`. diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 5b993b8..61376eb 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -277,9 +277,6 @@ specify std.table: expect (deprecate_on ("len", "{}")).to_contain_error "was deprecated" expect (deprecate_off ("len", "{}")).not_to_contain_error "was deprecated" - - context with bad arguments: - badargs.diagnose (f, "std.table.len (table)") - - it returns the length of a table: expect (f {"a", "b", "c"}).to_be (3) expect (f {1, 2, 5, a=10, 3}).to_be (4) @@ -523,9 +520,6 @@ specify std.table: expect (deprecate_off ("okeys", "{}")). not_to_contain_error "was deprecated" - - context with bad arguments: - badargs.diagnose (f, "std.table.okeys (table)") - - it returns an empty list when subject is empty: expect (f {}).to_equal {} - it makes an ordered list of table keys: From b59070f6a87507fdb3c8a68cc765f99fe6215570 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 27 Sep 2015 19:17:49 +0100 Subject: [PATCH 607/703] refactor: move setenvtable from std.base to std.strict. * lib/std/base.lua (setenvtable): Move from here... * lib/std/strict.lua (setenvtable): ...to here. Adjust all callers. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 9 +++ lib/std.lua.in | 2 +- lib/std/base.lua | 11 +-- lib/std/container.lua | 2 +- lib/std/debug.lua | 2 +- lib/std/debug_init/init.lua | 4 +- lib/std/delete-after/2016-01-03.lua | 3 +- lib/std/delete-after/2016-01-31.lua | 3 +- lib/std/delete-after/2016-03-08.lua | 3 +- lib/std/delete-after/a-year.lua | 3 +- lib/std/functional.lua | 2 +- lib/std/io.lua | 2 +- lib/std/list.lua | 2 +- lib/std/math.lua | 2 +- lib/std/maturity.lua | 2 +- lib/std/object.lua | 2 +- lib/std/operator.lua | 2 +- lib/std/optparse.lua | 2 +- lib/std/package.lua | 2 +- lib/std/set.lua | 2 +- lib/std/strbuf.lua | 2 +- lib/std/strict.lua | 115 ++++++++++++++++++---------- lib/std/string.lua | 2 +- lib/std/table.lua | 2 +- lib/std/tree.lua | 2 +- lib/std/tuple.lua | 2 +- 26 files changed, 110 insertions(+), 77 deletions(-) diff --git a/NEWS.md b/NEWS.md index a5c29ad..6c7c3a9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -21,6 +21,15 @@ if rawget (_G, "setfenv") then setfenv (1, _ENV) end ``` + - New `std.strict.setenvtable` encapsulates the above for portably + assigning an environment table at the module level, but enforcing + declaration strictness only when `_DEBUG.strict` (or equivalent) + is set: + + ```lua + local _ENV = strict.setenvtable {} + ``` + - All support for deprecated APIs has been moved out of the module sources, so that it needn't be loaded in production code that has been properly updated not to use deprecated calls, i.e: diff --git a/lib/std.lua.in b/lib/std.lua.in index 240d513..b79d0a8 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -33,7 +33,7 @@ local type = type local argscheck = require "std.debug".argscheck local std = require "std.base" -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/base.lua b/lib/std/base.lua index 2ad49b4..4a32f43 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -51,15 +51,7 @@ local table_maxn = table.maxn local table_sort = table.sort local table_unpack = table.unpack or unpack -local function setenvtable (env) - if require "std.debug_init"._DEBUG.strict then - env = require "std.strict" (env) - end - setfenv (2, env) - return env -end - -local _ENV = setenvtable {} +local _ENV = require "std.strict".setenvtable {} @@ -682,7 +674,6 @@ return { merge = merge, mnemonic = mnemonic, raise = raise, - setenvtable = setenvtable, sortkeys = sortkeys, toqstring = toqstring, }, diff --git a/lib/std/container.lua b/lib/std/container.lua index 52a558b..8abb828 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -53,7 +53,7 @@ local pickle = std.string.pickle local render = std.string.render local tostring = std.tostring -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 31ce4b1..a1d3475 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -89,7 +89,7 @@ local unpack = std.table.unpack local _DEBUG = require "std.debug_init"._DEBUG -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index 670450b..a99a33d 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -1,6 +1,4 @@ --- Strict mode is always enabled in this module. - -local _ENV = require "std.strict" { +local _ENV = { _G = _G, math_huge = math.huge, rawget = rawget, diff --git a/lib/std/delete-after/2016-01-03.lua b/lib/std/delete-after/2016-01-03.lua index 984129a..a7e40d5 100644 --- a/lib/std/delete-after/2016-01-03.lua +++ b/lib/std/delete-after/2016-01-03.lua @@ -36,6 +36,7 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", std = require "std.base", + strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -59,7 +60,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local std_tostring = _.std.tostring -- Only the above symbols are used below this line. - local _, _ENV = nil, _.std.base.setenvtable {} + local _, _ENV = nil, _.strict.setenvtable {} --[[ ========== ]]-- diff --git a/lib/std/delete-after/2016-01-31.lua b/lib/std/delete-after/2016-01-31.lua index 6a41e26..ab0bb73 100644 --- a/lib/std/delete-after/2016-01-31.lua +++ b/lib/std/delete-after/2016-01-31.lua @@ -32,6 +32,7 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", std = require "std.base", + strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -43,7 +44,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local std_ipairs = _.std.ipairs -- Only the above symbols are used below this line. - local _, _ENV = nil, _.std.base.setenvtable {} + local _, _ENV = nil, _.strict.setenvtable {} --[[ ========== ]]-- diff --git a/lib/std/delete-after/2016-03-08.lua b/lib/std/delete-after/2016-03-08.lua index d92e99c..5cb8931 100644 --- a/lib/std/delete-after/2016-03-08.lua +++ b/lib/std/delete-after/2016-03-08.lua @@ -31,6 +31,7 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", std = require "std.base", + strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -41,7 +42,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local DEPRECATED = _.maturity.DEPRECATED -- Only the above symbols are used below this line. - local _, _ENV = nil, _.std.base.setenvtable {} + local _, _ENV = nil, _.strict.setenvtable {} --[[ ========== ]]-- diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 56d7ce9..ce9ee85 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -30,6 +30,7 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", std = require "std.base", + strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -44,7 +45,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local std_type = _.std.type -- Only the above symbols are used below this line. - local _, _ENV = nil, _.std.base.setenvtable {} + local _, _ENV = nil, _.strict.setenvtable {} --[[ ========== ]]-- diff --git a/lib/std/functional.lua b/lib/std/functional.lua index e0e500f..0f6f6bc 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -37,7 +37,7 @@ local reduce = std.functional.reduce local render = std.string.render local unpack = std.table.unpack -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/io.lua b/lib/std/io.lua index 9a56ef3..ef9e923 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -48,7 +48,7 @@ local pairs = std.pairs local split = std.string.split local tostring = std.tostring -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/list.lua b/lib/std/list.lua index 7bea01d..2bd68f3 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -36,7 +36,7 @@ local merge = std.base.merge local pairs = std.pairs local unpack = std.table.unpack -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/math.lua b/lib/std/math.lua index 94de65f..30693f1 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -21,7 +21,7 @@ local argscheck = require "std.debug".argscheck local copy = std.base.copy local merge = std.base.merge -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua index 50a4799..89c0117 100644 --- a/lib/std/maturity.lua +++ b/lib/std/maturity.lua @@ -34,7 +34,7 @@ local string_format = string.format local _DEBUG = require "std.debug_init"._DEBUG -local _ENV = require "std.base".base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} local function DEPRECATIONMSG (version, name, extramsg, level) diff --git a/lib/std/object.lua b/lib/std/object.lua index 26fe3ee..df57383 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -35,7 +35,7 @@ local mapfields = std.object.mapfields local merge = std.base.merge local type = std.type -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 6b54322..b36181f 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -14,7 +14,7 @@ local len = std.operator.len local serialize = std.base.mnemonic local tostring = std.tostring -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 0f685b9..7a49344 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -46,7 +46,7 @@ local last = std.base.last local len = std.operator.len local pairs = std.pairs -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/package.lua b/lib/std/package.lua index 7e74523..aa68ac4 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -57,7 +57,7 @@ local pairs = std.pairs local split = std.string.split local unpack = std.table.unpack -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/set.lua b/lib/std/set.lua index b31c662..3550dbc 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -44,7 +44,7 @@ local pickle = std.string.pickle local tostring = std.tostring local std_type = std.type -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index ebe4099..0e5df42 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -45,7 +45,7 @@ local ielems = std.ielems local insert = std.table.insert local merge = std.base.merge -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/strict.lua b/lib/std/strict.lua index 2f832fb..18297bc 100644 --- a/lib/std/strict.lua +++ b/lib/std/strict.lua @@ -17,17 +17,27 @@ the `if` statement, because on Lua >= 5.2, it doesn't exist and would trigger an undeclared variable error! + Or, to set an empty strictness enforcing environment table for a module + (after caching all symbols used after this invocation): + + local _ENV = strict.setenvtable {} + + The implementation calls `setfenv` appropriately in Lua 5.1 interpreters + to provide the same semantics. + @module std.strict ]] -local _ENV = { - debug_getinfo = debug.getinfo, - error = error, - rawset = rawset, - rawget = rawget, - setfenv = setfenv or function () end, - setmetatable = setmetatable, -} +local debug_getinfo = debug.getinfo +local error = error +local rawset = rawset +local rawget = rawget +local setfenv = setfenv or function () end +local setmetatable = setmetatable + +local _DEBUG = require "std.debug_init"._DEBUG + +local _ENV = {} setfenv (1, _ENV) @@ -39,40 +49,61 @@ local function what () end -return setmetatable ({}, { - __call = function (self, env) - -- The set of globally declared variables. - local declared = {} - - return setmetatable ({}, { - --- Detect dereference of undeclared global. - -- @function __index - -- @string n name of the variable being dereferenced - __index = function (_, n) - local v = env[n] - if v ~= nil then - declared[n] = true - elseif not declared[n] and what () ~= "C" then - error ("variable '" .. n .. "' is not declared", 2) - end - return v - end, - - --- Detect assignment to undeclared global. - -- @function __newindex - -- @string n name of the variable being declared - -- @param v initial value of the variable - __newindex = function (_, n, v) - local x = env[n] - if x == nil and not declared[n] then - local w = what () - if w ~= "main" and w ~= "C" then - error ("assignment to undeclared variable '" .. n .. "'", 2) - end - end +local function restrict (env) + -- The set of declared variables in this scope. + local declared = {} + + --- Metamethods + -- @section metamethods + + return setmetatable ({}, { + --- Detect dereference of undeclared global. + -- @function __index + -- @string n name of the variable being dereferenced + __index = function (_, n) + local v = env[n] + if v ~= nil then declared[n] = true - env[n] = v - end, - }) + elseif not declared[n] and what () ~= "C" then + error ("variable '" .. n .. "' is not declared", 2) + end + return v + end, + + --- Detect assignment to undeclared global. + -- @function __newindex + -- @string n name of the variable being declared + -- @param v initial value of the variable + __newindex = function (_, n, v) + local x = env[n] + if x == nil and not declared[n] then + local w = what () + if w ~= "main" and w ~= "C" then + error ("assignment to undeclared variable '" .. n .. "'", 2) + end + end + declared[n] = true + env[n] = v + end, + }) +end + + +return setmetatable ({ + --- Functions + -- @section functions + + --- Enforce strict variable declaration in *env* according to `_DEBUG`. + -- @function setenvtable + -- @tparam table env lexical environment table + -- @treturn table *env* which must be assigned to `_ENV` + setenvtable = function (env) + if _DEBUG.strict then + env = restrict (env) + end + setfenv (2, env) + return env end, +}, { + __call = function (_, ...) return restrict (...) end, }) diff --git a/lib/std/string.lua b/lib/std/string.lua index 44451a6..fc708d4 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -48,7 +48,7 @@ local split = std.string.split local std_tostring = std.tostring local toqstring = std.base.toqstring -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/table.lua b/lib/std/table.lua index f78b6da..72e09a7 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -39,7 +39,7 @@ local merge = std.base.merge local pairs = std.pairs local unpack = std.table.unpack -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 53971cb..4c472c7 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -51,7 +51,7 @@ local pairs = std.pairs local reduce = std.functional.reduce local std_type = std.type -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 0d88508..df5d03c 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -47,7 +47,7 @@ local pickle = std.string.pickle local std_type = std.type local toqstring = std.base.toqstring -local _ENV = std.base.setenvtable {} +local _ENV = require "std.strict".setenvtable {} From bfa874c4a418c52ac1fdef64e50483e734454769 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 27 Sep 2015 19:30:16 +0100 Subject: [PATCH 608/703] maint: remove unused setfenv local. * lib/std/base.lua (setfenv): Remove unused local. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 4a32f43..4916445 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -32,7 +32,6 @@ local pairs = pairs local rawget = rawget local require = require local select = select -local setfenv = setfenv or function () end local setmetatable = setmetatable local tonumber = tonumber local tostring = tostring From 102b69296608ad4f85d0b4a411c1e64f2b2fe3d6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 27 Sep 2015 23:23:52 +0100 Subject: [PATCH 609/703] refactor: tighten up imported symbol visibility in all modules. * lib/std.lua.in, lib/std/base.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/delete-after/2016-01-03.lua, lib/std/delete-after/2016-01-31.lua, lib/std/delete-after/2016-03-08.lua, lib/std/delete-after/a-year.lua, lib/std/functional.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/maturity.lua, lib/std/object.lua, lib/std/operator.lua, lib/std/optparse.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua, lib/std/tuple.lua (_): Temporary symbols used only while setting up local variable cache. Use setenvtable to close off the module environment and then set `_` to nil. Avoid leaving access to entire tables of required dependent modules and especially `require` itself as far as possible. Fix a handful of violations discovered by being strict like this. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 58 +++++++++++------ lib/std/base.lua | 1 + lib/std/container.lua | 36 ++++++----- lib/std/debug.lua | 98 +++++++++++++++-------------- lib/std/delete-after/2016-01-03.lua | 44 ++++++------- lib/std/delete-after/2016-01-31.lua | 14 ++--- lib/std/delete-after/2016-03-08.lua | 4 +- lib/std/delete-after/a-year.lua | 14 ++--- lib/std/functional.lua | 53 +++++++++------- lib/std/io.lua | 44 +++++++------ lib/std/list.lua | 36 ++++++----- lib/std/math.lua | 16 +++-- lib/std/maturity.lua | 17 ++++- lib/std/object.lua | 30 +++++---- lib/std/operator.lua | 16 +++-- lib/std/optparse.lua | 31 +++++---- lib/std/package.lua | 26 ++++---- lib/std/set.lua | 36 ++++++----- lib/std/strbuf.lua | 26 +++++--- lib/std/string.lua | 50 ++++++++------- lib/std/table.lua | 61 +++++++++--------- lib/std/tree.lua | 55 +++++++++------- lib/std/tuple.lua | 21 ++++--- 23 files changed, 450 insertions(+), 337 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index b79d0a8..b35c0c3 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -30,10 +30,30 @@ local setmetatable = setmetatable local type = type -local argscheck = require "std.debug".argscheck -local std = require "std.base" +local _ = { + debug = require "std.debug", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local _assert = _.std.assert +local _ipairs = _.std.ipairs +local _pairs = _.std.pairs +local _require = _.std.require +local _tostring = _.std.tostring +local _type = _.std.type +local argscheck = _.debug.argscheck +local copy = _.std.base.copy +local elems = _.std.elems +local eval = _.std.eval +local getmetamethod = _.std.getmetamethod +local ielems = _.std.ielems +local ireverse = _.std.ireverse +local npairs = _.std.npairs +local ripairs = _.std.ripairs +local rnpairs = _.std.rnpairs -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} @@ -46,7 +66,7 @@ local M, monkeys local function monkey_patch (namespace) - std.base.copy (namespace or _G, monkeys) + copy (namespace or _G, monkeys) return M end @@ -115,7 +135,7 @@ M = { -- @usage -- std.assert (expect == nil, "100% unexpected!") -- std.assert (expect == "expect", "%s the unexpected!", expect) - assert = X ("assert (?any, ?string, [any...])", std.assert), + assert = X ("assert (?any, ?string, [any...])", _assert), --- Evaluate a string as Lua code. -- @function eval @@ -124,7 +144,7 @@ M = { -- @usage -- --> 2 -- std.eval "math.min (2, 10)" - eval = X ("eval (string)", std.eval), + 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 @@ -137,7 +157,7 @@ M = { -- @treturn callable|nil callable metamethod, or `nil` if no metamethod -- @usage -- clone = std.getmetamethod (std.object.prototype, "__call") - getmetamethod = X ("getmetamethod (?any, string)", std.getmetamethod), + getmetamethod = X ("getmetamethod (?any, string)", getmetamethod), --- Enhance core `tostring` to render table contents as a string. -- @function tostring @@ -146,14 +166,14 @@ M = { -- @usage -- -- {1=baz,foo=bar} -- print (std.tostring {foo="bar","baz"}) - tostring = X ("tostring (?any)", std.tostring), + tostring = X ("tostring (?any)", _tostring), --- Type of an object, or primitive. -- @function type -- @param x anything -- @treturn string type of *x* -- @see std.object.type - type = X ("type (?any)", std.type), + type = X ("type (?any)", _type), --- Module Functions @@ -194,7 +214,7 @@ M = { -- @usage -- -- posix.version == "posix library for Lua 5.2 / 32" -- posix = require ("posix", "29") - require = X ("require (string, ?string, ?string, ?string)", std.require), + require = X ("require (string, ?string, ?string, ?string)", _require), --- Iterator Functions -- @section iteratorfuncs @@ -214,7 +234,7 @@ M = { -- --> baz -- --> 5 -- std.functional.map (print, std.ielems, {"foo", "bar", [4]="baz", d=5}) - elems = X ("elems (table)", std.elems), + elems = X ("elems (table)", elems), --- An iterator over the integer keyed elements of a table. -- @@ -233,7 +253,7 @@ M = { -- --> foo -- --> bar -- std.functional.map (print, std.ielems, {"foo", "bar", [4]="baz", d=5}) - ielems = X ("ielems (table)", std.ielems), + ielems = X ("ielems (table)", ielems), --- An iterator over integer keyed pairs of a sequence. -- @@ -257,7 +277,7 @@ M = { -- --> 1 foo -- --> 2 bar -- std.functional.map (print, std.ipairs, {"foo", "bar", [4]="baz", d=5}) - ipairs = X ("ipairs (table)", std.ipairs), + ipairs = X ("ipairs (table)", _ipairs), --- Return a new sequence with element order reversed. -- @@ -274,7 +294,7 @@ M = { -- --> bar -- --> foo -- std.functional.map (print, rielems, {"foo", "bar", [4]="baz", d=5}) - ireverse = X ("ireverse (table)", std.ireverse), + ireverse = X ("ireverse (table)", ireverse), --- Ordered iterator for integer keyed values. -- Like ipairs, but does not stop until the __len or maxn of *t*. @@ -290,7 +310,7 @@ M = { -- --> 3 nil -- --> 4 baz -- std.functional.map (print, std.npairs, {"foo", "bar", [4]="baz", d=5}) - npairs = X ("npairs (table)", std.npairs), + npairs = X ("npairs (table)", npairs), --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. -- @function pairs @@ -306,7 +326,7 @@ M = { -- --> 4 baz -- --> d 5 -- std.functional.map (print, std.pairs, {"foo", "bar", [4]="baz", d=5}) - pairs = X ("pairs (table)", std.pairs), + pairs = X ("pairs (table)", _pairs), --- An iterator like ipairs, but in reverse. -- Apart from the order of the elements returned, this function follows @@ -322,7 +342,7 @@ M = { -- --> 2 bar -- --> 1 foo -- std.functional.map (print, std.ripairs, {"foo", "bar", [4]="baz", d=5}) - ripairs = X ("ripairs (table)", std.ripairs), + ripairs = X ("ripairs (table)", ripairs), --- An iterator like npairs, but in reverse. -- Apart from the order of the elements returned, this function follows @@ -339,11 +359,11 @@ M = { -- --> 2 bar -- --> 1 foo -- std.functional.map (print, std.rnpairs, {"foo", "bar", [4]="baz", d=5}) - rnpairs = X ("rnpairs (table)", std.rnpairs), + rnpairs = X ("rnpairs (table)", rnpairs), } -monkeys = std.base.copy ({}, M) +monkeys = copy ({}, M) -- Don't monkey_patch these apis into _G! for _, api in ipairs {"barrel", "monkey_patch", "version"} do diff --git a/lib/std/base.lua b/lib/std/base.lua index 4916445..5f10468 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -50,6 +50,7 @@ local table_maxn = table.maxn local table_sort = table.sort local table_unpack = table.unpack or unpack + local _ENV = require "std.strict".setenvtable {} diff --git a/lib/std/container.lua b/lib/std/container.lua index 8abb828..859d975 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -28,7 +28,6 @@ @prototype std.container ]] - local getmetatable = getmetatable local next = next local select = select @@ -40,20 +39,27 @@ local string_sub = string.sub local table_concat = table.concat -local _DEBUG = require "std.debug_init"._DEBUG -local std = require "std.base" -local debug = require "std.debug" +local _ = { + debug = require "std.debug", + debug_init = require "std.debug_init", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} -local Module = std.object.Module +local Module = _.std.object.Module -local copy = std.base.copy -local ipairs = std.ipairs -local mapfields = std.object.mapfields -local pickle = std.string.pickle -local render = std.string.render -local tostring = std.tostring +local _DEBUG = _.debug_init._DEBUG +local argcheck = _.debug.argcheck +local argscheck = _.debug.argscheck +local argerror = _.debug.argerror +local copy = _.std.base.copy +local extramsg_toomany = _.debug.extramsg_toomany +local mapfields = _.std.object.mapfields +local pickle = _.std.string.pickle +local render = _.std.string.render +local sortkeys = _.std.base.sortkeys -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} @@ -103,7 +109,7 @@ local tostring_vtable = { return ", " end, - sort = std.base.sortkeys, + sort = sortkeys, } @@ -276,8 +282,6 @@ local prototype = { if _DEBUG.argcheck then - local argcheck, argerror, extramsg_toomany = - debug.argcheck, debug.argerror, debug.extramsg_toomany local __call = prototype.__call prototype.__call = function (self, ...) @@ -336,6 +340,6 @@ return Module { -- } -- local groceries = Bag ("apple", "banana", "banana") -- local purse = Bag {_type = "Purse"} ("cards", "cash", "id") - mapfields = debug.argscheck ( + mapfields = argscheck ( "std.container.mapfields (table, table|object, ?table)", mapfields), } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index a1d3475..60f6c7b 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -48,25 +48,42 @@ local table_remove = table.remove local table_sort = table.sort +local _ = { + debug_init = require "std.debug_init", + std = require "std.base", + setenvtable = require "std.strict".setenvtable, +} + +local _DEBUG = _.debug_init._DEBUG +local _ipairs = _.std.ipairs +local _pairs = _.std.pairs +local _tostring = _.std.tostring +local _type = _.std.type +local argerror = _.std.debug.argerror +local copy = _.std.base.copy +local insert = _.std.table.insert +local last = _.std.base.last +local len = _.std.operator.len +local maxn = _.std.table.maxn +local merge = _.std.base.merge +local nop = _.std.functional.nop +local raise = _.std.base.raise +local split = _.std.string.split +local unpack = _.std.table.unpack + + local deprecated = require "std.delete-after.2016-03-08" -local std = require "std.base" - -local argerror = std.debug.argerror -local copy = std.base.copy -local insert = std.table.insert -local ipairs = std.ipairs -local last = std.base.last -local len = std.operator.len -local maxn = std.table.maxn -local merge = std.base.merge -local nop = std.functional.nop -local pairs = std.pairs -local raise = std.base.raise -local split = std.string.split -local stdtype = std.type -local tostring = std.tostring -local unpack = std.table.unpack +local _, _ENV = nil, _.setenvtable {} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M --- Control std.debug function behaviour. @@ -86,19 +103,6 @@ local unpack = std.table.unpack -- before use **in stdlib internals** -- @see std.strict -- @usage _DEBUG = { argcheck = false, level = 9, strict = false } -local _DEBUG = require "std.debug_init"._DEBUG - - -local _ENV = require "std.strict".setenvtable {} - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - -local M local function setfenv (fn, env) @@ -190,7 +194,7 @@ local function permute (t) if t[#t] then t[#t] = t[#t]:gsub ("%]%.%.%.$", "...]") end local p = {{}} - for i, v in ipairs (t) do + for i, v in _ipairs (t) do local optional = v:match "%[(.+)%]" if optional == nil then @@ -217,7 +221,7 @@ local function typesplit (types) types = split (types:gsub ("%s+or%s+", "|"), "%s*|%s*") end local r, seen, add_nil = {}, {}, false - for _, v in ipairs (types) do + for _, v in _ipairs (types) do local m = v:match "^%?(.+)$" if m then add_nil, v = true, m @@ -237,14 +241,14 @@ end local function projectuniq (fkey, tt) -- project local t = {} - for _, u in ipairs (tt) do + for _, u in _ipairs (tt) do t[#t + 1] = u[fkey] end -- split and remove duplicates local r, s = {}, {} - for _, e in ipairs (t) do - for _, v in ipairs (typesplit (e)) do + for _, e in _ipairs (t) do + for _, v in _ipairs (typesplit (e)) do if s[v] == nil then r[#r + 1], s[v] = v, true end @@ -280,7 +284,7 @@ end local function extramsg_mismatch (expectedtypes, actual, index) - local actualtype = stdtype (actual) + local actualtype = _type (actual) -- Tidy up actual type for display. if actualtype == "nil" then @@ -297,14 +301,14 @@ local function extramsg_mismatch (expectedtypes, actual, index) end if index then - actualtype = actualtype .. " at index " .. tostring (index) + actualtype = actualtype .. " at index " .. _tostring (index) end -- Tidy up expected types for display. local expectedstr = expectedtypes if type (expectedtypes) == "table" then local t = {} - for i, v in ipairs (expectedtypes) do + for i, v in _ipairs (expectedtypes) do if v == "func" then t[i] = "function" elseif v == "bool" then @@ -391,7 +395,7 @@ if _DEBUG.argcheck then end end - actualtype = stdtype (actual) + actualtype = _type (actual) if check == actualtype then return true elseif check == "list" or check == "#list" then @@ -428,7 +432,7 @@ if _DEBUG.argcheck then local permutations = argt.permutations local bestmismatch, t = 0 - for i, typelist in ipairs (permutations) do + for i, typelist in _ipairs (permutations) do local mismatch = match (typelist, valuelist) if mismatch == nil then bestmismatch, t = nil, nil @@ -455,7 +459,7 @@ if _DEBUG.argcheck then if typelist[i] then local check, contents = typelist[i]:match "^(%S+) of (%S-)s?$" if contents and type (valuelist[i]) == "table" then - for k, v in pairs (valuelist[i]) do + for k, v in _pairs (valuelist[i]) do if not checktype (contents, v) then argt.badtype (i, extramsg_mismatch (expected, v, k), 3) end @@ -482,7 +486,7 @@ if _DEBUG.argcheck then -- Check actual has one of the types from expected local ok = false - for _, expect in ipairs (expected) do + for _, expect in _ipairs (expected) do local check, contents = expect:match "^(%S+) of (%S-)s?$" check = check or expect @@ -491,7 +495,7 @@ if _DEBUG.argcheck then -- For "table of things", check all elements are a thing too. if ok and contents and type (actual) == "table" then - for k, v in pairs (actual) do + for k, v in _pairs (actual) do if not checktype (contents, v) then argerror (name, i, extramsg_mismatch (expected, v, k), level + 1) end @@ -534,9 +538,9 @@ if _DEBUG.argcheck then local returntypes = decl:match "=>%s*(.+)%s*$" if returntypes then local i, permutations = 0, {} - for _, group in ipairs (split (returntypes, "%s+or%s+")) do + for _, group in _ipairs (split (returntypes, "%s+or%s+")) do returntypes = split (group, ",%s*") - for _, t in ipairs (permute (returntypes)) do + for _, t in _ipairs (permute (returntypes)) do i = i + 1 permutations[i] = t end @@ -600,7 +604,7 @@ local function say (n, ...) ((type (_DEBUG.level) == "number" and _DEBUG.level >= level) or level <= 1) then local t = {} - for k, v in pairs (argt) do t[k] = tostring (v) end + for k, v in _pairs (argt) do t[k] = _tostring (v) end io_stderr:write (table_concat (t, "\t") .. "\n") end end @@ -860,7 +864,7 @@ M = { -- Private: _setdebug = function (t) - for k, v in pairs (t) do + for k, v in _pairs (t) do if v == "nil" then v = nil end _DEBUG[k] = v end diff --git a/lib/std/delete-after/2016-01-03.lua b/lib/std/delete-after/2016-01-03.lua index a7e40d5..918f187 100644 --- a/lib/std/delete-after/2016-01-03.lua +++ b/lib/std/delete-after/2016-01-03.lua @@ -32,11 +32,11 @@ if not require "std.debug_init"._DEBUG.deprecate then local math_max = math.max local table_unpack = table.unpack or unpack - local _, deprecated = { + local _, deprecated = { -- Adding anything else here will probably cause a require loop. - maturity = require "std.maturity", - std = require "std.base", - strict = require "std.strict", + maturity = require "std.maturity", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", } -- Merge in deprecated APIs from previous release if still available. @@ -48,19 +48,19 @@ if not require "std.debug_init"._DEBUG.deprecate then -- std.operator any time in the next year or so... local operator = require "std.operator" + local _assert = _.std.assert + local _ipairs = _.std.ipairs + local _pairs = _.std.pairs + local _require = _.std.require + local _tostring = _.std.tostring local DEPRECATED = _.maturity.DEPRECATED local eval = _.std.eval local ielems = _.std.ielems local ireverse = _.std.ireverse local ripairs = _.std.ripairs - local std_assert = _.std.assert - local std_ipairs = _.std.ipairs - local std_pairs = _.std.pairs - local std_require = _.std.require - local std_tostring = _.std.tostring -- Only the above symbols are used below this line. - local _, _ENV = nil, _.strict.setenvtable {} + local _, _ENV = nil, _.setenvtable {} --[[ ========== ]]-- @@ -87,7 +87,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function depair (proto, ls) local t = {} - for _, v in std_ipairs (ls) do + for _, v in _ipairs (ls) do t[v[1]] = v[2] end return t @@ -101,7 +101,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function enpair (proto, t) local ls = proto {} - for i, v in std_pairs (t) do + for i, v in _pairs (t) do ls[#ls + 1] = proto {i, v} end return ls @@ -110,7 +110,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function filter (proto, pfn, l) local r = proto {} - for _, e in std_ipairs (l) do + for _, e in _ipairs (l) do if pfn (e) then r[#r + 1] = e end @@ -135,7 +135,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function reduce (fn, d, ifn, ...) local argt = {...} if not callable (ifn) then - ifn, argt = std_pairs, {ifn, ...} + ifn, argt = _pairs, {ifn, ...} end local nextfn, state, k = ifn (table_unpack (argt)) @@ -173,7 +173,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function index_key (proto, f, l) local r = {} - for i, v in std_ipairs (l) do + for i, v in _ipairs (l) do local k = v[f] if k then r[k] = i @@ -185,7 +185,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function index_value (proto, f, l) local r = {} - for i, v in std_ipairs (l) do + for i, v in _ipairs (l) do local k = v[f] if k then r[k] = v @@ -211,7 +211,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function flatten (proto, l) local r = proto {} - for v in leaves (std_ipairs, l) do + for v in leaves (_ipairs, l) do r[#r + 1] = v end return r @@ -220,7 +220,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function map (proto, fn, l) local r = proto {} - for _, e in std_ipairs (l) do + for _, e in _ipairs (l) do local v = fn (e) if v ~= nil then r[#r + 1] = v @@ -251,7 +251,7 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Check the shape and calculate the size of the zero, if any local size = 1 local zero - for i, v in std_ipairs (s) do + for i, v in _ipairs (s) do if v == 0 then if zero then -- bad shape: two zeros return nil @@ -384,9 +384,9 @@ if not require "std.debug_init"._DEBUG.deprecate then }, string = { - assert = X ("string.assert", "std.assert", std_assert), - require_version = X ("string.require_version", "std.require", std_require), - tostring = X ("string.tostring", "std.tostring", std_tostring), + assert = X ("string.assert", "std.assert", _assert), + require_version = X ("string.require_version", "std.require", _require), + tostring = X ("string.tostring", "std.tostring", _tostring), }, table = { diff --git a/lib/std/delete-after/2016-01-31.lua b/lib/std/delete-after/2016-01-31.lua index ab0bb73..ec61abb 100644 --- a/lib/std/delete-after/2016-01-31.lua +++ b/lib/std/delete-after/2016-01-31.lua @@ -28,11 +28,11 @@ if not require "std.debug_init"._DEBUG.deprecate then local type = type local table_concat = table.concat - local _, deprecated = { + local _, deprecated = { -- Adding anything else here will probably cause a require loop. - maturity = require "std.maturity", - std = require "std.base", - strict = require "std.strict", + maturity = require "std.maturity", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", } -- Merge in deprecated APIs from previous release if still available. @@ -41,10 +41,10 @@ if not require "std.debug_init"._DEBUG.deprecate then local DEPRECATED = _.maturity.DEPRECATED - local std_ipairs = _.std.ipairs + local _ipairs = _.std.ipairs -- Only the above symbols are used below this line. - local _, _ENV = nil, _.strict.setenvtable {} + local _, _ENV = nil, _.setenvtable {} --[[ ========== ]]-- @@ -53,7 +53,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function strbuf_tostring (strbuf) local strs = {} - for _, v in std_ipairs (strbuf) do strs[#strs + 1] = tostring (v) end + for _, v in _ipairs (strbuf) do strs[#strs + 1] = tostring (v) end return table_concat (strs) end diff --git a/lib/std/delete-after/2016-03-08.lua b/lib/std/delete-after/2016-03-08.lua index 5cb8931..31064d3 100644 --- a/lib/std/delete-after/2016-03-08.lua +++ b/lib/std/delete-after/2016-03-08.lua @@ -30,8 +30,8 @@ if not require "std.debug_init"._DEBUG.deprecate then local _, deprecated = { -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", + setenvtable = require "std.strict".setenvtable, std = require "std.base", - strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -42,7 +42,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local DEPRECATED = _.maturity.DEPRECATED -- Only the above symbols are used below this line. - local _, _ENV = nil, _.strict.setenvtable {} + local _, _ENV = nil, _.setenvtable {} --[[ ========== ]]-- diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index ce9ee85..0f8f056 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -29,8 +29,8 @@ if not require "std.debug_init"._DEBUG.deprecate then local _, deprecated = { -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", + setenvtable = require "std.strict".setenvtable, std = require "std.base", - strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -38,14 +38,14 @@ if not require "std.debug_init"._DEBUG.deprecate then if not _.ok then deprecated = {} end + local _pairs = _.std.pairs + local _type = _.std.type local DEPRECATED = _.maturity.DEPRECATED local len = _.std.operator.len - local std_pairs = _.std.pairs local sortkeys = _.std.base.sortkeys - local std_type = _.std.type -- Only the above symbols are used below this line. - local _, _ENV = nil, _.strict.setenvtable {} + local _, _ENV = nil, _.setenvtable {} --[[ ========== ]]-- @@ -54,7 +54,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local function okeys (t) local r = {} - for k in std_pairs (t) do r[#r + 1] = k end + for k in _pairs (t) do r[#r + 1] = k end return sortkeys (r) end @@ -78,8 +78,8 @@ if not require "std.debug_init"._DEBUG.deprecate then M = acyclic_merge ({ object = { - prototype = X ("object.prototype", "std.type", std_type), - type = X ("object.type", "std.type", std_type), + prototype = X ("object.prototype", "std.type", _type), + type = X ("object.type", "std.type", _type), }, table = { diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 0f6f6bc..20376d7 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -17,27 +17,32 @@ local setmetatable = setmetatable local table_remove = table.remove -local deprecated = require "std.delete-after.2016-01-03" -local std = require "std.base" +local _ = { + debug = require "std.debug", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local _pairs = _.std.pairs +local argscheck = _.debug.argscheck +local callable = _.std.functional.callable +local collect = _.std.functional.collect +local copy = _.std.base.copy +local ielems = _.std.ielems +local ireverse = _.std.ireverse +local len = _.std.operator.len +local merge = _.std.base.merge +local mnemonic = _.std.base.mnemonic +local nop = _.std.functional.nop +local npairs = _.std.npairs +local reduce = _.std.functional.reduce +local render = _.std.string.render +local unpack = _.std.table.unpack -local argscheck = require "std.debug".argscheck -local callable = std.functional.callable -local collect = std.functional.collect -local copy = std.base.copy -local ielems = std.ielems -local ipairs = std.ipairs -local ireverse = std.ireverse -local len = std.operator.len -local merge = std.base.merge -local mnemonic = std.base.mnemonic -local nop = std.functional.nop -local npairs = std.npairs -local pairs = std.pairs -local reduce = std.functional.reduce -local render = std.string.render -local unpack = std.table.unpack -local _ENV = require "std.strict".setenvtable {} +local deprecated = require "std.delete-after.2016-01-03" + +local _, _ENV = nil, _.setenvtable {} @@ -108,7 +113,7 @@ end local function filter (pfn, ifn, ...) local argt, r = {...}, {} if not callable (ifn) then - ifn, argt = pairs, {ifn, ...} + ifn, argt = _pairs, {ifn, ...} end local nextfn, state, k = ifn (unpack (argt)) @@ -236,7 +241,7 @@ end, id) local function map (mapfn, ifn, ...) local argt, r = {...}, {} if not callable (ifn) or not next (argt) then - ifn, argt = pairs, {ifn, ...} + ifn, argt = _pairs, {ifn, ...} end local nextfn, state, k = ifn (unpack (argt)) @@ -269,7 +274,7 @@ end local function map_with (mapfn, tt) local r = {} - for k, v in pairs (tt) do + for k, v in _pairs (tt) do r[k] = mapfn (unpack (v)) end return r @@ -302,8 +307,8 @@ end local function zip (tt) local r = {} - for outerk, inner in pairs (tt) do - for k, v in pairs (inner) do + for outerk, inner in _pairs (tt) do + for k, v in _pairs (inner) do r[k] = r[k] or {} r[k][outerk] = v end diff --git a/lib/std/io.lua b/lib/std/io.lua index ef9e923..2b41bf1 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -32,23 +32,27 @@ local string_format = string.format local table_concat = table.concat -local std = require "std.base" +local _ = { + debug = require "std.debug", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local _ipairs = _.std.ipairs +local _tostring = _.std.tostring +local argerror = _.debug.argerror +local argscheck = _.debug.argscheck +local catfile = _.std.io.catfile +local copy = _.std.base.copy +local dirsep = _.std.package.dirsep +local insert = _.std.table.insert +local leaves = _.std.tree.leaves +local len = _.std.operator.len +local merge = _.std.base.merge +local split = _.std.string.split -local argerror = std.debug.argerror -local argscheck = require "std.debug".argscheck -local catfile = std.io.catfile -local copy = std.base.copy -local dirsep = std.package.dirsep -local insert = std.table.insert -local ipairs = std.ipairs -local leaves = std.tree.leaves -local len = std.operator.len -local merge = std.base.merge -local pairs = std.pairs -local split = std.string.split -local tostring = std.tostring -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} @@ -100,7 +104,7 @@ local function writelines (h, ...) io_write (h, "\n") h = io_output () end - for v in leaves (ipairs, {...}) do + for v in leaves (_ipairs, {...}) do h:write (v, "\n") end end @@ -126,7 +130,7 @@ local function process_files (fn) if len (arg) == 0 then insert (arg, "-") end - for i, v in ipairs (arg) do + for i, v in _ipairs (arg) do if v == "-" then io_input (io_stdin) else @@ -144,17 +148,17 @@ local function warnfmt (msg, ...) if prog.name then prefix = prog.name .. ":" if prog.line then - prefix = prefix .. tostring (prog.line) .. ":" + prefix = prefix .. _tostring (prog.line) .. ":" end elseif prog.file then prefix = prog.file .. ":" if prog.line then - prefix = prefix .. tostring (prog.line) .. ":" + prefix = prefix .. _tostring (prog.line) .. ":" end elseif opts.program then prefix = opts.program .. ":" if opts.line then - prefix = prefix .. tostring (opts.line) .. ":" + prefix = prefix .. _tostring (opts.line) .. ":" end end if #prefix > 0 then prefix = prefix .. " " end diff --git a/lib/std/list.lua b/lib/std/list.lua index 2bd68f3..10ff382 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -21,20 +21,26 @@ local math_ceil = math.ceil local math_max = math.max -local debug = require "std.debug" -local deprecated = require "std.delete-after.2016-01-03" -local std = require "std.base" +local _ = { + debug = require "std.debug", + object = require "std.object", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local Module = _.std.object.Module +local Object = _.object.prototype -local Module = std.object.Module -local Object = require "std.object".prototype +local _ipairs = _.std.ipairs +local _pairs = _.std.pairs +local argscheck = _.debug.argscheck +local compare = _.std.list.compare +local len = _.std.operator.len +local merge = _.std.base.merge +local unpack = _.std.table.unpack -local argscheck = debug.argscheck -local compare = std.list.compare -local ipairs = std.ipairs -local len = std.operator.len -local merge = std.base.merge -local pairs = std.pairs -local unpack = std.table.unpack + +local deprecated = require "std.delete-after.2016-01-03" local _ENV = require "std.strict".setenvtable {} @@ -57,8 +63,8 @@ end local function concat (l, ...) local r = prototype {} - for _, e in ipairs {l, ...} do - for _, v in ipairs (e) do + for _, e in _ipairs {l, ...} do + for _, v in _ipairs (e) do r[#r + 1] = v end end @@ -232,7 +238,7 @@ local M = {} if deprecated then local function bindfns (dest, src) - for k, v in pairs (src) do + for k, v in _pairs (src) do dest[k] = dest[k] or function (...) return v (prototype, ...) end end return dest diff --git a/lib/std/math.lua b/lib/std/math.lua index 30693f1..ea85e6c 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -13,15 +13,21 @@ local math = math + local math_floor = math.floor -local std = require "std.base" +local _ = { + debug = require "std.debug", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local argscheck = _.debug.argscheck +local copy = _.std.base.copy +local merge = _.std.base.merge -local argscheck = require "std.debug".argscheck -local copy = std.base.copy -local merge = std.base.merge -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua index 89c0117..d8bd54e 100644 --- a/lib/std/maturity.lua +++ b/lib/std/maturity.lua @@ -32,9 +32,22 @@ local tostring = tostring local io_stderr = io.stderr local string_format = string.format -local _DEBUG = require "std.debug_init"._DEBUG -local _ENV = require "std.strict".setenvtable {} +local _ = { + debug_init = require "std.debug_init", + setenvtable = require "std.strict".setenvtable, +} + +local _DEBUG = _.debug_init._DEBUG + + +local _, _ENV = nil, _.setenvtable {} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- local function DEPRECATIONMSG (version, name, extramsg, level) diff --git a/lib/std/object.lua b/lib/std/object.lua index df57383..c0a32fb 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -22,20 +22,26 @@ ]] -local debug = require "std.debug" -local deprecated = require "std.delete-after.a-year" -local std = require "std.base" +local _ = { + container = require "std.container", + debug = require "std.debug", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local Container = _.container.prototype +local Module = _.std.object.Module -local Container = require "std.container".prototype -local Module = std.object.Module +local _type = _.std.type +local argscheck = _.debug.argscheck +local getmetamethod = _.std.getmetamethod +local mapfields = _.std.object.mapfields +local merge = _.std.base.merge -local argscheck = debug.argscheck -local getmetamethod = std.getmetamethod -local mapfields = std.object.mapfields -local merge = std.base.merge -local type = std.type -local _ENV = require "std.strict".setenvtable {} +local deprecated = require "std.delete-after.a-year" + +local _, _ENV = nil, _.setenvtable {} @@ -80,7 +86,7 @@ local methods = { -- @see std.type -- @usage -- assert (Object:type () == getmetatable (Object)._type) - type = X ("type (?any)", type), + type = X ("type (?any)", _type), --- Object Functions diff --git a/lib/std/operator.lua b/lib/std/operator.lua index b36181f..7b8c901 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -8,13 +8,17 @@ local type = type -local std = require "std.base" +local _ = { + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local _tostring = _.std.tostring +local len = _.std.operator.len +local serialize = _.std.base.mnemonic -local len = std.operator.len -local serialize = std.base.mnemonic -local tostring = std.tostring -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} @@ -44,7 +48,7 @@ local M = { -- @usage -- --> "=> 1000010010" -- functional.foldl (concat, "=> ", {10000, 100, 10}) - concat = function (a, b) return tostring (a) .. tostring (b) end, + concat = function (a, b) return _tostring (a) .. _tostring (b) end, --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. -- @function len diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 7a49344..8f07d59 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -36,17 +36,22 @@ local os_exit = os.exit local string_len = string.len -local std = require "std.base" +local _ = { + object = require "std.object", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local Object = _.object.prototype -local Object = require "std.object".prototype +local _ipairs = _.std.ipairs +local _pairs = _.std.pairs +local insert = _.std.table.insert +local last = _.std.base.last +local len = _.std.operator.len -local insert = std.table.insert -local ipairs = std.ipairs -local last = std.base.last -local len = std.operator.len -local pairs = std.pairs -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} @@ -121,7 +126,7 @@ local function normalise (self, arglist) until opt == nil -- Append split options to normalised list - for _, v in ipairs (split) do insert (normal, v) end + for _, v in _ipairs (split) do insert (normal, v) end else insert (normal, opt) end @@ -452,7 +457,7 @@ local function on (self, opts, handler, value) handler = handler or flag -- unspecified options behave as flags local normal = {} - for _, optspec in ipairs (opts) do + for _, optspec in _ipairs (opts) do optspec:gsub ("(%S+)", function (opt) -- 'x' => '-x' @@ -478,7 +483,7 @@ local function on (self, opts, handler, value) -- strip leading '-', and convert non-alphanums to '_' local key = last (normal):match ("^%-*(.*)$"):gsub ("%W", "_") - for _, opt in ipairs (normal) do + for _, opt in _ipairs (normal) do self[opt] = { key = key, handler = handler, value = value } end end @@ -550,7 +555,7 @@ local function parse (self, arglist, defaults) end -- Merge defaults into user options. - for k, v in pairs (defaults or {}) do + for k, v in _pairs (defaults or {}) do if self.opts[k] == nil then self.opts[k] = v end end @@ -588,7 +593,7 @@ local function _init (_, spec) function (spec) insert (specs, spec) end) -- Register option handlers according to the help text. - for _, spec in ipairs (specs) do + for _, spec in _ipairs (specs) do local options, handler = {} -- Loop around each '-' prefixed option on this line. diff --git a/lib/std/package.lua b/lib/std/package.lua index aa68ac4..5894eda 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -36,6 +36,7 @@ ]] +local ipairs = ipairs local package = package local package_config = package.config @@ -45,19 +46,22 @@ local table_insert = table.insert local table_remove = table.remove -local std = require "std.base" +local _ = { + debug = require "std.debug", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local argscheck = _.debug.argscheck +local catfile = _.std.io.catfile +local escape_pattern = _.std.string.escape_pattern +local invert = _.std.table.invert +local merge = _.std.base.merge +local split = _.std.string.split +local unpack = _.std.table.unpack -local argscheck = require "std.debug".argscheck -local catfile = std.io.catfile -local escape_pattern = std.string.escape_pattern -local invert = std.table.invert -local ipairs = std.ipairs -local merge = std.base.merge -local pairs = std.pairs -local split = std.string.split -local unpack = std.table.unpack -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} diff --git a/lib/std/set.lua b/lib/std/set.lua index 3550dbc..c609fc5 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -33,18 +33,24 @@ local table_concat = table.concat local table_sort = table.sort -local std = require "std.base" +local _ = { + container = require "std.container", + debug = require "std.debug", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local Container = _.container.prototype +local Module = _.std.object.Module -local Container = require "std.container".prototype -local Module = std.object.Module +local _pairs = _.std.pairs +local _tostring = _.std.tostring +local _type = _.std.type +local argscheck = _.debug.argscheck +local pickle = _.std.string.pickle -local argscheck = require "std.debug".argscheck -local pairs = std.pairs -local pickle = std.string.pickle -local tostring = std.tostring -local std_type = std.type -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} @@ -67,7 +73,7 @@ local prototype -- forward declaration -- whose values are true. -local elems = pairs +local elems = _pairs local function insert (set, e) @@ -179,7 +185,7 @@ prototype = Container { -- @tparam table t initialisation table from `__call` _init = function (new, t) local mt = {} - for k, v in pairs (t) do + for k, v in _pairs (t) do local type_k = type (k) if type_k == "number" then insert (new, v) @@ -256,11 +262,11 @@ prototype = Container { -- @see std.tostring __tostring = function (self) local keys = {} - for k in pairs (self) do - keys[#keys + 1] = tostring (k) + for k in _pairs (self) do + keys[#keys + 1] = _tostring (k) end table_sort (keys) - return std_type (self) .. " {" .. table_concat (keys, ", ") .. "}" + return _type (self) .. " {" .. table_concat (keys, ", ") .. "}" end, --- Return a loadable serialization of this object, where possible. @@ -269,7 +275,7 @@ prototype = Container { -- @see std.string.pickle __pickle = function (self) local mt, keys = getmetatable (self), {} - for k in pairs (self) do + for k in _pairs (self) do keys[#keys + 1] = pickle (k) end table_sort (keys) diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 0e5df42..ded4342 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -33,19 +33,25 @@ local type = type local table_concat = table.concat -local std = require "std.base" -local debug = require "std.debug" -local deprecated = require "std.delete-after.2016-01-31" +local _ = { + debug = require "std.debug", + object = require "std.object", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local Module = _.std.object.Module +local Object = _.object.prototype -local Module = std.object.Module -local Object = require "std.object".prototype +local argscheck = _.debug.argscheck +local ielems = _.std.ielems +local insert = _.std.table.insert +local merge = _.std.base.merge -local argscheck = debug.argscheck -local ielems = std.ielems -local insert = std.table.insert -local merge = std.base.merge -local _ENV = require "std.strict".setenvtable {} +local deprecated = require "std.delete-after.2016-01-31" + +local _, _ENV = nil, _.setenvtable {} diff --git a/lib/std/string.lua b/lib/std/string.lua index fc708d4..f2d5424 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -23,32 +23,34 @@ local math_abs = math.abs local math_floor = math.floor -local debug = require "std.debug" -local deprecated = require "std.delete-after.2016-01-03" -local maturity = require "std.maturity" -local std = require "std.base" +local _ = { + debug = require "std.debug", + maturity = require "std.maturity", + setenvtable = require "std.strict".setenvtable, + strbuf = require "std.strbuf", + std = require "std.base", +} + +local StrBuf = _.strbuf.prototype -local StrBuf = require "std.strbuf".prototype +local _tostring = _.std.tostring +local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG +local argscheck = _.debug.argscheck +local copy = _.std.base.copy +local escape_pattern = _.std.string.escape_pattern +local insert = _.std.table.insert +local len = _.std.operator.len +local merge = _.std.base.merge +local pickle = _.std.string.pickle +local render = _.std.string.render +local sortkeys = _.std.base.sortkeys +local split = _.std.string.split +local toqstring = _.std.base.toqstring -local DEPRECATIONMSG = maturity.DEPRECATIONMSG -local argscheck = debug.argscheck -local callable = std.functional.callable -local copy = std.base.copy -local escape_pattern = std.string.escape_pattern -local getmetamethod = std.getmetamethod -local insert = std.table.insert -local keysort = std.base.keysort -local len = std.operator.len -local merge = std.base.merge -local pairs = std.pairs -local pickle = std.string.pickle -local render = std.string.render -local sortkeys = std.base.sortkeys -local split = std.string.split -local std_tostring = std.tostring -local toqstring = std.base.toqstring -local _ENV = require "std.strict".setenvtable {} +local deprecated = require "std.delete-after.2016-01-03" + +local _, _ENV = nil, _.setenvtable {} @@ -61,7 +63,7 @@ local M local function __concat (s, o) - return std_tostring (s) .. std_tostring (o) + return _tostring (s) .. _tostring (o) end diff --git a/lib/std/table.lua b/lib/std/table.lua index 72e09a7..ac10033 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -21,25 +21,30 @@ local math_ceil = math.ceil local math_min = math.min -local debug = require "std.debug" -local deprecated = require "std.delete-after.a-year" -local std = require "std.base" +local _ = { + debug = require "std.debug", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local _ipairs = _.std.ipairs +local _pairs = _.std.pairs +local argscheck = _.debug.argscheck +local argerror = _.std.debug.argerror +local collect = _.std.functional.collect +local copy = _.std.base.copy +local insert = _.std.table.insert +local invert = _.std.table.invert +local leaves = _.std.tree.leaves +local len = _.std.operator.len +local maxn = _.std.table.maxn +local merge = _.std.base.merge +local unpack = _.std.table.unpack -local argscheck = debug.argscheck -local argerror = std.debug.argerror -local collect = std.functional.collect -local copy = std.base.copy -local insert = std.table.insert -local invert = std.table.invert -local ipairs = std.ipairs -local leaves = std.tree.leaves -local len = std.operator.len -local maxn = std.table.maxn -local merge = std.base.merge -local pairs = std.pairs -local unpack = std.table.unpack -local _ENV = require "std.strict".setenvtable {} +local deprecated = require "std.delete-after.a-year" + +local _, _ENV = nil, _.setenvtable {} @@ -61,9 +66,9 @@ local function merge_allfields (t, u, map, nometa) setmetatable (t, getmetatable (u)) end if map then - for k, v in pairs (u) do t[map[k] or k] = v end + 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 + for k, v in _pairs (u) do t[k] = v end end return t end @@ -77,14 +82,14 @@ local function merge_namedfields (t, u, keys, nometa) if not nometa then setmetatable (t, getmetatable (u)) end - for _, k in pairs (keys or {}) do t[k] = u[k] 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 + for _, v in _ipairs (ls) do t[v[1]] = v[2] end return t @@ -93,7 +98,7 @@ end local function enpair (t) local tt = {} - for i, v in pairs (t) do + for i, v in _pairs (t) do tt[#tt + 1] = {i, v} end return tt @@ -101,13 +106,13 @@ end local function flatten (t) - return collect (leaves, ipairs, t) + return collect (leaves, _ipairs, t) end local function keys (t) local l = {} - for k in pairs (t) do + for k in _pairs (t) do l[#l + 1] = k end return l @@ -124,7 +129,7 @@ end local function project (fkey, tt) local r = {} - for _, t in ipairs (tt) do + for _, t in _ipairs (tt) do r[#r + 1] = t[fkey] end return r @@ -136,7 +141,7 @@ local function shape (dims, t) -- Check the shape and calculate the size of the zero, if any local size = 1 local zero - for i, v in ipairs (dims) do + for i, v in _ipairs (dims) do if v == 0 then if zero then -- bad shape: two zeros return nil @@ -169,7 +174,7 @@ end local function size (t) local n = 0 - for _ in pairs (t) do + for _ in _pairs (t) do n = n + 1 end return n @@ -206,7 +211,7 @@ end local function values (t) local l = {} - for _, v in pairs (t) do + for _, v in _pairs (t) do l[#l + 1] = v end return l diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 4c472c7..d939684 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -35,23 +35,30 @@ local coroutine_wrap = coroutine.wrap local table_remove = table.remove -local std = require "std.base" -local operator = require "std.operator" +local _ = { + container = require "std.container", + debug = require "std.debug", + operator = require "std.operator", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local Container = _.container.prototype +local Module = _.std.object.Module -local Container = require "std.container".prototype -local Module = std.object.Module +local _ipairs = _.std.ipairs +local _pairs = _.std.pairs +local _type = _.std.type +local argscheck = _.debug.argscheck +local get = _.operator.get +local ielems = _.std.ielems +local last = _.std.base.last +local leaves = _.std.tree.leaves +local len = _.std.operator.len +local reduce = _.std.functional.reduce -local argscheck = require "std.debug".argscheck -local ielems = std.ielems -local ipairs = std.ipairs -local last = std.base.last -local leaves = std.tree.leaves -local len = std.operator.len -local pairs = std.pairs -local reduce = std.functional.reduce -local std_type = std.type -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} @@ -96,7 +103,7 @@ local function clone (t, nometa) end local d = {[t] = r} local function copy (o, x) - for i, v in pairs (x) do + for i, v in _pairs (x) do if type (v) == "table" then if not d[v] then d[v] = {} @@ -118,7 +125,7 @@ end local function merge (t, u) - for ty, p, n in _nodes (pairs, u) do + for ty, p, n in _nodes (_pairs, u) do if ty == "leaf" then t[p] = n end @@ -171,8 +178,8 @@ prototype = Container { -- @usage -- del_other_window = keymap[{"C-x", "4", KEY_DELETE}] __index = function (tr, i) - if std_type (i) == "table" then - return reduce (operator.get, tr, ielems, i) + if _type (i) == "table" then + return reduce (get, tr, ielems, i) else return rawget (tr, i) end @@ -185,9 +192,9 @@ prototype = Container { -- @usage -- function bindkey (keylist, fn) keymap[keylist] = fn end __newindex = function (tr, i, v) - if std_type (i) == "table" then + if _type (i) == "table" then for n = 1, len (i) - 1 do - if std_type (tr[i[n]]) ~= "Tree" then + if _type (tr[i[n]]) ~= "Tree" then rawset (tr, i[n], prototype {}) end tr = tr[i[n]] @@ -233,7 +240,7 @@ return Module { -- do -- t[#t + 1] = leaf -- end - ileaves = X ("ileaves (table)", function (t) return leaves (ipairs, t) end), + ileaves = X ("ileaves (table)", function (t) return leaves (_ipairs, t) end), --- Tree iterator over numbered nodes, in order. -- @@ -244,7 +251,7 @@ return Module { -- @treturn function iterator function -- @treturn tree|table the tree, *tr* -- @see nodes - inodes = X ("inodes (table)", function (t) return _nodes (ipairs, t) end), + inodes = X ("inodes (table)", function (t) return _nodes (_ipairs, t) end), --- Tree iterator which returns just leaves. -- @function leaves @@ -260,7 +267,7 @@ return Module { -- end -- --> t = {2, 4, "five", "foo", "one", "three"} -- table.sort (t, lambda "=tostring(_1) < tostring(_2)") - leaves = X ("leaves (table)", function (t) return leaves (pairs, t) end), + leaves = X ("leaves (table)", function (t) return leaves (_pairs, t) end), --- Destructively deep-merge one tree into another. -- @function merge @@ -306,5 +313,5 @@ return Module { -- --> "leaf" {2} "leaf3" -- --> "join" {} {{"leaf1", "leaf2"}, "leaf3"} -- os.exit (0) - nodes = X ("nodes (table)", function (t) return _nodes (pairs, t) end), + nodes = X ("nodes (table)", function (t) return _nodes (_pairs, t) end), } diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index df5d03c..616787b 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -38,16 +38,21 @@ local table_concat = table.concat local table_unpack = table.unpack or unpack -local std = require "std.base" +local _ = { + container = require "std.container", + setenvtable = require "std.strict".setenvtable, + std = require "std.base", +} + +local Container = _.container.prototype +local Module = _.std.object.Module -local Container = require "std.container".prototype -local Module = std.object.Module +local _type = _.std.type +local pickle = _.std.string.pickle +local toqstring = _.std.base.toqstring -local pickle = std.string.pickle -local std_type = std.type -local toqstring = std.base.toqstring -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.setenvtable {} @@ -140,7 +145,7 @@ local prototype = Container { -- print (Tuple ("nil", nil, false)) __tostring = function (self) local _, argstr = next (self) - return string_format ("%s (%s)", std_type (self), argstr) + return string_format ("%s (%s)", _type (self), argstr) end, --- Unpack tuple values between index *i* and *j*, inclusive. From e792caaeeb9dc417dc04f50b69050002a28f89e9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 28 Sep 2015 22:41:25 +0100 Subject: [PATCH 610/703] refactor: factor away strict.setenvtable. * lib/std/strict.lua (setenvtable): Move from here... (__call): ...to here. Simplify all callers accordingly. * NEWS.md (New features): Remove setenvtable entry. Signed-off-by: Gary V. Vaughan --- NEWS.md | 13 +----------- lib/std/base.lua | 2 +- lib/std/container.lua | 4 ++-- lib/std/debug.lua | 4 ++-- lib/std/delete-after/2016-01-03.lua | 4 ++-- lib/std/delete-after/2016-01-31.lua | 4 ++-- lib/std/delete-after/2016-03-08.lua | 4 ++-- lib/std/delete-after/a-year.lua | 4 ++-- lib/std/functional.lua | 4 ++-- lib/std/io.lua | 4 ++-- lib/std/list.lua | 4 ++-- lib/std/math.lua | 4 ++-- lib/std/maturity.lua | 4 ++-- lib/std/object.lua | 4 ++-- lib/std/operator.lua | 4 ++-- lib/std/optparse.lua | 4 ++-- lib/std/package.lua | 4 ++-- lib/std/set.lua | 4 ++-- lib/std/strbuf.lua | 4 ++-- lib/std/strict.lua | 33 +++++++---------------------- lib/std/string.lua | 6 +++--- lib/std/table.lua | 4 ++-- lib/std/tree.lua | 4 ++-- lib/std/tuple.lua | 4 ++-- 24 files changed, 53 insertions(+), 81 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6c7c3a9..a0f0801 100644 --- a/NEWS.md +++ b/NEWS.md @@ -16,18 +16,7 @@ module, but without changing the behaviour of the client code: ```lua - local strict = require "std.strict" - local _ENV = strict (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end - ``` - - - New `std.strict.setenvtable` encapsulates the above for portably - assigning an environment table at the module level, but enforcing - declaration strictness only when `_DEBUG.strict` (or equivalent) - is set: - - ```lua - local _ENV = strict.setenvtable {} + local _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) ``` - All support for deprecated APIs has been moved out of the module diff --git a/lib/std/base.lua b/lib/std/base.lua index 5f10468..e661563 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -51,7 +51,7 @@ local table_sort = table.sort local table_unpack = table.unpack or unpack -local _ENV = require "std.strict".setenvtable {} +local _ENV = require "std.strict" {} diff --git a/lib/std/container.lua b/lib/std/container.lua index 859d975..49fc241 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -42,8 +42,8 @@ local table_concat = table.concat local _ = { debug = require "std.debug", debug_init = require "std.debug_init", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local Module = _.std.object.Module @@ -59,7 +59,7 @@ local pickle = _.std.string.pickle local render = _.std.string.render local sortkeys = _.std.base.sortkeys -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 60f6c7b..f55c226 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -51,7 +51,7 @@ local table_sort = table.sort local _ = { debug_init = require "std.debug_init", std = require "std.base", - setenvtable = require "std.strict".setenvtable, + strict = require "std.strict", } local _DEBUG = _.debug_init._DEBUG @@ -74,7 +74,7 @@ local unpack = _.std.table.unpack local deprecated = require "std.delete-after.2016-03-08" -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/delete-after/2016-01-03.lua b/lib/std/delete-after/2016-01-03.lua index 918f187..e8cd0e4 100644 --- a/lib/std/delete-after/2016-01-03.lua +++ b/lib/std/delete-after/2016-01-03.lua @@ -35,8 +35,8 @@ if not require "std.debug_init"._DEBUG.deprecate then local _, deprecated = { -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -60,7 +60,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local ripairs = _.std.ripairs -- Only the above symbols are used below this line. - local _, _ENV = nil, _.setenvtable {} + local _, _ENV = nil, _.strict {} --[[ ========== ]]-- diff --git a/lib/std/delete-after/2016-01-31.lua b/lib/std/delete-after/2016-01-31.lua index ec61abb..fbae56e 100644 --- a/lib/std/delete-after/2016-01-31.lua +++ b/lib/std/delete-after/2016-01-31.lua @@ -31,8 +31,8 @@ if not require "std.debug_init"._DEBUG.deprecate then local _, deprecated = { -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -44,7 +44,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local _ipairs = _.std.ipairs -- Only the above symbols are used below this line. - local _, _ENV = nil, _.setenvtable {} + local _, _ENV = nil, _.strict {} --[[ ========== ]]-- diff --git a/lib/std/delete-after/2016-03-08.lua b/lib/std/delete-after/2016-03-08.lua index 31064d3..c6ed718 100644 --- a/lib/std/delete-after/2016-03-08.lua +++ b/lib/std/delete-after/2016-03-08.lua @@ -30,8 +30,8 @@ if not require "std.debug_init"._DEBUG.deprecate then local _, deprecated = { -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -42,7 +42,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local DEPRECATED = _.maturity.DEPRECATED -- Only the above symbols are used below this line. - local _, _ENV = nil, _.setenvtable {} + local _, _ENV = nil, _.strict {} --[[ ========== ]]-- diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 0f8f056..864dcdd 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -29,8 +29,8 @@ if not require "std.debug_init"._DEBUG.deprecate then local _, deprecated = { -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -45,7 +45,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local sortkeys = _.std.base.sortkeys -- Only the above symbols are used below this line. - local _, _ENV = nil, _.setenvtable {} + local _, _ENV = nil, _.strict {} --[[ ========== ]]-- diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 20376d7..3757f83 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -19,8 +19,8 @@ local table_remove = table.remove local _ = { debug = require "std.debug", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local _pairs = _.std.pairs @@ -42,7 +42,7 @@ local unpack = _.std.table.unpack local deprecated = require "std.delete-after.2016-01-03" -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/io.lua b/lib/std/io.lua index 2b41bf1..7aa622a 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -34,8 +34,8 @@ local table_concat = table.concat local _ = { debug = require "std.debug", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local _ipairs = _.std.ipairs @@ -52,7 +52,7 @@ local merge = _.std.base.merge local split = _.std.string.split -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/list.lua b/lib/std/list.lua index 10ff382..22b2af1 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -24,8 +24,8 @@ local math_max = math.max local _ = { debug = require "std.debug", object = require "std.object", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local Module = _.std.object.Module @@ -42,7 +42,7 @@ local unpack = _.std.table.unpack local deprecated = require "std.delete-after.2016-01-03" -local _ENV = require "std.strict".setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/math.lua b/lib/std/math.lua index ea85e6c..0433e07 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -18,8 +18,8 @@ local math_floor = math.floor local _ = { debug = require "std.debug", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local argscheck = _.debug.argscheck @@ -27,7 +27,7 @@ local copy = _.std.base.copy local merge = _.std.base.merge -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua index d8bd54e..6808dd8 100644 --- a/lib/std/maturity.lua +++ b/lib/std/maturity.lua @@ -35,13 +35,13 @@ local string_format = string.format local _ = { debug_init = require "std.debug_init", - setenvtable = require "std.strict".setenvtable, + strict = require "std.strict", } local _DEBUG = _.debug_init._DEBUG -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/object.lua b/lib/std/object.lua index c0a32fb..610a024 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -25,8 +25,8 @@ local _ = { container = require "std.container", debug = require "std.debug", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local Container = _.container.prototype @@ -41,7 +41,7 @@ local merge = _.std.base.merge local deprecated = require "std.delete-after.a-year" -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 7b8c901..168b3c2 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -9,8 +9,8 @@ local type = type local _ = { - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local _tostring = _.std.tostring @@ -18,7 +18,7 @@ local len = _.std.operator.len local serialize = _.std.base.mnemonic -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 8f07d59..6fc02ff 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -38,8 +38,8 @@ local string_len = string.len local _ = { object = require "std.object", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local Object = _.object.prototype @@ -51,7 +51,7 @@ local last = _.std.base.last local len = _.std.operator.len -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/package.lua b/lib/std/package.lua index 5894eda..17c13e1 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -48,8 +48,8 @@ local table_remove = table.remove local _ = { debug = require "std.debug", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local argscheck = _.debug.argscheck @@ -61,7 +61,7 @@ local split = _.std.string.split local unpack = _.std.table.unpack -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/set.lua b/lib/std/set.lua index c609fc5..bc843da 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -36,8 +36,8 @@ local table_sort = table.sort local _ = { container = require "std.container", debug = require "std.debug", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local Container = _.container.prototype @@ -50,7 +50,7 @@ local argscheck = _.debug.argscheck local pickle = _.std.string.pickle -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index ded4342..fed405e 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -36,8 +36,8 @@ local table_concat = table.concat local _ = { debug = require "std.debug", object = require "std.object", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local Module = _.std.object.Module @@ -51,7 +51,7 @@ local merge = _.std.base.merge local deprecated = require "std.delete-after.2016-01-31" -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/strict.lua b/lib/std/strict.lua index 18297bc..870a252 100644 --- a/lib/std/strict.lua +++ b/lib/std/strict.lua @@ -6,24 +6,10 @@ used anywhere or assigned to inside a function. Use the callable returned by this module to interpose a strictness check - proxy table to the argument table. To apply to just the current module, - for example: - - local strict = require "std.strict" - local _ENV = strict (setmetatable ({}, {__index = _G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end - - Note that we have to be careful not to reference `setfenv` directly in - the `if` statement, because on Lua >= 5.2, it doesn't exist and would - trigger an undeclared variable error! - - Or, to set an empty strictness enforcing environment table for a module - (after caching all symbols used after this invocation): - - local _ENV = strict.setenvtable {} + proxy table to the given environment. The implementation calls `setfenv` appropriately in Lua 5.1 interpreters - to provide the same semantics. + to ensure the same semantics. @module std.strict ]] @@ -89,21 +75,18 @@ local function restrict (env) end -return setmetatable ({ - --- Functions - -- @section functions - - --- Enforce strict variable declaration in *env* according to `_DEBUG`. - -- @function setenvtable +return setmetatable ({}, { + --- Enforce strict variable declarations in *env* according to `_DEBUG`. + -- @function strict:__call -- @tparam table env lexical environment table -- @treturn table *env* which must be assigned to `_ENV` - setenvtable = function (env) + -- @usage + -- local _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + __call = function (_, env) if _DEBUG.strict then env = restrict (env) end setfenv (2, env) return env end, -}, { - __call = function (_, ...) return restrict (...) end, }) diff --git a/lib/std/string.lua b/lib/std/string.lua index f2d5424..cad1b2d 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -26,9 +26,9 @@ local math_floor = math.floor local _ = { debug = require "std.debug", maturity = require "std.maturity", - setenvtable = require "std.strict".setenvtable, - strbuf = require "std.strbuf", std = require "std.base", + strbuf = require "std.strbuf", + strict = require "std.strict", } local StrBuf = _.strbuf.prototype @@ -50,7 +50,7 @@ local toqstring = _.std.base.toqstring local deprecated = require "std.delete-after.2016-01-03" -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/table.lua b/lib/std/table.lua index ac10033..3c237dc 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -23,8 +23,8 @@ local math_min = math.min local _ = { debug = require "std.debug", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local _ipairs = _.std.ipairs @@ -44,7 +44,7 @@ local unpack = _.std.table.unpack local deprecated = require "std.delete-after.a-year" -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/tree.lua b/lib/std/tree.lua index d939684..041c6ea 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -39,8 +39,8 @@ local _ = { container = require "std.container", debug = require "std.debug", operator = require "std.operator", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local Container = _.container.prototype @@ -58,7 +58,7 @@ local len = _.std.operator.len local reduce = _.std.functional.reduce -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 616787b..019a715 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -40,8 +40,8 @@ local table_unpack = table.unpack or unpack local _ = { container = require "std.container", - setenvtable = require "std.strict".setenvtable, std = require "std.base", + strict = require "std.strict", } local Container = _.container.prototype @@ -52,7 +52,7 @@ local pickle = _.std.string.pickle local toqstring = _.std.base.toqstring -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} From 55c055edc7df2ebd63e3bf99a636b72c0ca6b6f1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 28 Sep 2015 22:46:51 +0100 Subject: [PATCH 611/703] maint: commit forgotten lib/std/lua.in changes. * lib/std.lua.in (_.setenvtable): Use new `_.strict` __call instead. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index b35c0c3..b122fad 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -32,7 +32,7 @@ local type = type local _ = { debug = require "std.debug", - setenvtable = require "std.strict".setenvtable, + strict = require "std.strict", std = require "std.base", } @@ -53,7 +53,7 @@ local npairs = _.std.npairs local ripairs = _.std.ripairs local rnpairs = _.std.rnpairs -local _, _ENV = nil, _.setenvtable {} +local _, _ENV = nil, _.strict {} From 7d67d57b54c6a88324051689ea81a960d608cdd8 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Oct 2015 16:34:11 +0100 Subject: [PATCH 612/703] refactor: export std.strict.strict again. This function is useful in its own right outside of stdlib, and required for the specl examples to pass on Lua 5.1. * lib/std/strict.lua (restrict): Move this local function... (std.strict.strict): ...to the exported module table. * specs/strict_spec.yaml (std.strict): Adjust examples accordingly. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 2 +- lib/std/strict.lua | 94 +++++++++++++++++++++++------------------- specs/strict_spec.yaml | 19 +++++---- 3 files changed, 64 insertions(+), 51 deletions(-) diff --git a/NEWS.md b/NEWS.md index a0f0801..501828d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,7 +10,7 @@ us organization-wise, but improvements and corrections to the content are always welcome! - - `require "std.strict"` now returns a callable that can be applied + - `require "std.strict"` now returns a function that can be applied to any environment that should detect references to undeclared variables. For example, to check within the implementation of a module, but without changing the behaviour of the client code: diff --git a/lib/std/strict.lua b/lib/std/strict.lua index 870a252..a5f59af 100644 --- a/lib/std/strict.lua +++ b/lib/std/strict.lua @@ -35,56 +35,66 @@ local function what () end -local function restrict (env) - -- The set of declared variables in this scope. - local declared = {} - - --- Metamethods - -- @section metamethods - - return setmetatable ({}, { - --- Detect dereference of undeclared global. - -- @function __index - -- @string n name of the variable being dereferenced - __index = function (_, n) - local v = env[n] - if v ~= nil then - declared[n] = true - elseif not declared[n] and what () ~= "C" then - error ("variable '" .. n .. "' is not declared", 2) - end - return v - end, - - --- Detect assignment to undeclared global. - -- @function __newindex - -- @string n name of the variable being declared - -- @param v initial value of the variable - __newindex = function (_, n, v) - local x = env[n] - if x == nil and not declared[n] then - local w = what () - if w ~= "main" and w ~= "C" then - error ("assignment to undeclared variable '" .. n .. "'", 2) +return setmetatable ({ + --- Enforce variable declarations required before use in scope *env*. + -- @function strict + -- @tparam table env lexical environment table + -- @treturn table *env* proxy table with metamethods to enforce strict declarations + -- @usage + -- local _ENV = setmetatable ({}, {__index = _G}) + -- if require "std.debug_init"._DEBUG.strict then + -- _ENV = require "std.strict".strict (_ENV) + -- end + -- -- ...and for Lua 5.1 compatibility: + -- if rawget (_G, "setfenv") ~= nil then setfenv (1, _ENV) end + strict = function (env) + -- The set of declared variables in this scope. + local declared = {} + + --- Metamethods + -- @section metamethods + + return setmetatable ({}, { + --- Detect dereference of undeclared global. + -- @function env:__index + -- @string n name of the variable being dereferenced + __index = function (_, n) + local v = env[n] + if v ~= nil then + declared[n] = true + elseif not declared[n] and what () ~= "C" then + error ("variable '" .. n .. "' is not declared", 2) end - end - declared[n] = true - env[n] = v - end, - }) -end - - -return setmetatable ({}, { + return v + end, + + --- Detect assignment to undeclared global. + -- @function env:__newindex + -- @string n name of the variable being declared + -- @param v initial value of the variable + __newindex = function (_, n, v) + local x = env[n] + if x == nil and not declared[n] then + local w = what () + if w ~= "main" and w ~= "C" then + error ("assignment to undeclared variable '" .. n .. "'", 2) + end + end + declared[n] = true + env[n] = v + end, + }) + end, +}, { --- Enforce strict variable declarations in *env* according to `_DEBUG`. -- @function strict:__call -- @tparam table env lexical environment table -- @treturn table *env* which must be assigned to `_ENV` -- @usage -- local _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - __call = function (_, env) + __call = function (self, env) if _DEBUG.strict then - env = restrict (env) + env = self.strict (env) end setfenv (2, env) return env diff --git a/specs/strict_spec.yaml b/specs/strict_spec.yaml index f1d6685..1d31074 100644 --- a/specs/strict_spec.yaml +++ b/specs/strict_spec.yaml @@ -8,32 +8,35 @@ specify std.strict: - describe strict: + - before: + f = strict.strict + - it allows assignment to declared variables: - scope = strict { foo = "bar" } + scope = f { foo = "bar" } expect ((function () scope.foo = "baz" end) ()). not_to_raise "not declared" expect (scope.foo).to_be "baz" - it diagnoses assignment to undeclared variable: - scope = strict { foo = "bar" } + scope = f { foo = "bar" } expect ((function () scope.undefined = "rval" end) ()). to_raise "assignment to undeclared variable 'undefined'" - it allows reference to declared variables: - scope = strict { foo = "bar" } + scope = f { foo = "bar" } expect ((function () return scope.foo end) ()).to_be "bar" - it diagnoses reference to undeclared variable: - scope = strict {} + scope = f {} expect ((function () return scope.undefined end) ()). to_raise "variable 'undefined' is not declared" - it allows assignemnt to undeclared global variables: - _ENV = strict (setmetatable ({}, {__index=_G})) + _ENV = f (setmetatable ({}, {__index=_G})) if rawget (_G, "setfenv") then setfenv (1, _ENV) end defined = "rval" @@ -42,21 +45,21 @@ specify std.strict: not_to_raise "undeclared variable" - it diagnoses assignment to undeclared global variable: - _ENV = strict (setmetatable ({}, {__index=_G})) + _ENV = f (setmetatable ({}, {__index=_G})) if rawget (_G, "setfenv") then setfenv (1, _ENV) end expect ((function () undefined = "rval" end) ()). to_raise "assignment to undeclared variable 'undefined'" - it diagnoses reference to undeclared global variable: - _ENV = strict (setmetatable ({}, {__index=_G})) + _ENV = f (setmetatable ({}, {__index=_G})) if rawget (_G, "setfenv") then setfenv (1, _ENV) end expect ((function () foo = undefined end) ()). to_raise "variable 'undefined' is not declared" - it does not leak into surrounding scope: - _ENV = strict (setmetatable ({}, {__index=_G})) + _ENV = f (setmetatable ({}, {__index=_G})) if rawget (_G, "setfenv") then setfenv (1, _ENV) end expect ((function () _G.undefined = "rval" end) ()). From eaf693569aad5b68194a5047a54cea836ace98aa Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Oct 2015 18:04:23 +0100 Subject: [PATCH 613/703] maint: update README.md. * README.md: Update and overhaul. Signed-off-by: Gary V. Vaughan --- README.md | 91 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index bd8b233..a0abdfb 100644 --- a/README.md +++ b/README.md @@ -10,40 +10,63 @@ by the [stdlib project][github] [![Stories in Ready](https://badge.waffle.io/lua-stdlib/lua-stdlib.png?label=ready&title=Ready)](https://waffle.io/lua-stdlib/lua-stdlib) -This is a collection of Lua libraries for Lua 5.1, 5.2 and 5.3. The -libraries are copyright by their authors 2000-2015 (see the AUTHORS -file for details), and released under the MIT license (the same -license as Lua itself). There is no warranty. +This is a collection of Lua libraries for LuaJIT, Lua 5.1, 5.2 and 5.3. +The libraries are copyright by their authors 2000-2015 (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 prerequisites beyond a standard Lua system. +Stdlib has no run-time prerequisites beyond a standard Lua system. + +[authors]: http://github.com/lua-stdlib/lua-stdlib/blob/master/AUTHORS +[lua]: http://www.lua.org "The Lua Project" +[mit]: http://mit-license.org "MIT License" Installation ------------ -The simplest way to install stdlib is with [LuaRocks][]. To install the -latest release (recommended): +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): - - luarocks install https://raw.githubusercontent.com/lua-stdlib/lua-stdlib/release/stdlib-git-1.rockspec - -To install without LuaRocks, check out the sources from the -[repository][github], and then run the following commands: the -dependencies are listed in the dependencies entry of the file -`stdlib-rockspec.lua`. You will also need autoconf and automake. - - cd lua-stdlib - autoreconf --force --version --install - ./configure --prefix=INSTALLATION-ROOT-DIRECTORY - make all check install - -See [INSTALL][] for instructions for `configure`. - +``` + +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 download a github +[release tarball][releases] and follow the instructions in the included +[INSTALL][] file. Even if you are repackaging or redistributing +[stdlib][github], this is by far the most straight forward place to +begin. + +Note that you'll be responsible for providing dependencies if you choose +not to let [LuaRocks][] handle them for you, though you can find a list +of minimal dependencies in the [rockspec.conf][] file. + +It is also possible to perform a complete bootstrap of the +[master][github] development branch, although this branch is unstable, +and sometimes breaks subtly, or does not build at all, or provides +experimental new APIs that end up being removed prior to the next +official release. Unfortunately, we don't have time to provide support +for taking this most difficult and dangerous option. It is presumed +that you already know enough to be aware of what you are getting yourself +into - however, there are full logs of complete bootstrap builds in +[Travis][] after every commit thatyou can examine if you get stuck, and +the bootstrap script tries very hard to tell you why it is unhappy and, +sometimes, even how to fix things before trying again. + +[install]: http://raw.githubusercontent.com/lua-stdlib/lua-stdlib/release/INSTALL [luarocks]: http://www.luarocks.org "LuaRocks Project" -[install]: https://raw.githubusercontent.com/lua-stdlib/lua-stdlib/master/INSTALL +[releases]: http://github.com/lua-stdlib/lua-stdlib/releases +[rockspec.conf]: http://github.com/lua-stdlib/lua-stdlib/blob/release/rockspec.conf +[travis]: http://travis-ci.org/lua-stdlib/lua-stdlib/builds + Use --- @@ -51,7 +74,9 @@ Use As well as requiring individual libraries, you can load the standard set with - require "std" +```lua + local std = require "std" +``` Modules not in the standard set may be removed from future versions of stdlib. @@ -69,6 +94,14 @@ files are included in the release. Bug reports and code contributions ---------------------------------- -These libraries are written and maintained by their users. Please make -bug report and suggestions on GitHub (see URL at top of file). Pull -requests are especially appreciated. +These libraries are written and maintained by their users. + +Please make bug report 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][]). + +[issues]: http://github.com/lua-stdlib/lua-stdlib/issues From 6b4c95740b85456fe964b3e01dc6d29020e2d676 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Oct 2015 18:33:48 +0100 Subject: [PATCH 614/703] maint: update README.md some more. * README.md: Merge in useful improvements from luaposix README.md. Signed-off-by: Gary V. Vaughan --- README.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a0abdfb..c01bd9c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ Standard Lua libraries by the [stdlib project][github] -[github]: http://github.com/lua-stdlib/lua-stdlib/ "Github repository" - [![License](http://img.shields.io/:license-mit-blue.svg)](http://mit-license.org) [![travis-ci status](https://secure.travis-ci.org/lua-stdlib/lua-stdlib.png?branch=master)](http://travis-ci.org/lua-stdlib/lua-stdlib/builds) [![Stories in Ready](https://badge.waffle.io/lua-stdlib/lua-stdlib.png?label=ready&title=Ready)](https://waffle.io/lua-stdlib/lua-stdlib) @@ -18,6 +16,7 @@ The libraries are copyright by their authors 2000-2015 (see the Stdlib has no run-time prerequisites beyond a standard Lua system. [authors]: http://github.com/lua-stdlib/lua-stdlib/blob/master/AUTHORS +[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" @@ -57,12 +56,12 @@ official release. Unfortunately, we don't have time to provide support for taking this most difficult and dangerous option. It is presumed that you already know enough to be aware of what you are getting yourself into - however, there are full logs of complete bootstrap builds in -[Travis][] after every commit thatyou can examine if you get stuck, and -the bootstrap script tries very hard to tell you why it is unhappy and, -sometimes, even how to fix things before trying again. +[Travis][] after every commit, that you can examine if you get stuck. +Also, the bootstrap script tries very hard to tell you why it is unhappy +and, sometimes, even how to fix things before trying again. [install]: http://raw.githubusercontent.com/lua-stdlib/lua-stdlib/release/INSTALL -[luarocks]: http://www.luarocks.org "LuaRocks Project" +[luarocks]: http://www.luarocks.org "Lua package manager" [releases]: http://github.com/lua-stdlib/lua-stdlib/releases [rockspec.conf]: http://github.com/lua-stdlib/lua-stdlib/blob/release/rockspec.conf [travis]: http://travis-ci.org/lua-stdlib/lua-stdlib/builds @@ -85,8 +84,8 @@ stdlib. Documentation ------------- -The libraries are [documented in LDoc][github.io]. Pre-built HTML -files are included in the release. +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 @@ -96,7 +95,7 @@ Bug reports and code contributions These libraries are written and maintained by their users. -Please make bug report and suggestions as [GitHub Issues][issues]. +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 @@ -104,4 +103,12 @@ 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. 2-character indentation using SPACES in Lua sources. + [issues]: http://github.com/lua-stdlib/lua-stdlib/issues From 728f7229bffe135d2a8a177d9f371a010b785c83 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 3 Oct 2015 21:16:49 +0100 Subject: [PATCH 615/703] refactor: move require and assert from std.base to std proper. Everything loads 'std.base' so we should keep it as small as possible. * lib/std/base.lua (assert, require, vcompare): Move from here... * lib/std.lua.in (assert, require, vcompare): ...to here. * lib/std/delete-after/2016-01-03.lua (assert, compare, require) (split, vcompare): Copy to here too, because including 'std' would create a require loop. Plus this whole file is scheduled for deletion in a few months. Signed-off-by: Gary V. Vaughan --- lib/std.lua.in | 37 +++++++++++++- lib/std/base.lua | 33 +----------- lib/std/delete-after/2016-01-03.lua | 78 ++++++++++++++++++++++++++++- 3 files changed, 113 insertions(+), 35 deletions(-) diff --git a/lib/std.lua.in b/lib/std.lua.in index b122fad..8ddeec3 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -21,14 +21,19 @@ local _G = _G +local error = error local ipairs = ipairs local pairs = pairs local pcall = pcall local rawset = rawset local require = require local setmetatable = setmetatable +local tostring = tostring local type = type +local string_format = string.format +local string_match = string.match + local _ = { debug = require "std.debug", @@ -36,13 +41,13 @@ local _ = { std = require "std.base", } -local _assert = _.std.assert local _ipairs = _.std.ipairs local _pairs = _.std.pairs local _require = _.std.require local _tostring = _.std.tostring local _type = _.std.type local argscheck = _.debug.argscheck +local compare = _.std.list.compare local copy = _.std.base.copy local elems = _.std.elems local eval = _.std.eval @@ -52,6 +57,7 @@ local ireverse = _.std.ireverse local npairs = _.std.npairs local ripairs = _.std.ripairs local rnpairs = _.std.rnpairs +local split = _.std.string.split local _, _ENV = nil, _.strict {} @@ -71,6 +77,12 @@ local function monkey_patch (namespace) end +local function _assert (expect, fmt, arg1, ...) + local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or "" + return expect or error (msg, 2) +end + + local function barrel (namespace) namespace = namespace or _G @@ -107,6 +119,29 @@ local function barrel (namespace) end +local function vcompare (a, b) + return compare (split (a, "%."), split (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 = string_match (s, pattern) + 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. ]]-- diff --git a/lib/std/base.lua b/lib/std/base.lua index e661563..4a57f81 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -30,7 +30,6 @@ local loadstring = loadstring or load local next = next local pairs = pairs local rawget = rawget -local require = require local select = select local setmetatable = setmetatable local tonumber = tonumber @@ -61,19 +60,13 @@ local _ENV = require "std.strict" {} -- Forward declarations for Helper functions below. -local argerror, getmetamethod, len, vcompare +local argerror, getmetamethod, len -- 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 function assert (expect, fmt, arg1, ...) - local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or "" - return expect or error (msg, 2) -end - - 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 @@ -113,23 +106,6 @@ local maxn = table_maxn or function (t) end -local _require = require - -local function require (module, min, too_big, pattern) - local m = _require (module) - local v = tostring (type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") - 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 - - --[[ ============================ ]]-- --[[ Shared Stdlib API functions. ]]-- @@ -629,11 +605,6 @@ getmetamethod = function (x, n) end -vcompare = function (a, b) - return compare (split (a, "%."), split (b, "%.")) -end - - --[[ ============= ]]-- --[[ Internal API. ]]-- @@ -648,7 +619,6 @@ end -- public API here too, which means everything looks relatively normal -- when importing the functions into stdlib implementation modules. return { - assert = assert, elems = elems, eval = eval, getmetamethod = getmetamethod, @@ -657,7 +627,6 @@ return { ireverse = ireverse, npairs = npairs, pairs = pairs, - require = require, ripairs = ripairs, rnpairs = rnpairs, diff --git a/lib/std/delete-after/2016-01-03.lua b/lib/std/delete-after/2016-01-03.lua index e8cd0e4..2937908 100644 --- a/lib/std/delete-after/2016-01-03.lua +++ b/lib/std/delete-after/2016-01-03.lua @@ -22,14 +22,23 @@ local M = false if not require "std.debug_init"._DEBUG.deprecate then + local assert = assert + local error = error local getmetatable = getmetatable local pairs = pairs + local require = require + local tonumber = tonumber + local tostring = tostring local type = type local coroutine_yield = coroutine.yield local coroutine_wrap = coroutine.wrap local math_ceil = math.ceil + local math_min = math.min local math_max = math.max + local string_find = string.find + local string_format = string.format + local table_insert = table.insert local table_unpack = table.unpack or unpack local _, deprecated = { @@ -48,10 +57,8 @@ if not require "std.debug_init"._DEBUG.deprecate then -- std.operator any time in the next year or so... local operator = require "std.operator" - local _assert = _.std.assert local _ipairs = _.std.ipairs local _pairs = _.std.pairs - local _require = _.std.require local _tostring = _.std.tostring local DEPRECATED = _.maturity.DEPRECATED local eval = _.std.eval @@ -67,6 +74,13 @@ if not require "std.debug_init"._DEBUG.deprecate then --[[ Death Row! ]]-- --[[ ========== ]]-- + + local function _assert (expect, fmt, arg1, ...) + local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or "" + return expect or error (msg, 2) + end + + local function callable (x) if type (x) == "function" then return x end return (getmetatable (x) or {}).__call @@ -85,6 +99,28 @@ if not require "std.debug_init"._DEBUG.deprecate then end + local function compare (l, m) + local lenl, lenm = len (l), len (m) + for i = 1, math_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 depair (proto, ls) local t = {} for _, v in _ipairs (ls) do @@ -282,6 +318,24 @@ if not require "std.debug_init"._DEBUG.deprecate then end + local function split (s, sep) + local r, patt = {} + if sep == "" then + patt = "(.)" + table_insert (r, "") + else + patt = "(.-)" .. (sep or "%s+") + end + local b, slen = 0, len (s) + while b <= slen do + local e, n, m = string_find (s, patt, b + 1) + table_insert (r, m or s:sub (b + 1, slen)) + b = n or slen + 1 + end + return r + end + + local function totable (x) local m = getmetamethod (x, "__totable") if m then @@ -312,6 +366,26 @@ if not require "std.debug_init"._DEBUG.deprecate then end + local function vcompare (a, b) + return compare (split (a, "%."), split (b, "%.")) + end + + + local function _require (module, min, too_big, pattern) + local m = require (module) + local v = tostring (type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") + 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 + + local function zip_with (proto, ls, fn) return map_with (proto, fn, transpose (proto, ls)) end From b901bee777b03836173b43749e155fd6c039d811 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 4 Oct 2015 16:50:37 +0100 Subject: [PATCH 616/703] functional: new any function. * specs/functional_spec.yaml (any): Add examples for correct behaviour of this function. * lib/std/functional.lua (any): Implement it to satisfy behaviours. * NEWS.md (New features): Update accordingly. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++++ lib/std/functional.lua | 24 ++++++++++++++++++++++++ specs/functional_spec.yaml | 25 ++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 501828d..72f2ab4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -114,6 +114,10 @@ assert (a == nil and b == false and c == nil) ``` + - New `functional.any` returns a function that calls each of the + passed functions with the same arguments, stopping and returning the + result from the first of those calls that does not equal `nil`. + - New `functional.product` returns a list of combinations made by taking one element from each of the argument lists. See LDocs for an example. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 3757f83..9ddebfc 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -51,6 +51,22 @@ local _, _ENV = nil, _.strict {} --[[ =============== ]]-- +local function any (...) + local fns = {...} + + return function (...) + local argt = {} + for _, fn in npairs (fns) do + argt = {fn (...)} + if argt[1] ~= nil then + return unpack (argt) + end + end + return unpack (argt) + end +end + + local function bind (fn, bound) return function (...) local argt, i = copy (bound), 1 @@ -333,6 +349,14 @@ local function X (decl, fn) end local M = { + --- Call a series of functions until one returns non-nil. + -- @function any + -- @func ... functions to call + -- @treturn function to call fn1 .. fnN until one returns non-nil. + -- @usage + -- old_object_type = any (std.object.type, io.type, type) + any = X ("any (func...)", any), + --- Partially apply a function. -- @function bind -- @func fn function to apply partially diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index c285731..039d650 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -4,7 +4,7 @@ before: this_module = "std.functional" global_table = "_G" - exported_apis = { "bind", "callable", "case", "collect", "compose", + exported_apis = { "any", "bind", "callable", "case", "collect", "compose", "cond", "curry", "eval", "filter", "fold", "foldl", "foldr", "id", "lambda", "map", "map_with", "memoize", "nop", "op", "product", "reduce", "zip", "zip_with" } @@ -34,6 +34,29 @@ specify std.functional: to_equal {} +- describe any: + - before: + stop = function () return true end + fail = function () return false end + + f = M.any + + - context with bad arguments: + badargs.diagnose (f, "std.functional.any (func*)") + + - it returns a single function: + expect (type (f (M.id))).to_be "function" + - it propagates arguments and returned results: + expect (f (M.id) (true)).to_be (true) + expect ({f (M.id) (1, 2, 3)}).to_equal {1, 2, 3} + - it calls all functions until one returns non-nil: + expect (f (M.id, M.id, stop, fail) (nil)).to_be (true) + - it only looks at first returned value: + expect (f (M.id, M.id, stop, fail) (nil, "ignored")).to_be (true) + - it does not call remaining functions after non-nil return: + expect (f (M.id, fail) (true)).to_be (true) + + - describe bind: - before: op = require "std.operator" From 3cc3e37184215335a1383685b46775b1a3ee1c35 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 4 Oct 2015 16:22:19 +0100 Subject: [PATCH 617/703] std: rationalize `std.object.type` to work like `io.type` etc. * specs/std_spec.yaml (type): Remove behaviours for type. * specs/object_spec.yaml (type): Adjust examples to demonstrate deprecation warnings, backwards compatible behaviours and new similified behaviour. * lib/std/base.lua (type): Remove. * lib/std.lua.in (type): Remove. * NEWS.md (New features): Remove std.type announcement. * lib/std/object.lua (type): Simplify. Return _type metatable field only, and rely on the caller to qualify with type themselves. * lib/std/delete-after/a-year.lua (deprecate.methods.object.type) (deprecate.methods.object.prototype): Provide backwards compatible object methods with deprecation warnings. (deprecate.object.type): Provide backwards compatible module function, with deprecation warning when called on non-table. * lib/std/debug.lua (_type): New module local composition of type functions for argcheck stack. * lib/std/set.lua, lib/std/tree.lua, lib/std/tuple.lua (__tostring): Use getmetatable to access _type metafield instead of calling out to removed std.type function. * NEWS.md (Deprecations, Incompatible changes): Update accordingly. Signed-off-by: Gary V. Vaughan --- NEWS.md | 19 ++++++------ lib/std.lua.in | 8 ----- lib/std/base.lua | 5 ---- lib/std/debug.lua | 8 +++-- lib/std/delete-after/a-year.lua | 30 +++++++++++++++++-- lib/std/object.lua | 22 +++++++------- lib/std/set.lua | 3 +- lib/std/tree.lua | 9 +++++- lib/std/tuple.lua | 3 +- specs/object_spec.yaml | 53 +++++++++++++++++++++++++-------- specs/std_spec.yaml | 20 +------------ 11 files changed, 105 insertions(+), 75 deletions(-) diff --git a/NEWS.md b/NEWS.md index 72f2ab4..06b27dc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,9 +28,6 @@ _DEBUG.deprecate = true ``` - - For orthogonality with core Lua `type`, we now export the - `std.object.type` function as `std.type`. - - Objects and Modules are no longer conflated - what you get back from a `require "std.something"` is now ALWAYS a module: @@ -205,19 +202,23 @@ - Now that the `prototype` field is used to reference a module's object prototype, `std.object.prototype` no longer return the object - type of an argument. Use either `std.type` or the type function from - an instantiated object: + type of an argument. Additionally, for orthogonality with the way Lua + itself uses `io.type` and `math.type` to get more detail about certain + objects than `type` itself, `std.object.type` now operates purely on + stdlib objects with a `_type` metatable field, and returns `nil` for + anything else. + + To replicate the old behaviour, use this: ```lua - local stdtype = require "std.type" - local Object = require "std.object".prototype - local objtype = Object.type + local std = require "std" + local object_type = std.functional.any (std.object.type, io.type, type) ``` - Objects no longer honor mangling and stripping `_functions` tables from objects during instantiation, instead move your actual object into the module `prototype` field, and add the module functions to - the parent table returned whn the module is required. + the parent table returned when the module is required. - `functional.lambda` no longer returns a bare function, but a functable that can be called and stringified. diff --git a/lib/std.lua.in b/lib/std.lua.in index 8ddeec3..59db0a9 100644 --- a/lib/std.lua.in +++ b/lib/std.lua.in @@ -45,7 +45,6 @@ local _ipairs = _.std.ipairs local _pairs = _.std.pairs local _require = _.std.require local _tostring = _.std.tostring -local _type = _.std.type local argscheck = _.debug.argscheck local compare = _.std.list.compare local copy = _.std.base.copy @@ -203,13 +202,6 @@ M = { -- print (std.tostring {foo="bar","baz"}) tostring = X ("tostring (?any)", _tostring), - --- Type of an object, or primitive. - -- @function type - -- @param x anything - -- @treturn string type of *x* - -- @see std.object.type - type = X ("type (?any)", _type), - --- Module Functions -- @section modulefuncs diff --git a/lib/std/base.lua b/lib/std/base.lua index 4a57f81..1263022 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -40,7 +40,6 @@ local coroutine_wrap = coroutine.wrap local coroutine_yield = coroutine.yield local math_huge = math.huge local math_min = math.min -local io_type = io.type local string_find = string.find local string_format = string.format local table_concat = table.concat @@ -632,10 +631,6 @@ return { tostring = function (x) return render (x, tostring_vtable) end, - type = function (x) - return (getmetatable (x) or {})._type or io_type (x) or type (x) - end, - base = { copy = copy, keysort = keysort, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index f55c226..b2a688a 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -58,7 +58,6 @@ local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs local _tostring = _.std.tostring -local _type = _.std.type local argerror = _.std.debug.argerror local copy = _.std.base.copy local insert = _.std.table.insert @@ -283,8 +282,13 @@ local function concat (alternatives) end +local function _type (x) + return (getmetatable (x) or {})._type or io_type (x) or type (x) +end + + local function extramsg_mismatch (expectedtypes, actual, index) - local actualtype = _type (actual) + local actualtype = _type (actual) or type (actual) -- Tidy up actual type for display. if actualtype == "nil" then diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 864dcdd..6dc92c8 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -23,9 +23,13 @@ local M = false if not require "std.debug_init"._DEBUG.deprecate then + local getmetatable = getmetatable local pairs = pairs local type = type + local io_stderr = io.stderr + local io_type = io.type + local _, deprecated = { -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", @@ -39,8 +43,8 @@ if not require "std.debug_init"._DEBUG.deprecate then local _pairs = _.std.pairs - local _type = _.std.type local DEPRECATED = _.maturity.DEPRECATED + local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG local len = _.std.operator.len local sortkeys = _.std.base.sortkeys @@ -59,6 +63,11 @@ if not require "std.debug_init"._DEBUG.deprecate then end + local function _type (x) + return (getmetatable (x) or {})._type or io_type (x) or type (x) + end + + -- Ensure deprecated APIs observe _DEBUG warning standards. local function X (old, new, fn) return DEPRECATED (RELEASE, "'std." .. old .. "'", "use '" .. new .. "' instead", fn) @@ -78,14 +87,29 @@ if not require "std.debug_init"._DEBUG.deprecate then M = acyclic_merge ({ object = { - prototype = X ("object.prototype", "std.type", _type), - type = X ("object.type", "std.type", _type), + type = function (x) + local r = (getmetatable (x) or {})._type + if r == nil then + io_stderr:write (DEPRECATIONMSG (RELEASE, + "non-object argument to 'std.object.type'", + [[check for 'type (x) == "table"' before calling 'std.object.type (x)' instead]], + 2)) + end + return r or io_type (x) or type (x) + end, }, table = { len = X ("table.len", "std.operator.len", len), okeys = DEPRECATED (RELEASE, "'std.table.okeys'", "compose 'std.table.keys' and 'std.table.sort' instead", okeys), }, + + methods = { + object = { + prototype = DEPRECATED (RELEASE, "'std.object.prototype'", "use 'std.functional.any (std.object.type, io.type, type)' instead", _type), + type = DEPRECATED (RELEASE, "'std.object.type'", "use 'std.functional.any (std.object.type, io.type, type)' instead", _type), + }, + }, }, deprecated) diff --git a/lib/std/object.lua b/lib/std/object.lua index 610a024..9d3af83 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -22,9 +22,13 @@ ]] +local getmetatable = getmetatable + + local _ = { container = require "std.container", debug = require "std.debug", + maturity = require "std.maturity", std = require "std.base", strict = require "std.strict", } @@ -32,7 +36,6 @@ local _ = { local Container = _.container.prototype local Module = _.std.object.Module -local _type = _.std.type local argscheck = _.debug.argscheck local getmetamethod = _.std.getmetamethod local mapfields = _.std.object.mapfields @@ -80,14 +83,6 @@ local methods = { -- } clone = getmetamethod (Container, "__call"), - --- Type of this object. - -- @function prototype:type - -- @treturn string type of this object. - -- @see std.type - -- @usage - -- assert (Object:type () == getmetatable (Object)._type) - type = X ("type (?any)", _type), - --- Object Functions -- @section objfunctions @@ -111,7 +106,7 @@ local methods = { if deprecated then - methods = merge (methods, deprecated.object) + methods = merge (methods, deprecated.methods.object) end @@ -147,11 +142,14 @@ local prototype = Container { local M = { - prototype = prototype, + prototype = prototype, + type = function (x) return (getmetatable (x) or {})._type end, } if deprecated then - M = merge (M, deprecated.object) + -- Yes, we really are overwriting the new fast object.type with the + -- deprecation warning backwards compatible version here! + M = merge (deprecated.object, M) end diff --git a/lib/std/set.lua b/lib/std/set.lua index bc843da..9d518ff 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -45,7 +45,6 @@ local Module = _.std.object.Module local _pairs = _.std.pairs local _tostring = _.std.tostring -local _type = _.std.type local argscheck = _.debug.argscheck local pickle = _.std.string.pickle @@ -266,7 +265,7 @@ prototype = Container { keys[#keys + 1] = _tostring (k) end table_sort (keys) - return _type (self) .. " {" .. table_concat (keys, ", ") .. "}" + return getmetatable (self)._type .. " {" .. table_concat (keys, ", ") .. "}" end, --- Return a loadable serialization of this object, where possible. diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 041c6ea..099d203 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -48,7 +48,6 @@ local Module = _.std.object.Module local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local _type = _.std.type local argscheck = _.debug.argscheck local get = _.operator.get local ielems = _.std.ielems @@ -145,6 +144,14 @@ local function X (decl, fn) end +--- Return the object type, if set, otherwise the Lua type. +-- @param x item to act on +-- @treturn string object type of *x*, otherwise `type (x)` +local function _type (x) + return (getmetatable (x) or {})._type or type (x) +end + + --- Tree prototype object. -- @object prototype -- @string[opt="Tree"] _type object name diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 019a715..19fa5f5 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -47,7 +47,6 @@ local _ = { local Container = _.container.prototype local Module = _.std.object.Module -local _type = _.std.type local pickle = _.std.string.pickle local toqstring = _.std.base.toqstring @@ -145,7 +144,7 @@ local prototype = Container { -- print (Tuple ("nil", nil, false)) __tostring = function (self) local _, argstr = next (self) - return string_format ("%s (%s)", _type (self), argstr) + return string_format ("%s (%s)", getmetatable (self)._type, argstr) end, --- Unpack tuple values between index *i* and *j*, inclusive. diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index a2c8420..67c77a9 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -88,6 +88,7 @@ specify std.object: expect (fn "0.0").to_be "string" expect (fn (function () end)).to_be "function" expect (fn {}).to_be "table" + - context when called as an object method: - it reports the type stored in the object's metatable: expect (o:prototype ()).to_be "Object" @@ -109,6 +110,12 @@ specify std.object: fn = object.type - context when called from the object module: + - it writes an argument passing deprecation warning: + expect (deprecate_on ("type", "{}")). + to_contain_error "non-object argument to 'std.object.type' was deprecated" + expect (deprecate_off ("type", "{}")). + not_to_contain_error "was deprecated" + - it reports the type stored in the object's metatable: expect (fn (o)).to_be "Object" - it reports the type of a cloned object: @@ -121,19 +128,29 @@ specify std.object: p = Portal {} expect (fn (p)).to_be "Demon" expect (fn (p {})).to_be "Demon" - - it recognizes a file object: - h = io.open (os.tmpname ()) - expect (fn (h)).to_be "file" - h:close () - expect (fn (h)).to_be "closed file" - - it recognizes a primitive object: - expect (fn (nil)).to_be "nil" - expect (fn (false)).to_be "boolean" - expect (fn (0.0)).to_be "number" - expect (fn "0.0").to_be "string" - expect (fn (function () end)).to_be "function" - expect (fn {}).to_be "table" + - it returns nil for a primitive object: + # Have to use a separate process, because we already loaded + # debug_init and thus deprecated methods into this process + expect (deprecation ("true", this_module, "type", "nil")). + to_output "nil\n" + expect (deprecation ("true", this_module, "type", "false")). + to_output "nil\n" + expect (deprecation ("true", this_module, "type", "0.0")). + to_output "nil\n" + expect (deprecation ("true", this_module, "type", "'0.0'")). + to_output "nil\n" + expect (deprecation ("true", this_module, "type", "function () end")). + to_output "nil\n" + expect (deprecation ("true", this_module, "type", "{}")). + to_output "nil\n" + - context when called as an object method: + - it writes a deprecation warning: + expect (deprecate_on ("type", "", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("type", "", "{}")). + not_to_contain_error "was deprecated" + - it reports the type stored in the object's metatable: expect (o:type ()).to_be "Object" - it reports the type of a cloned object: @@ -146,6 +163,18 @@ specify std.object: p = Portal {} expect (p:type ()).to_be "Demon" expect ((p {}):type ()).to_be "Demon" + - it recognizes a file object: + h = io.open (os.tmpname ()) + expect (o.type (h)).to_be "file" + h:close () + expect (o.type (h)).to_be "closed file" + - it recognizes a primitive object: + expect (o.type (nil)).to_be "nil" + expect (o.type (false)).to_be "boolean" + expect (o.type (0.0)).to_be "number" + expect (o.type "0.0").to_be "string" + expect (o.type (function () end)).to_be "function" + expect (o.type {}).to_be "table" - describe instantiation from a prototype: diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index f137cbf..8e405d5 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -5,7 +5,7 @@ before: | exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", "ielems", "ipairs", "ireverse", "monkey_patch", "npairs", "pairs", "require", "ripairs", "rnpairs", - "tostring", "type", "version" } + "tostring", "version" } -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -602,21 +602,3 @@ specify std: expect (f { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }). to_be '{?=1,[]=1,_=0,a-key=1,word=0}' - -- describe type: - - before: f = M.type - - - it recognizes a primitive object: - expect (f (nil)).to_be (type (nil)) - expect (f (false)).to_be (type (false)) - expect (f (0.0)).to_be (type (0.0)) - expect (f "0.0").to_be (type "0.0") - expect (f (function () end)).to_be (type (function () end)) - expect (f {}).to_be (type {}) - - it recognizes a file object: - h = io.open (os.tmpname ()) - expect (f (h)).to_be (io.type (h)) - h:close () - expect (f (h)).to_be (io.type (h)) - - it recognizes _type metatable field: - expect (f (setmetatable ({}, {_type = "objet"}))).to_be "objet" From aadbbad064a34b2fdf859a171b089eba3a666ea1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 6 Oct 2015 00:05:12 +0100 Subject: [PATCH 618/703] configury: simplify file generation. * lib/std.lua.in (version): Move from here... * local.mk (lib/std/version.lua): ...to here. * lib/std.lua.in: Rename from here... * lib/std/init.lua: ...to here. (version): Remove declaration. Now autovivified from std.version. * local.mk (dist_lua_DATA): Remove. (EXTRA_DIST): Remove lib/std.lua.in. * build-aux/config.ld.in (file): Rename from ../lib/std.lua to ../lib/std/init.lua. * specs/std_spec.yaml (before): Undo cached autovivified version. (require): Reset _VERSION carefully. * specs/string_spec.yaml (require_version): Likewise. * .gitignore: Add lib/std/version.lua, remove lib/std.lua. Signed-off-by: Gary V. Vaughan --- .gitignore | 2 +- build-aux/config.ld.in | 2 +- lib/{std.lua.in => std/init.lua} | 1 - local.mk | 18 +++++++----------- specs/std_spec.yaml | 7 ++++--- specs/string_spec.yaml | 4 ++-- 6 files changed, 15 insertions(+), 19 deletions(-) rename lib/{std.lua.in => std/init.lua} (99%) diff --git a/.gitignore b/.gitignore index 9d796a7..3c58006 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ /config.status /configure /doc -/lib/std.lua +/lib/std/version.lua /luarocks /m4/ax_lua.m4 /m4/slingshot.m4 diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 8dd5e76..c9aea1e 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -42,7 +42,7 @@ dir = "." file = { -- Core Functions - "../lib/std.lua", + "../lib/std/init.lua", -- Core Libraries "../lib/std/debug.lua", diff --git a/lib/std.lua.in b/lib/std/init.lua similarity index 99% rename from lib/std.lua.in rename to lib/std/init.lua index 59db0a9..0586d58 100644 --- a/lib/std.lua.in +++ b/lib/std/init.lua @@ -154,7 +154,6 @@ end M = { --- Release version string. -- @field version - version = "General Lua libraries / @VERSION@", --- Core Functions diff --git a/local.mk b/local.mk index c85e826..24d08d4 100644 --- a/local.mk +++ b/local.mk @@ -61,10 +61,6 @@ include specs/specs.mk ## Build. ## ## ------ ## -dist_lua_DATA += \ - lib/std.lua \ - $(NOTHING_ELSE) - luastddir = $(luadir)/std dist_luastd_DATA = \ @@ -73,6 +69,7 @@ dist_luastd_DATA = \ lib/std/debug.lua \ lib/std/functional.lua \ lib/std/io.lua \ + lib/std/init.lua \ lib/std/list.lua \ lib/std/math.lua \ lib/std/maturity.lua \ @@ -87,6 +84,7 @@ dist_luastd_DATA = \ lib/std/table.lua \ lib/std/tree.lua \ lib/std/tuple.lua \ + lib/std/version.lua \ $(NOTHING_ELSE) luastddeletedir = $(luastddir)/delete-after @@ -110,12 +108,11 @@ dist_luastddebug_DATA = \ lib/std/debug_init/init.lua \ $(NOTHING_ELSE) -# In order to avoid regenerating std.lua at configure time, which -# causes the documentation to be rebuilt and hence requires users to -# have ldoc installed, put std/std.lua in as a Makefile dependency. -# (Strictly speaking, distributing an AC_CONFIG_FILE would be wrong.) -lib/std.lua: lib/std.lua.in - ./config.status --file=$@ +lib/std/version.lua: + echo 'return "General Lua libraries / $(VERSION)"' > $@T + @test -f $@ || cp -f $@T $@ + @cmp -s $@T $@ || cp -f $@T $@ + @rm -f $@T ## Use a builtin rockspec build with root at $(srcdir)/lib, and note @@ -129,7 +126,6 @@ mkrockspecs_args = --module-dir $(srcdir)/lib --repository lua-stdlib EXTRA_DIST += \ build-aux/config.ld.in \ - lib/std.lua.in \ $(NOTHING_ELSE) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 8e405d5..dd7ab96 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -5,7 +5,7 @@ before: | exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", "ielems", "ipairs", "ireverse", "monkey_patch", "npairs", "pairs", "require", "ripairs", "rnpairs", - "tostring", "version" } + "tostring" } -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -27,6 +27,7 @@ before: | }) M = require (this_module) + M.version = nil -- previous specs may have autoloaded it specify std: @@ -455,9 +456,9 @@ specify std: expect (f "std").to_be (require "std") - it uses _VERSION when version field is nil: std = require "std" - M._VERSION, M.version = M.version, M._VERSION + M._VERSION, M.version = M.version, nil expect (f ("std", "41", "9999")).to_be (require "std") - M._VERSION, M.version = M.version, M._VERSION + M._VERSION, M.version = nil, M._VERSION - context with semantic versioning: - before: std = require "std" diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 491876e..022fa58 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -570,9 +570,9 @@ specify std.string: expect (f "std").to_be (require "std") - it uses _VERSION when version field is nil: std = require "std" - std._VERSION, std.version = std.version, std._VERSION + std._VERSION, std.version = std.version, nil expect (f ("std", "41", "9999")).to_be (require "std") - std._VERSION, std.version = std.version, std._VERSION + std._VERSION, std.version = nil, std._VERSION - context with semantic versioning: - before: std = require "std" From ceaf0ccb531506a38ea91de512b8f83c14deec86 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 6 Oct 2015 21:52:08 +0100 Subject: [PATCH 619/703] table: move flatten and shape functions to functional module. * specs/table_spec.yaml (flatten, shape): Check for deprecation warning. (monkey_patch): Don't expect flatten and shape implementations in the target namespace. * specs/std_spec.yaml (barrel): Adjust for differences in table.monkey_patch. * specs/functional_spec.yaml (flatten, shape): Check for normal operation of these functions. * lib/std/base.lua (collect): Move from here... * lib/std/table.lua (flatten, shape): ...and here... * lib/std/functional.lua (collect, flatten, shape): ...to here. * lib/std/delete-after/a-year.lua (flatten, shape): Deprecation warning enabled versions of these two functions. * NEWS.md (Depractions): Update accordingly. Signed-off-by: Gary V. Vaughan --- NEWS.md | 6 ++ lib/std/base.lua | 26 -------- lib/std/delete-after/a-year.lua | 72 +++++++++++++++++++++- lib/std/functional.lua | 104 +++++++++++++++++++++++++++++++- lib/std/table.lua | 76 ----------------------- specs/functional_spec.yaml | 54 ++++++++++++++++- specs/std_spec.yaml | 6 +- specs/table_spec.yaml | 25 +++++--- 8 files changed, 250 insertions(+), 119 deletions(-) diff --git a/NEWS.md b/NEWS.md index 06b27dc..749ca61 100644 --- a/NEWS.md +++ b/NEWS.md @@ -154,6 +154,12 @@ orthogonality with core Lua, we're going back to using `std.object.type`, because that just makes more sense. Sorry! + - `std.table.flatten` and `std.table.shape` have been deprecated in + favour of `std.functional.flatten` and `std.functional.shape` + because these functions are far more useful in conjunction with a + functional programming style than with regular tables in imperative + code. + - `std.table.len` has been deprecated in favour of `std.operator.len`, because it is not just for tables! diff --git a/lib/std/base.lua b/lib/std/base.lua index 1263022..e3dd8eb 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -322,31 +322,6 @@ local function unpack (t, i, j) end -local function collect (ifn, ...) - local argt, r = {...}, {} - if not callable (ifn) then - ifn, argt = npairs, {ifn, ...} - end - - -- How many return values from ifn? - local arity = 1 - for e, v in ifn (unpack (argt)) do - if v then arity, r = 2, {} break end - -- Build an arity-1 result table on first pass... - r[#r + 1] = e - end - - if arity == 2 then - -- ...oops, it was arity-2 all along, start again! - for k, v in ifn (unpack (argt)) do - r[k] = v - end - end - - return r -end - - local function reduce (fn, d, ifn, ...) local argt = {...} if not callable (ifn) then @@ -648,7 +623,6 @@ return { functional = { callable = callable, - collect = collect, nop = function () end, reduce = reduce, }, diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 6dc92c8..dd03169 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -29,6 +29,8 @@ if not require "std.debug_init"._DEBUG.deprecate then local io_stderr = io.stderr local io_type = io.type + local math_ceil = math.ceil + local table_unpack = table.unpack or unpack local _, deprecated = { -- Adding anything else here will probably cause a require loop. @@ -42,20 +44,50 @@ if not require "std.debug_init"._DEBUG.deprecate then if not _.ok then deprecated = {} end + local _ipairs = _.std.ipairs local _pairs = _.std.pairs local DEPRECATED = _.maturity.DEPRECATED local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG + local leaves = _.std.tree.leaves local len = _.std.operator.len local sortkeys = _.std.base.sortkeys -- Only the above symbols are used below this line. local _, _ENV = nil, _.strict {} - + --[[ ========== ]]-- --[[ Death Row! ]]-- --[[ ========== ]]-- + + local function collect (ifn, ...) + local argt, r = {...}, {} + + -- How many return values from ifn? + local arity = 1 + for e, v in ifn (table_unpack (argt)) do + if v then arity, r = 2, {} break end + -- Build an arity-1 result table on first pass... + r[#r + 1] = e + end + + if arity == 2 then + -- ...oops, it was arity-2 all along, start again! + for k, v in ifn (table_unpack (argt)) do + r[k] = v + end + end + + return r + end + + + local function flatten (t) + return collect (leaves, _ipairs, t) + end + + local function okeys (t) local r = {} for k in _pairs (t) do r[#r + 1] = k end @@ -63,6 +95,42 @@ if not require "std.debug_init"._DEBUG.deprecate then end + local function shape (dims, t) + t = flatten (t) + -- Check the shape and calculate the size of the zero, if any + local size = 1 + local zero + for i, v in _ipairs (dims) do + if v == 0 then + if zero then -- bad shape: two zeros + return nil + else + zero = i + end + else + size = size * v + end + end + if zero then + dims[zero] = math_ceil (len (t) / size) + end + local function fill (i, d) + if d > len (dims) then + return t[i], i + 1 + else + local r = {} + for j = 1, dims[d] do + local e + e, i = fill (i, d + 1) + r[#r + 1] = e + end + return r, i + end + end + return (fill (1, 1)) + end + + local function _type (x) return (getmetatable (x) or {})._type or io_type (x) or type (x) end @@ -100,8 +168,10 @@ if not require "std.debug_init"._DEBUG.deprecate then }, table = { + flatten = X ("table.flatten", "functional.flatten", flatten), len = X ("table.len", "std.operator.len", len), okeys = DEPRECATED (RELEASE, "'std.table.okeys'", "compose 'std.table.keys' and 'std.table.sort' instead", okeys), + shape = X ("table.shape", "functional.shape", shape), }, methods = { diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 9ddebfc..b52a197 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -14,6 +14,7 @@ local pcall = pcall local select = select local setmetatable = setmetatable +local math_ceil = math.ceil local table_remove = table.remove @@ -23,10 +24,10 @@ local _ = { strict = require "std.strict", } +local _ipairs = _.std.ipairs local _pairs = _.std.pairs local argscheck = _.debug.argscheck local callable = _.std.functional.callable -local collect = _.std.functional.collect local copy = _.std.base.copy local ielems = _.std.ielems local ireverse = _.std.ireverse @@ -37,6 +38,7 @@ local nop = _.std.functional.nop local npairs = _.std.npairs local reduce = _.std.functional.reduce local render = _.std.string.render +local leaves = _.std.tree.leaves local unpack = _.std.table.unpack @@ -88,6 +90,31 @@ local function case (with, branches) end +local function collect (ifn, ...) + local argt, r = {...}, {} + if not callable (ifn) then + ifn, argt = npairs, {ifn, ...} + end + + -- How many return values from ifn? + local arity = 1 + for e, v in ifn (unpack (argt)) do + if v then arity, r = 2, {} break end + -- Build an arity-1 result table on first pass... + r[#r + 1] = e + end + + if arity == 2 then + -- ...oops, it was arity-2 all along, start again! + for k, v in ifn (unpack (argt)) do + r[k] = v + end + end + + return r +end + + local function compose (...) local fns = {...} @@ -169,6 +196,11 @@ local function filter (pfn, ifn, ...) end +local function flatten (t) + return collect (leaves, _ipairs, t) +end + + local function foldl (fn, d, t) if t == nil then local tail = {} @@ -321,6 +353,42 @@ local function product (...) end +local function shape (dims, t) + t = flatten (t) + -- Check the shape and calculate the size of the zero, if any + local size = 1 + local zero + for i, v in _ipairs (dims) do + if v == 0 then + if zero then -- bad shape: two zeros + return nil + else + zero = i + end + else + size = size * v + end + end + if zero then + dims[zero] = math_ceil (len (t) / size) + end + local function fill (i, d) + if d > len (dims) then + return t[i], i + 1 + else + local r = {} + for j = 1, dims[d] do + local e + e, i = fill (i, d + 1) + r[#r + 1] = e + end + return r, i + end + end + return (fill (1, 1)) +end + + local function zip (tt) local r = {} for outerk, inner in _pairs (tt) do @@ -466,6 +534,15 @@ local M = { -- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) filter = X ("filter (func, [func], any...)", filter), + --- Flatten a nested table into a list. + -- @function flatten + -- @tparam table t a table + -- @treturn table a list of all non-table elements of *t* + -- @usage + -- --> {1, 2, 3, 4, 5} + -- flatten {{1, {{2}, 3}, 4}, 5} + flatten = X ("flatten (table)", flatten), + --- Fold a binary function left associatively. -- If parameter *d* is omitted, the first element of *t* is used, -- and *t* treated as if it had been passed without that element. @@ -599,6 +676,31 @@ local M = { -- reduce (std.operator.pow, 2, std.ielems, {3, 4}) reduce = X ("reduce (func, any, [func], any...)", reduce), + --- Shape a table according to a list of dimensions. + -- + -- Dimensions are given outermost first and items from the original + -- list are distributed breadth first; there may be one 0 indicating + -- an indefinite number. Hence, `{0}` is a flat list, + -- `{1}` is a singleton, `{2, 0}` is a list of + -- two lists, and `{0, 2}` is a list of pairs. + -- + -- Algorithm: turn shape into all positive numbers, calculating + -- the zero if necessary and making sure there is at most one; + -- recursively walk the shape, adding empty tables until the bottom + -- level is reached at which point add table items instead, using a + -- counter to walk the flattened original list. + -- + -- @todo Use ileaves instead of flatten (needs a while instead of a + -- for in fill function) + -- @function shape + -- @tparam table dims table of dimensions `{d1, ..., dn}` + -- @tparam table t a table of elements + -- @return reshaped list + -- @usage + -- --> {{"a", "b"}, {"c", "d"}, {"e", "f"}} + -- shape ({3, 2}, {"a", "b", "c", "d", "e", "f"}) + shape = X ("shape (table, table)", shape), + --- Zip a table of tables. -- Make a new table, with lists of elements at the same index in the -- original table. This function is effectively its own inverse. diff --git a/lib/std/table.lua b/lib/std/table.lua index 3c237dc..bf1476a 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -17,7 +17,6 @@ local setmetatable = setmetatable local table = table local type = type -local math_ceil = math.ceil local math_min = math.min @@ -105,11 +104,6 @@ local function enpair (t) end -local function flatten (t) - return collect (leaves, _ipairs, t) -end - - local function keys (t) local l = {} for k in _pairs (t) do @@ -136,42 +130,6 @@ local function project (fkey, tt) end -local function shape (dims, t) - t = flatten (t) - -- Check the shape and calculate the size of the zero, if any - local size = 1 - local zero - for i, v in _ipairs (dims) do - if v == 0 then - if zero then -- bad shape: two zeros - return nil - else - zero = i - end - else - size = size * v - end - end - if zero then - dims[zero] = math_ceil (len (t) / size) - end - local function fill (i, d) - if d > len (dims) then - return t[i], i + 1 - else - local r = {} - for j = 1, dims[d] do - local e - e, i = fill (i, d + 1) - r[#r + 1] = e - end - return r, i - end - end - return (fill (1, 1)) -end - - local function size (t) local n = 0 for _ in _pairs (t) do @@ -350,15 +308,6 @@ M = { -- @usage if empty (t) then error "ohnoes" end empty = X ("empty (table)", function (t) return not next (t) end), - --- Flatten a nested table into a list. - -- @function flatten - -- @tparam table t a table - -- @treturn table a list of all non-table elements of *t* - -- @usage - -- --> {1, 2, 3, 4, 5} - -- flatten {{1, {{2}, 3}, 4}, 5} - flatten = X ("flatten (table)", flatten), - --- Make a table with a default value for unset keys. -- @function new -- @param[opt=nil] x default entry value @@ -386,31 +335,6 @@ M = { -- project ("xx", {{"a", xx=1, yy="z"}, {"b", yy=2}, {"c", xx=3}, {xx="yy"}) project = X ("project (any, list of tables)", project), - --- Shape a table according to a list of dimensions. - -- - -- Dimensions are given outermost first and items from the original - -- list are distributed breadth first; there may be one 0 indicating - -- an indefinite number. Hence, `{0}` is a flat list, - -- `{1}` is a singleton, `{2, 0}` is a list of - -- two lists, and `{0, 2}` is a list of pairs. - -- - -- Algorithm: turn shape into all positive numbers, calculating - -- the zero if necessary and making sure there is at most one; - -- recursively walk the shape, adding empty tables until the bottom - -- level is reached at which point add table items instead, using a - -- counter to walk the flattened original list. - -- - -- @todo Use ileaves instead of flatten (needs a while instead of a - -- for in fill function) - -- @function shape - -- @tparam table dims table of dimensions `{d1, ..., dn}` - -- @tparam table t a table of elements - -- @return reshaped list - -- @usage - -- --> {{"a", "b"}, {"c", "d"}, {"e", "f"}} - -- shape ({3, 2}, {"a", "b", "c", "d", "e", "f"}) - shape = X ("shape (table, table)", shape), - --- Find the number of elements in a table. -- @function size -- @tparam table t any table diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 039d650..4ee18ae 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -5,9 +5,10 @@ before: global_table = "_G" exported_apis = { "any", "bind", "callable", "case", "collect", "compose", - "cond", "curry", "eval", "filter", "fold", "foldl", - "foldr", "id", "lambda", "map", "map_with", "memoize", - "nop", "op", "product", "reduce", "zip", "zip_with" } + "cond", "curry", "eval", "filter", "flatten", "fold", + "foldl", "foldr", "id", "lambda", "map", "map_with", + "memoize", "nop", "op", "product", "reduce", "shape", + "zip", "zip_with" } setdebug { deprecate = false } @@ -286,6 +287,23 @@ specify std.functional: to_equal {"first", last="three"} +- describe flatten: + - before: + t = {{{"one"}}, "two", {{"three"}, "four"}} + + f = M.flatten + + - context with bad arguments: + badargs.diagnose (f, "std.functional.flatten (table)") + + - it returns a table: + expect (type (f (t))).to_be "table" + - it works for an empty table: + expect (f {}).to_equal {} + - it flattens a nested table: + expect (f (t)).to_equal {"one", "two", "three", "four"} + + - describe fold: - before: op = require "std.operator" @@ -774,6 +792,36 @@ specify std.functional: expect (f (rawset, {}, {"one", two=5})).to_equal {"one", two=5} +- describe shape: + - before: + l = {1, 2, 3, 4, 5, 6} + + f = M.shape + + - context with bad arguments: + badargs.diagnose (f, "std.functional.shape (table, table)") + + - it returns a table: + expect (objtype (f ({2, 3}, l))).to_be "table" + - it works for an empty table: + expect (f ({0}, {})).to_equal ({}) + - it returns the result in a new table: + expect (f ({2, 3}, l)).not_to_be (l) + - it does not perturb the argument table: + f ({2, 3}, l) + expect (l).to_equal {1, 2, 3, 4, 5, 6} + - it reshapes a table according to given dimensions: + expect (f ({2, 3}, l)). + to_equal ({{1, 2, 3}, {4, 5, 6}}) + expect (f ({3, 2}, l)). + to_equal ({{1, 2}, {3, 4}, {5, 6}}) + - it treats 0-valued dimensions as an indefinite number: + expect (f ({2, 0}, l)). + to_equal ({{1, 2, 3}, {4, 5, 6}}) + expect (f ({0, 2}, l)). + to_equal ({{1, 2}, {3, 4}, {5, 6}}) + + - describe zip: - before: tt = {{1, 2}, {3, 4}, {5, 6}} diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index dd7ab96..4aac280 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -142,9 +142,9 @@ specify std: end - it installs std.table monkey patches: for _, api in ipairs { "clone", "clone_select", "depair", "empty", - "enpair", "flatten", "insert", "invert", "keys", "maxn", - "merge", "merge_select", "monkey_patch", "new", "pack", "project", - "shape", "size", "sort", "values" } + "enpair", "insert", "invert", "keys", "maxn", "merge", + "merge_select", "monkey_patch", "new", "pack", "project", + "size", "sort", "values" } do expect (namespace.table[api]).to_be (M.table[api]) end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 61376eb..5d85d45 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -4,11 +4,12 @@ before: | global_table = "_G" extend_base = { "clone", "clone_select", "depair", "empty", - "enpair", "flatten", "insert", "invert", "keys", - "maxn", "merge", "merge_select", "monkey_patch", - "new", "pack", "project", "remove", "shape", - "size", "sort", "unpack", "values" } - deprecations = { "len", "okeys", "metamethod", "ripairs", "totable" } + "enpair", "insert", "invert", "keys", "maxn", + "merge", "merge_select", "monkey_patch", "new", + "pack", "project", "remove", "size", "sort", + "unpack", "values" } + deprecations = { "flatten", "len", "okeys", "metamethod", "ripairs", + "shape", "totable" } setdebug { deprecate = false } @@ -177,8 +178,11 @@ specify std.table: f = M.flatten - - context with bad arguments: - badargs.diagnose (f, "std.table.flatten (table)") + - it writes a deprecation warning: + expect (deprecate_on ("ripairs", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("ripairs", "{}")). + not_to_contain_error "was deprecated" - it returns a table: expect (type (f (t))).to_be "table" @@ -631,8 +635,11 @@ specify std.table: f = M.shape - - context with bad arguments: - badargs.diagnose (f, "std.table.shape (table, table)") + - it writes a deprecation warning: + expect (deprecate_on ("shape", "{}, {}")). + to_contain_error "was deprecated" + expect (deprecate_off ("shape", "{}, {}")). + not_to_contain_error "was deprecated" - it returns a table: expect (objtype (f ({2, 3}, l))).to_be "table" From 99ad21b1c5f7f56b58344e1cc64366f87bbcfe44 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 6 Oct 2015 22:24:17 +0100 Subject: [PATCH 620/703] refactor: fix deprecation message extramsg default. * lib/std/delete-after/a-year.lua (X): Don't forget the leading `std.` on extramsg fallback. Signed-off-by: Gary V. Vaughan --- lib/std/delete-after/a-year.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index dd03169..bcbc3c1 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -138,7 +138,7 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Ensure deprecated APIs observe _DEBUG warning standards. local function X (old, new, fn) - return DEPRECATED (RELEASE, "'std." .. old .. "'", "use '" .. new .. "' instead", fn) + return DEPRECATED (RELEASE, "'std." .. old .. "'", "use 'std." .. new .. "' instead", fn) end local function acyclic_merge (dest, src) @@ -169,7 +169,7 @@ if not require "std.debug_init"._DEBUG.deprecate then table = { flatten = X ("table.flatten", "functional.flatten", flatten), - len = X ("table.len", "std.operator.len", len), + len = X ("table.len", "operator.len", len), okeys = DEPRECATED (RELEASE, "'std.table.okeys'", "compose 'std.table.keys' and 'std.table.sort' instead", okeys), shape = X ("table.shape", "functional.shape", shape), }, From 94a73628aef9238370596c3bdfc71922c172440e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 6 Oct 2015 22:42:06 +0100 Subject: [PATCH 621/703] std: move ireverse to functional module. * specs/std_spec.yaml (ireverse): Check for deprecation warning. * specs/functional_spec.yaml (ireverse): Check for normal behaviour. * lib/std/base.lua (ireverse): Move from here... * lib/std/functional.lua (ireverse): ...to here. * lib/std/delete-after/2016-01-03.lua (ireverse): ...and here... * lib/std/delete-after/a-year.lua (std.ireverse): ...and here, but exported by a deprecation warning wrapper. * lib/std/init.lua (deprecations): Import delete-after.a-year and merge in warning std.ireverse. * NEWS.md (Deprecations): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++++ lib/std/base.lua | 14 ------------ lib/std/delete-after/2016-01-03.lua | 19 ++++++++++++---- lib/std/delete-after/a-year.lua | 16 +++++++++++++ lib/std/functional.lua | 31 ++++++++++++++++++++++++- lib/std/init.lua | 27 ++++++++-------------- specs/functional_spec.yaml | 35 ++++++++++++++++++++++++++--- specs/std_spec.yaml | 23 ++++++++++++++----- 8 files changed, 123 insertions(+), 46 deletions(-) diff --git a/NEWS.md b/NEWS.md index 749ca61..5c77f9d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -154,6 +154,10 @@ orthogonality with core Lua, we're going back to using `std.object.type`, because that just makes more sense. Sorry! + - `std.ireverse` has been deprecated in favour of + `std.functional.ireverse` because of the functional style of a + non-destructive sequence reversing operation. + - `std.table.flatten` and `std.table.shape` have been deprecated in favour of `std.functional.flatten` and `std.functional.shape` because these functions are far more useful in conjunction with a diff --git a/lib/std/base.lua b/lib/std/base.lua index e3dd8eb..6c8e6a9 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -213,19 +213,6 @@ local function invert (t) end --- Be careful to reverse only the valid sequence part of a table. -local function ireverse (t) - local oob = 1 - while t[oob] ~= nil do - oob = oob + 1 - end - - local r = {} - for i = 1, oob - 1 do r[oob - i] = t[i] end - return r -end - - -- Sort numbers first then asciibetically local function keysort (a, b) if type (a) == "number" then @@ -598,7 +585,6 @@ return { getmetamethod = getmetamethod, ielems = ielems, ipairs = ipairs, - ireverse = ireverse, npairs = npairs, pairs = pairs, ripairs = ripairs, diff --git a/lib/std/delete-after/2016-01-03.lua b/lib/std/delete-after/2016-01-03.lua index 2937908..7570fcf 100644 --- a/lib/std/delete-after/2016-01-03.lua +++ b/lib/std/delete-after/2016-01-03.lua @@ -63,7 +63,6 @@ if not require "std.debug_init"._DEBUG.deprecate then local DEPRECATED = _.maturity.DEPRECATED local eval = _.std.eval local ielems = _.std.ielems - local ireverse = _.std.ireverse local ripairs = _.std.ripairs -- Only the above symbols are used below this line. @@ -168,6 +167,18 @@ if not require "std.debug_init"._DEBUG.deprecate then end + local function ireverse (t) + local oob = 1 + while t[oob] ~= nil do + oob = oob + 1 + end + + local r = {} + for i = 1, oob - 1 do r[oob - i] = t[i] end + return r + end + + local function reduce (fn, d, ifn, ...) local argt = {...} if not callable (ifn) then @@ -449,9 +460,9 @@ if not require "std.debug_init"._DEBUG.deprecate then map_with = X ("list.map_with'", "std.functional.map_with", map_with), project = X ("list.project", "std.table.project", project), relems = DEPRECATED (RELEASE, "'std.list.relems'", - "compose 'std.ielems' and 'std.ireverse' instead", relems), + "compose 'std.ielems' and 'std.functional.ireverse' instead", relems), reverse = DEPRECATED (RELEASE, "'std.list.reverse'", - "compose 'std.list' and 'std.ireverse' instead", reverse), + "compose 'std.list' and 'std.functional.ireverse' instead", reverse), shape = X ("list.shape", "std.table.shape", shape), transpose = X ("list.transpose", "std.functional.zip", transpose), zip_with = X ("list.zip_with", "std.functional.zip_with", zip_with), @@ -494,7 +505,7 @@ if not require "std.debug_init"._DEBUG.deprecate then function (proto, self, x) return project (proto, x, self) end), relems = X ("list:relems", relems), reverse = DEPRECATED (RELEASE, "'std.list:reverse'", - "compose 'std.list' and 'std.ireverse' instead", reverse), + "compose 'std.list' and 'std.functional.ireverse' instead", reverse), shape = X ("list:shape", "std.table.shape", function (proto, t, l) return shape (proto, l, t) end), }, diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index bcbc3c1..db2ef12 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -88,6 +88,18 @@ if not require "std.debug_init"._DEBUG.deprecate then end + local function ireverse (t) + local oob = 1 + while t[oob] ~= nil do + oob = oob + 1 + end + + local r = {} + for i = 1, oob - 1 do r[oob - i] = t[i] end + return r + end + + local function okeys (t) local r = {} for k in _pairs (t) do r[#r + 1] = k end @@ -167,6 +179,10 @@ if not require "std.debug_init"._DEBUG.deprecate then end, }, + std = { + ireverse = X ("ireverse", "functional.ireverse", ireverse), + }, + table = { flatten = X ("table.flatten", "functional.flatten", flatten), len = X ("table.len", "operator.len", len), diff --git a/lib/std/functional.lua b/lib/std/functional.lua index b52a197..c293e1c 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -30,7 +30,6 @@ local argscheck = _.debug.argscheck local callable = _.std.functional.callable local copy = _.std.base.copy local ielems = _.std.ielems -local ireverse = _.std.ireverse local len = _.std.operator.len local merge = _.std.base.merge local mnemonic = _.std.base.mnemonic @@ -211,6 +210,19 @@ local function foldl (fn, d, t) end +-- Be careful to reverse only the valid sequence part of a table. +local function ireverse (t) + local oob = 1 + while t[oob] ~= nil do + oob = oob + 1 + end + + local r = {} + for i = 1, oob - 1 do r[oob - i] = t[i] end + return r +end + + local function foldr (fn, d, t) if t == nil then local u, last = {}, len (d) @@ -577,6 +589,23 @@ local M = { -- @return *arguments* id = id, -- any number of any type arguments! + --- Return a new sequence with element order reversed. + -- + -- Apart from the order of the elements returned, this function follows + -- the same rules as @{ipairs} for determining first and last elements. + -- @function ireverse + -- @tparam table t a table + -- @treturn table a new table with integer keyed elements in reverse + -- order with respect to *t* + -- @see ielems + -- @see ipairs + -- @usage + -- local rielems = std.functional.compose (std.ireverse, std.ielems) + -- --> bar + -- --> foo + -- std.functional.map (print, rielems, {"foo", "bar", [4]="baz", d=5}) + ireverse = X ("ireverse (table)", ireverse), + --- Compile a lambda string into a Lua function. -- -- A valid lambda string takes one of the following forms: diff --git a/lib/std/init.lua b/lib/std/init.lua index 0586d58..0cf29a7 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -52,12 +52,14 @@ local elems = _.std.elems local eval = _.std.eval local getmetamethod = _.std.getmetamethod local ielems = _.std.ielems -local ireverse = _.std.ireverse +local merge = _.std.base.merge local npairs = _.std.npairs local ripairs = _.std.ripairs local rnpairs = _.std.rnpairs local split = _.std.string.split +local deprecated = require "std.delete-after.a-year" + local _, _ENV = nil, _.strict {} @@ -305,23 +307,6 @@ M = { -- std.functional.map (print, std.ipairs, {"foo", "bar", [4]="baz", d=5}) ipairs = X ("ipairs (table)", _ipairs), - --- Return a new sequence with element order reversed. - -- - -- Apart from the order of the elements returned, this function follows - -- the same rules as @{ipairs} for determining first and last elements. - -- @function ireverse - -- @tparam table t a table - -- @treturn table a new table with integer keyed elements in reverse - -- order with respect to *t* - -- @see ielems - -- @see ipairs - -- @usage - -- local rielems = std.functional.compose (std.ireverse, std.ielems) - -- --> bar - -- --> foo - -- std.functional.map (print, rielems, {"foo", "bar", [4]="baz", d=5}) - ireverse = X ("ireverse (table)", ireverse), - --- Ordered iterator for integer keyed values. -- Like ipairs, but does not stop until the __len or maxn of *t*. -- @function npairs @@ -397,6 +382,12 @@ for _, api in ipairs {"barrel", "monkey_patch", "version"} do end +if deprecated then + M = merge (M, deprecated.std) +end + + + --- Metamethods -- @section Metamethods diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 4ee18ae..d777cb2 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -6,9 +6,9 @@ before: exported_apis = { "any", "bind", "callable", "case", "collect", "compose", "cond", "curry", "eval", "filter", "flatten", "fold", - "foldl", "foldr", "id", "lambda", "map", "map_with", - "memoize", "nop", "op", "product", "reduce", "shape", - "zip", "zip_with" } + "foldl", "foldr", "id", "ireverse", "lambda", "map", + "map_with", "memoize", "nop", "op", "product", "reduce", + "shape", "zip", "zip_with" } setdebug { deprecate = false } @@ -375,6 +375,35 @@ specify std.functional: expect ({f (1, "two", false)}).to_equal {1, "two", false} +- describe ireverse: + - before: | + __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, + }) + + f = M.ireverse + + - context with bad arguments: + badargs.diagnose (f, "std.functional.ireverse (table)") + + - it returns a new list: + t = {1, 2, 5} + expect (f (t)).not_to_be (t) + - it reverses the elements relative to the original list: + expect (f {1, 2, "five"}).to_equal {"five", 2, 1} + - it ignores the dictionary part of a table: + expect (f {1, 2, "five"; a = "b", c = "d"}).to_equal {"five", 2, 1} + - it respects __len metamethod: + expect (f (__index)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} + - it works for an empty list: + expect (f {}).to_equal {} + + - describe lambda: - before: f = M.lambda diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 4aac280..7f17c4f 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -3,9 +3,14 @@ before: | global_table = "_G" exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", - "ielems", "ipairs", "ireverse", "monkey_patch", - "npairs", "pairs", "require", "ripairs", "rnpairs", - "tostring" } + "ielems", "ipairs", "monkey_patch", "npairs", "pairs", + "require", "ripairs", "rnpairs", "tostring" } + deprecations = { "ireverse" } + + setdebug { deprecate = false } + + deprecate_on = bind (deprecation, {"nil", this_module}) + deprecate_off = bind (deprecation, {false, this_module}) -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -38,7 +43,10 @@ specify std: - 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) + apis = {} + for i, v in ipairs (exported_apis) do apis[i] = v end + for _, v in ipairs (deprecations) do apis[#apis + 1] = v end + expect (t).to_contain.a_permutation_of (apis) - context when lazy loading: - it has no submodules on initial load: @@ -328,8 +336,11 @@ specify std: - before: f = M.ireverse - - context with bad arguments: - badargs.diagnose (f, "std.ireverse (table)") + - it writes a deprecation warning: + expect (deprecate_on ("ireverse", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("ireverse", "{}")). + not_to_contain_error "was deprecated" - it returns a new list: t = {1, 2, 5} From 9a846dbac479e42dbb0b462faea86a82432d460d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 6 Oct 2015 23:04:48 +0100 Subject: [PATCH 622/703] refactor: reduce coupling between std.strbuf and std.base. * lib/std/strbuf.lua (type): Remove unused variable. (ielems): Use ipairs instead. (insert): Use t[#t + 1] instead. (X): Fold into single caller. Signed-off-by: Gary V. Vaughan --- lib/std/strbuf.lua | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index fed405e..cc39e5a 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -27,8 +27,8 @@ ]] +local ipairs = ipairs local tostring = tostring -local type = type local table_concat = table.concat @@ -44,8 +44,6 @@ local Module = _.std.object.Module local Object = _.object.prototype local argscheck = _.debug.argscheck -local ielems = _.std.ielems -local insert = _.std.table.insert local merge = _.std.base.merge @@ -61,13 +59,14 @@ local _, _ENV = nil, _.strict {} local function __concat (self, x) - return insert (self, x) + self[#self + 1] = x + return self end local function __tostring (self) local strs = {} - for e in ielems (self) do strs[#strs + 1] = tostring (e) end + for _, e in ipairs (self) do strs[#strs + 1] = tostring (e) end return table_concat (strs) end @@ -77,10 +76,6 @@ end --[[ ================= ]]-- -local function X (decl, fn) - return argscheck ("std.strbuf." .. decl, fn) -end - local methods = { --- Methods -- @section methods @@ -94,7 +89,7 @@ local methods = { -- @treturn prototype modified buffer -- @usage -- c = StrBuf {} :concat "append this" :concat (StrBuf {" and", " this"}) - concat = X ("concat (StrBuf, any)", __concat), + concat = argscheck ("std.strbuf.concat (StrBuf, any)", __concat), } if deprecated then From aaa3fca9f66fc0dc7810bb66d0fc94b202243a07 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 7 Oct 2015 11:04:39 +0100 Subject: [PATCH 623/703] refactor: move insert definiton to table module. * lib/std/base.lua (insert): Move from here... * lib/std/table.lua (insert): ...to here. * lib/std/base.lua (split): Call table.insert instead of our wrapper, because we don't use and needn't pay the overhead for returned result and bounds checking. * lib/std/debug.lua (permute): Likewise. * lib/std/io.lua (process_files): Likewise. * lib/std/optparse.lua (normalise, on, parse, set): Likewise. * lib/std/string.lua (finds): Likewies. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 15 ++------------- lib/std/debug.lua | 6 +++--- lib/std/io.lua | 4 ++-- lib/std/optparse.lua | 30 +++++++++++++++--------------- lib/std/string.lua | 4 ++-- lib/std/table.lua | 12 +++++++++++- 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 6c8e6a9..1b81fa2 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -66,16 +66,6 @@ local argerror, getmetamethod, len -- (which have slightly varying semantics between releases). -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 - table_insert (t, pos, v) - return t -end - - -- Iterate over keys 1..n, where n is the key before the first nil -- valued ordinal key (like Lua 5.3). local function ipairs (l) @@ -511,14 +501,14 @@ local function split (s, sep) local r, patt = {} if sep == "" then patt = "(.)" - insert (r, "") + table_insert (r, "") else patt = "(.-)" .. (sep or "%s+") end local b, slen = 0, len (s) while b <= slen do local e, n, m = string_find (s, patt, b + 1) - insert (r, m or s:sub (b + 1, slen)) + table_insert (r, m or s:sub (b + 1, slen)) b = n or slen + 1 end return r @@ -642,7 +632,6 @@ return { }, table = { - insert = insert, invert = invert, maxn = maxn, unpack = unpack, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index b2a688a..7ae17cd 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -44,6 +44,7 @@ local math_floor = math.floor local math_huge = math.huge local math_max = math.max local table_concat = table.concat +local table_insert = table.insert local table_remove = table.remove local table_sort = table.sort @@ -60,7 +61,6 @@ local _pairs = _.std.pairs local _tostring = _.std.tostring local argerror = _.std.debug.argerror local copy = _.std.base.copy -local insert = _.std.table.insert local last = _.std.base.last local len = _.std.operator.len local maxn = _.std.table.maxn @@ -199,7 +199,7 @@ local function permute (t) if optional == nil then -- Append non-optional type-spec to each permutation. for b = 1, #p do - insert (p[b], markdots (p[b], v)) + table_insert (p[b], markdots (p[b], v)) end else -- Duplicate all existing permutations, and add optional type-spec @@ -207,7 +207,7 @@ local function permute (t) local o = #p for b = 1, o do p[b + o] = copy (p[b]) - insert (p[b], markdots (p[b], optional)) + table_insert (p[b], markdots (p[b], optional)) end end end diff --git a/lib/std/io.lua b/lib/std/io.lua index 7aa622a..0c751b7 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -30,6 +30,7 @@ local io_type = io.type local io_write = io.write local string_format = string.format local table_concat = table.concat +local table_insert = table.insert local _ = { @@ -45,7 +46,6 @@ local argscheck = _.debug.argscheck local catfile = _.std.io.catfile local copy = _.std.base.copy local dirsep = _.std.package.dirsep -local insert = _.std.table.insert local leaves = _.std.tree.leaves local len = _.std.operator.len local merge = _.std.base.merge @@ -128,7 +128,7 @@ 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, "-") + table_insert (arg, "-") end for i, v in _ipairs (arg) do if v == "-" then diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua index 6fc02ff..a77ce3d 100644 --- a/lib/std/optparse.lua +++ b/lib/std/optparse.lua @@ -34,6 +34,7 @@ local io_open = io.open local io_stderr = io.stderr local os_exit = os.exit local string_len = string.len +local table_insert = table.insert local _ = { @@ -46,7 +47,6 @@ local Object = _.object.prototype local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local insert = _.std.table.insert local last = _.std.base.last local len = _.std.operator.len @@ -85,8 +85,8 @@ local function normalise (self, arglist) -- Only split recognised long options. if self[optname] then - insert (normal, optname) - insert (normal, opt:sub (x + 1)) + table_insert (normal, optname) + table_insert (normal, opt:sub (x + 1)) else x = nil end @@ -94,7 +94,7 @@ local function normalise (self, arglist) if x == nil then -- No '=', or substring before '=' is not a known option name. - insert (normal, opt) + table_insert (normal, opt) end elseif opt:sub (1, 1) == "-" and string_len (opt) > 2 then @@ -126,9 +126,9 @@ local function normalise (self, arglist) until opt == nil -- Append split options to normalised list - for _, v in _ipairs (split) do insert (normal, v) end + for _, v in _ipairs (split) do table_insert (normal, v) end else - insert (normal, opt) + table_insert (normal, opt) end end @@ -147,7 +147,7 @@ local function set (self, opt, value) local opts = self.opts[key] if type (opts) == "table" then - insert (opts, value) + table_insert (opts, value) elseif opts ~= nil then self.opts[key] = { opts, value } else @@ -264,7 +264,7 @@ end -- parser:on ("--", parser.finished) local function finished (self, arglist, i) for opt = i + 1, len (arglist) do - insert (self.unrecognised, arglist[opt]) + table_insert (self.unrecognised, arglist[opt]) end return 1 + len (arglist) end @@ -472,10 +472,10 @@ local function on (self, opts, handler, value) if opt:match ("^%-[^%-]+") ~= nil then -- '-xyz' => '-x -y -z' for i = 2, string_len (opt) do - insert (normal, "-" .. opt:sub (i, i)) + table_insert (normal, "-" .. opt:sub (i, i)) end else - insert (normal, opt) + table_insert (normal, opt) end end) end @@ -537,12 +537,12 @@ local function parse (self, arglist, defaults) local opt = arglist[i] if self[opt] == nil then - insert (self.unrecognised, opt) + table_insert (self.unrecognised, opt) i = i + 1 -- Following non-'-' prefixed argument is an optarg. if i <= len (arglist) and arglist[i]:match "^[^%-]" then - insert (self.unrecognised, arglist[i]) + table_insert (self.unrecognised, arglist[i]) i = i + 1 end @@ -590,7 +590,7 @@ local function _init (_, spec) -- by a '-'. local specs = {} parser.helptext:gsub ("\n %s*(%-[^\n]+)", - function (spec) insert (specs, spec) end) + function (spec) table_insert (specs, spec) end) -- Register option handlers according to the help text. for _, spec in _ipairs (specs) do @@ -626,7 +626,7 @@ local function _init (_, spec) local _, c = spec:gsub ("^%-([-%w]),?%s+(.*)$", function (opt, rest) if opt == "-" then opt = "--" end - insert (options, opt) + table_insert (options, opt) spec = rest end) @@ -636,7 +636,7 @@ local function _init (_, spec) -- Consume long option. spec:gsub ("^%-%-([%-%w]+),?%s+(.*)$", function (opt, rest) - insert (options, opt) + table_insert (options, opt) spec = rest end) end diff --git a/lib/std/string.lua b/lib/std/string.lua index cad1b2d..ad921dc 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -21,6 +21,7 @@ local type = type local io_stderr = io.stderr local math_abs = math.abs local math_floor = math.floor +local table_insert = table.insert local _ = { @@ -38,7 +39,6 @@ local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG local argscheck = _.debug.argscheck local copy = _.std.base.copy local escape_pattern = _.std.string.escape_pattern -local insert = _.std.table.insert local len = _.std.operator.len local merge = _.std.base.merge local pickle = _.std.string.pickle @@ -100,7 +100,7 @@ local function finds (s, p, i, ...) repeat from, to, r = tfind (s, p, i, ...) if from ~= nil then - insert (l, {from, to, capt = r}) + table_insert (l, {from, to, capt = r}) i = to + 1 end until not from diff --git a/lib/std/table.lua b/lib/std/table.lua index bf1476a..eb5e0c4 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -18,6 +18,7 @@ local table = table local type = type local math_min = math.min +local table_insert = table.insert local _ = { @@ -32,7 +33,6 @@ local argscheck = _.debug.argscheck local argerror = _.std.debug.argerror local collect = _.std.functional.collect local copy = _.std.base.copy -local insert = _.std.table.insert local invert = _.std.table.invert local leaves = _.std.tree.leaves local len = _.std.operator.len @@ -104,6 +104,16 @@ local function enpair (t) end +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 + table_insert (t, pos, v) + return t +end + + local function keys (t) local l = {} for k in _pairs (t) do From 7a880b9ca754abd0687737a66bc75035857c6729 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 8 Oct 2015 01:04:04 +0100 Subject: [PATCH 624/703] table: make pack set n field, like Lua > 5.1. * specs/table_spec.yaml (pack): Add examples demonstrating n field being set. * lib/std/base.lua (pack): If table.pack is missing (Lua 5.1), use an equivalent Lua implementation. * lib/std/table.lua (pack): Move to core functions section, and use implementation from std.base. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 5 ++++- lib/std/base.lua | 7 +++++++ lib/std/table.lua | 19 ++++++++++--------- specs/table_spec.yaml | 17 +++++++++++++---- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5c77f9d..9f272a1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -122,7 +122,7 @@ - New `operator.eqv` is similar to `operator.eq`, except that it succeeds when recursive table contents are equivalent. - - New `std.operator.len` replaces deprecated `std.table.len`. + - New `operator.len` replaces deprecated `table.len`. - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. @@ -195,6 +195,9 @@ field in the init argument, just like the other table argument objects. + - `table.pack` now sets `n` field to number of arguments packed, even + in Lua 5.1. + ### Incompatible changes - `std.debug.DEPRECATED` and `std.debug.DEPRECATIONMSG` have moved to diff --git a/lib/std/base.lua b/lib/std/base.lua index 1b81fa2..f984f84 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -45,6 +45,7 @@ local string_format = string.format local table_concat = table.concat local table_insert = table.insert local table_maxn = table.maxn +local table_pack = table.pack local table_sort = table.sort local table_unpack = table.unpack or unpack @@ -288,6 +289,11 @@ local function npairs (t) end +local pack = table_pack or function (...) + return {n = select ("#", ...), ...} +end + + local function unpack (t, i, j) if j == nil then -- respect __len, and then maxn if nil j was passed @@ -634,6 +640,7 @@ return { table = { invert = invert, maxn = maxn, + pack = pack, unpack = unpack, }, diff --git a/lib/std/table.lua b/lib/std/table.lua index eb5e0c4..7ccf272 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -38,6 +38,7 @@ local leaves = _.std.tree.leaves local len = _.std.operator.len local maxn = _.std.table.maxn local merge = _.std.base.merge +local pack = _.std.table.pack local unpack = _.std.table.unpack @@ -223,6 +224,15 @@ M = { -- 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 (("ax1"):find "(%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. @@ -326,15 +336,6 @@ M = { -- @usage t = new (0) new = X ("new (?any, ?table)", new), - --- Turn a tuple into a list. - -- @function pack - -- @param ... tuple - -- @return list - -- @usage - -- --> {1, 2, "ax"} - -- pack (("ax1"):find "(%D+)") - pack = function (...) return {...} end, - --- Project a list of fields from a list of tables. -- @function project -- @param fkey field to project diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 5d85d45..606ae2e 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -16,7 +16,7 @@ before: | deprecate_on = bind (deprecation, {"nil", this_module}) deprecate_off = bind (deprecation, {false, this_module}) - M = require "std.table" + M = require (this_module) specify std.table: @@ -29,7 +29,12 @@ specify std.table: expect (show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core table table: - apis = require "std.base".base.copy (extend_base) + apis = {} + for _, v in ipairs (extend_base) do + if v ~= "pack" or M.pack ~= table.pack then + apis[#apis + 1] = v + end + end for _, v in ipairs (deprecations) do apis[#apis + 1] = v end @@ -534,14 +539,18 @@ specify std.table: - describe pack: - before: unpack = unpack or table.unpack - t = {"one", "two", "five"} + t = {"one", "two", "five", n=3} f = M.pack - it creates an empty table with no arguments: - expect (f ()).to_equal {} + 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: From 87b5d4712cc650c86599fbebc9edd884da592eba Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 9 Oct 2015 11:14:27 +0100 Subject: [PATCH 625/703] functional: decouple and speed up any, bind, collect and compose. * lib/std/functional.lua (any, bind, compose): Use pack and loop from 1 to n, instead of calling npairs. (collect): With no iterator, synthesize npairs by copying values with a numeric key to results table. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index c293e1c..54b5256 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -13,6 +13,7 @@ local next = next local pcall = pcall local select = select local setmetatable = setmetatable +local type = type local math_ceil = math.ceil local table_remove = table.remove @@ -35,6 +36,7 @@ local merge = _.std.base.merge local mnemonic = _.std.base.mnemonic local nop = _.std.functional.nop local npairs = _.std.npairs +local pack = _.std.table.pack local reduce = _.std.functional.reduce local render = _.std.string.render local leaves = _.std.tree.leaves @@ -53,12 +55,12 @@ local _, _ENV = nil, _.strict {} local function any (...) - local fns = {...} + local fns = pack (...) return function (...) local argt = {} - for _, fn in npairs (fns) do - argt = {fn (...)} + for i = 1, fns.n do + argt = {fns[i] (...)} if argt[1] ~= nil then return unpack (argt) end @@ -70,10 +72,10 @@ end local function bind (fn, bound) return function (...) - local argt, i = copy (bound), 1 - for _, v in npairs {...} do + local argt, unbound, i = copy (bound), pack (...), 1 + for j = 1, unbound.n do while argt[i] ~= nil do i = i + 1 end - argt[i], i = v, i + 1 + argt[i], i = unbound[j], i + 1 end return fn (unpack (argt)) end @@ -90,14 +92,21 @@ end local function collect (ifn, ...) - local argt, r = {...}, {} + local r = {} if not callable (ifn) then - ifn, argt = npairs, {ifn, ...} + -- No iterator behaves like npairs, which would collect all integer + -- key values from the first argument table: + local k, v = next (ifn) + repeat + if type (k) == "number" then r[k] = v end + k, v = next (ifn, k) + until k == nil + return r end - -- How many return values from ifn? + -- Or else result depends on how many return values from ifn? local arity = 1 - for e, v in ifn (unpack (argt)) do + for e, v in ifn (...) do if v then arity, r = 2, {} break end -- Build an arity-1 result table on first pass... r[#r + 1] = e @@ -105,7 +114,7 @@ local function collect (ifn, ...) if arity == 2 then -- ...oops, it was arity-2 all along, start again! - for k, v in ifn (unpack (argt)) do + for k, v in ifn (...) do r[k] = v end end @@ -115,12 +124,12 @@ end local function compose (...) - local fns = {...} + local fns = pack (...) return function (...) local argt = {...} - for _, fn in npairs (fns) do - argt = {fn (unpack (argt))} + for i = 1, fns.n do + argt = {fns[i] (unpack (argt))} end return unpack (argt) end From 0c7a5eb5c6b620984c57bb849bafc1463bdee789 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 11 Oct 2015 00:25:24 +0100 Subject: [PATCH 626/703] typing: factor gradual typing functions out of debug module. * specs/debug_spec.yaml (argcheck, argerror, argscheck) (extramsg_mismatch, extramsg_toomany, parsetypes, resulterror) (typesplit): Examples to specify deprecation warnings. * specs/typing_spec.yaml (argcheck, argerror, argscheck) (extramsg_mismatch, extramsg_toomany, parsetypes, resulterror) (typesplit): Similar, non-depracated examlpes from their new module. * specs/specs.mk (specl_SPECS): Add specs/typing_spec.yaml. * lib/std/debug.lua (getfenv, setfenv): Move from here... * lib/std/base.lua (_getfenv, _setfenv): ...to here, for sharing with new typing module. * lib/std/debug.lua (argcheck, argscheck, extramsg_mismatch) (extramsg_toomany, parsetypes, resulterror, typesplit): Move from here... * lib/std/base.lua (argerror, raise): ...and here... * lib/std/typing.lua: New module. (argcheck, argerror, argscheck, extramsg_mismatch) (extramsg_toomany, parsetypes, raise, resulterror, typesplit): ...to here. Adjust all callers to import from std.typing. * build-aux/config.ld.in (file): Add lib/std/typing.lua. * local.mk (dist_luastd_DATA): Add lib/std/typing.lua. (dist_docmodules_DATA): Add doc/modules/typing.html. Signed-off-by: Gary V. Vaughan --- build-aux/config.ld.in | 3 +- lib/std/base.lua | 86 +++- lib/std/container.lua | 10 +- lib/std/debug.lua | 714 +------------------------- lib/std/delete-after/a-year.lua | 492 ++++++++++++++++++ lib/std/functional.lua | 4 +- lib/std/init.lua | 4 +- lib/std/io.lua | 6 +- lib/std/list.lua | 4 +- lib/std/math.lua | 4 +- lib/std/object.lua | 4 +- lib/std/package.lua | 4 +- lib/std/set.lua | 4 +- lib/std/strbuf.lua | 4 +- lib/std/string.lua | 4 +- lib/std/table.lua | 6 +- lib/std/tree.lua | 4 +- lib/std/typing.lua | 690 +++++++++++++++++++++++++ local.mk | 2 + specs/debug_spec.yaml | 89 +++- specs/specs.mk | 1 + specs/typing_spec.yaml | 872 ++++++++++++++++++++++++++++++++ 22 files changed, 2231 insertions(+), 780 deletions(-) create mode 100644 lib/std/typing.lua create mode 100644 specs/typing_spec.yaml diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index c9aea1e..fdad474 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -27,7 +27,7 @@ LuaJIT), 5.2 and 5.3 written in pure Lua, comprising: 6. A runtime gradual typing system, for typechecking argument and return types at function boundaries with simple annotations that can be enabled or disabled for production code, with a Lua API modelled on - the core Lua C language API: also in @{std.debug}; + the core Lua C language API: also in @{std.typing}; 7. And an implementation of @{std.strict} to enforce declaration of all globals prior to use. @@ -69,6 +69,7 @@ file = { "../lib/std/maturity.lua", "../lib/std/optparse.lua", "../lib/std/strict.lua", + "../lib/std/typing.lua", } new_type ("corefunction", "Core_Functions", true) diff --git a/lib/std/base.lua b/lib/std/base.lua index f984f84..f186828 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -25,6 +25,7 @@ local dirsep = string.match (package.config, "^(%S+)\n") local error = error +local getfenv = getfenv local getmetatable = getmetatable local loadstring = loadstring or load local next = next @@ -38,6 +39,11 @@ local type = type local coroutine_wrap = coroutine.wrap local coroutine_yield = coroutine.yield +local debug_getinfo = debug.getinfo +local debug_getupvalue = debug.getupvalue +local debug_setfenv = debug.setfenv +local debug_setupvalue = debug.setupvalue +local debug_upvaluejoin = debug.upvaluejoin local math_huge = math.huge local math_min = math.min local string_find = string.find @@ -60,7 +66,7 @@ local _ENV = require "std.strict" {} -- Forward declarations for Helper functions below. -local argerror, getmetamethod, len +local getmetamethod, len -- 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 @@ -190,6 +196,37 @@ local function eval (s) end +local function _getfenv (fn) + fn = fn or 1 + + -- Unwrap functable: + if type (fn) == "table" then + fn = fn.call or (getmetatable (fn) or {}).__call + end + + if getfenv then + if type (fn) == "number" then fn = fn + 1 end + + -- Stack frame count is critical here, so ensure we don't optimise one + -- away in LuaJIT... + return getfenv (fn), nil + + else + if type (fn) == "number" then + fn = debug_getinfo (fn + 1, "f").func + end + + local name, env + local up = 0 + repeat + up = up + 1 + name, env = debug_getupvalue (fn, up) + until name == '_ENV' or name == nil + return env + end +end + + local function ielems (l) return wrapiterator (ipairs, l) end @@ -465,16 +502,6 @@ local function pickle (x) end -local function raise (bad, to, name, i, extramsg, level) - level = level or 1 - local s = string_format ("bad %s #%d %s '%s'", bad, i, to, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) -end - - local function ripairs (t) local oob = 1 while t[oob] ~= nil do @@ -503,6 +530,33 @@ local function rnpairs (t) end +local function _setfenv (fn, env) + -- Unwrap functable: + if type (fn) == "table" then + fn = fn.call or (getmetatable (fn) or {}).__call + end + + if debug_setfenv then + return debug_setfenv (fn, env) + + else + -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html + local name + local up = 0 + repeat + up = up + 1 + name = debug_getupvalue (fn, up) + until name == '_ENV' or name == nil + if name then + debug_upvaluejoin (fn, up, function () return name end, 1) + debug_setupvalue (fn, up, env) + end + + return fn + end +end + + local function split (s, sep) local r, patt = {} if sep == "" then @@ -543,12 +597,6 @@ local tostring_vtable = { -- of the file. -argerror = function (name, i, extramsg, level) - level = level or 1 - raise ("argument", "to", name, i, extramsg, level + 1) -end - - -- Lua < 5.2 doesn't call `__len` automatically! len = function (t) local m = getmetamethod (t, "__len") @@ -594,13 +642,13 @@ return { last = last, merge = merge, mnemonic = mnemonic, - raise = raise, sortkeys = sortkeys, toqstring = toqstring, }, debug = { - argerror = argerror, + getfenv = _getfenv, + setfenv = _setfenv, }, functional = { diff --git a/lib/std/container.lua b/lib/std/container.lua index 49fc241..48056c3 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -40,20 +40,20 @@ local table_concat = table.concat local _ = { - debug = require "std.debug", debug_init = require "std.debug_init", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local Module = _.std.object.Module local _DEBUG = _.debug_init._DEBUG -local argcheck = _.debug.argcheck -local argscheck = _.debug.argscheck -local argerror = _.debug.argerror +local argcheck = _.typing.argcheck +local argscheck = _.typing.argscheck +local argerror = _.typing.argerror local copy = _.std.base.copy -local extramsg_toomany = _.debug.extramsg_toomany +local extramsg_toomany = _.typing.extramsg_toomany local mapfields = _.std.object.mapfields local pickle = _.std.string.pickle local render = _.std.string.render diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 7ae17cd..8da0ede 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -7,46 +7,18 @@ local debug = require "std.debug" - The behaviour of the functions in this module are controlled by the value - of the global `_DEBUG`. Not setting `_DEBUG` prior to requiring **any** of - stdlib's modules is equivalent to having `_DEBUG = true`. - - The first line of Lua code in production quality projects that use stdlib - should be either: - - _DEBUG = false - - or alternatively, if you need to be careful not to damage the global - environment: - - local init = require "std.debug_init" - init._DEBUG = false - - This mitigates almost all of the overhead of argument typechecking in - stdlib API functions. - @corelibrary std.debug ]] local debug = debug -local getfenv = getfenv -local getmetatable = getmetatable -local next = next -local pcall = pcall local setmetatable = setmetatable local type = type -local debug_setfenv = debug.setfenv local io_stderr = io.stderr -local io_type = io.type -local math_floor = math.floor local math_huge = math.huge local math_max = math.max local table_concat = table.concat -local table_insert = table.insert -local table_remove = table.remove -local table_sort = table.sort local _ = { @@ -56,22 +28,14 @@ local _ = { } local _DEBUG = _.debug_init._DEBUG -local _ipairs = _.std.ipairs +local _getfenv = _.std.debug.getfenv local _pairs = _.std.pairs +local _setfenv = _.std.debug.setfenv local _tostring = _.std.tostring -local argerror = _.std.debug.argerror -local copy = _.std.base.copy -local last = _.std.base.last -local len = _.std.operator.len -local maxn = _.std.table.maxn local merge = _.std.base.merge -local nop = _.std.functional.nop -local raise = _.std.base.raise -local split = _.std.string.split -local unpack = _.std.table.unpack -local deprecated = require "std.delete-after.2016-03-08" +local deprecated = require "std.delete-after.a-year" local _, _ENV = nil, _.strict {} @@ -82,9 +46,6 @@ local _, _ENV = nil, _.strict {} --[[ =============== ]]-- -local M - - --- Control std.debug function behaviour. -- To declare debugging state, set _DEBUG either to `false` to disable all -- runtime debugging; to any "truthy" value (equivalent to enabling everything @@ -104,501 +65,6 @@ local M -- @usage _DEBUG = { argcheck = false, level = 9, strict = false } -local function setfenv (fn, env) - -- Unwrap functable: - if type (fn) == "table" then - fn = fn.call or (getmetatable (fn) or {}).__call - end - - if debug_setfenv then - return debug_setfenv (fn, env) - - else - -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html - local name - local up = 0 - repeat - up = up + 1 - name = debug.getupvalue (fn, up) - until name == '_ENV' or name == nil - if name then - debug.upvaluejoin (fn, up, function () return name end, 1) - debug.setupvalue (fn, up, env) - end - - return fn - end -end - - -local _getfenv = getfenv - -local getfenv = function (fn) - fn = fn or 1 - - -- Unwrap functable: - if type (fn) == "table" then - fn = fn.call or (getmetatable (fn) or {}).__call - end - - if _getfenv then - if type (fn) == "number" then fn = fn + 1 end - - -- Stack frame count is critical here, so ensure we don't optimise one - -- away in LuaJIT... - return _getfenv (fn), nil - - else - if type (fn) == "number" then - fn = debug.getinfo (fn + 1, "f").func - end - - local name, env - local up = 0 - repeat - up = up + 1 - name, env = debug.getupvalue (fn, up) - until name == '_ENV' or name == nil - return env - end -end - - -local function resulterror (name, i, extramsg, level) - level = level or 1 - raise ("result", "from", name, i, extramsg, level + 1) -end - - -local function extramsg_toomany (bad, expected, actual) - local s = "no more than %d %s%s expected, got %d" - return s:format (expected, bad, expected == 1 and "" or "s", actual) -end - - ---- Strip trailing ellipsis from final argument if any, storing maximum --- number of values that can be matched directly in `t.maxvalues`. --- @tparam table t table to act on --- @string v element added to *t*, to match against ... suffix --- @treturn table *t* with ellipsis stripped and maxvalues field set -local function markdots (t, v) - return (v:gsub ("%.%.%.$", function () t.dots = true return "" end)) -end - - ---- Calculate permutations of type lists with and without [optionals]. --- @tparam table t a list of expected types by argument position --- @treturn table set of possible type lists -local function permute (t) - if t[#t] then t[#t] = t[#t]:gsub ("%]%.%.%.$", "...]") end - - local p = {{}} - for i, v in _ipairs (t) do - local optional = v:match "%[(.+)%]" - - if optional == nil then - -- Append non-optional type-spec to each permutation. - for b = 1, #p do - table_insert (p[b], markdots (p[b], v)) - end - else - -- Duplicate all existing permutations, and add optional type-spec - -- to the unduplicated permutations. - local o = #p - for b = 1, o do - p[b + o] = copy (p[b]) - table_insert (p[b], markdots (p[b], optional)) - end - end - end - return p -end - - -local function typesplit (types) - if type (types) == "string" then - types = split (types:gsub ("%s+or%s+", "|"), "%s*|%s*") - end - local r, seen, add_nil = {}, {}, false - for _, v in _ipairs (types) do - local m = v:match "^%?(.+)$" - if m then - add_nil, v = true, m - end - if not seen[v] then - r[#r + 1] = v - seen[v] = true - end - end - if add_nil then - r[#r + 1] = "nil" - end - return r -end - - -local function projectuniq (fkey, tt) - -- project - local t = {} - for _, u in _ipairs (tt) do - t[#t + 1] = u[fkey] - end - - -- split and remove duplicates - local r, s = {}, {} - for _, e in _ipairs (t) do - for _, v in _ipairs (typesplit (e)) do - if s[v] == nil then - r[#r + 1], s[v] = v, true - end - end - end - return r -end - - -local function parsetypes (types) - local r, permutations = {}, permute (types) - for i = 1, #permutations[1] do - r[i] = projectuniq (i, permutations) - end - r.dots = permutations[1].dots - return r -end - - ---- Concatenate a table of strings using ", " and " or " delimiters. --- @tparam table alternatives a table of strings --- @treturn string string of elements from alternatives delimited by ", " --- and " or " -local function concat (alternatives) - if len (alternatives) > 1 then - local t = copy (alternatives) - local top = table_remove (t) - t[#t] = t[#t] .. " or " .. top - alternatives = t - end - return table_concat (alternatives, ", ") -end - - -local function _type (x) - return (getmetatable (x) or {})._type or io_type (x) or type (x) -end - - -local function extramsg_mismatch (expectedtypes, actual, index) - local actualtype = _type (actual) or type (actual) - - -- Tidy up actual type for display. - if actualtype == "nil" then - actualtype = "no value" - elseif actualtype == "string" and actual:sub (1, 1) == ":" then - actualtype = actual - elseif type (actual) == "table" and next (actual) == nil then - local matchstr = "," .. table_concat (expectedtypes, ",") .. "," - if actualtype == "table" and matchstr == ",#list," then - actualtype = "empty list" - elseif actualtype == "table" or matchstr:match ",#" then - actualtype = "empty " .. actualtype - end - end - - if index then - actualtype = actualtype .. " at index " .. _tostring (index) - end - - -- Tidy up expected types for display. - local expectedstr = expectedtypes - if type (expectedtypes) == "table" then - local t = {} - for i, v in _ipairs (expectedtypes) do - if v == "func" then - t[i] = "function" - elseif v == "bool" then - t[i] = "boolean" - elseif v == "any" then - t[i] = "any value" - elseif v == "file" then - t[i] = "FILE*" - elseif not index then - t[i] = v:match "(%S+) of %S+" or v - else - t[i] = v - end - end - expectedstr = (concat (t) .. " expected"): - gsub ("#table", "non-empty table"): - gsub ("#list", "non-empty list"): - gsub ("(%S+ of [^,%s]-)s? ", "%1s "): - gsub ("(%S+ of [^,%s]-)s?,", "%1s,"): - gsub ("(s, [^,%s]-)s? ", "%1s "): - gsub ("(s, [^,%s]-)s?,", "%1s,"): - gsub ("(of .-)s? or ([^,%s]-)s? ", "%1s or %2s ") - end - - return expectedstr .. ", got " .. actualtype -end - - -local argcheck, argscheck -- forward declarations - -if _DEBUG.argcheck then - - --- Return index of the first mismatch between types and values, or `nil`. - -- @tparam table typelist a list of expected types - -- @tparam table valuelist a table of arguments to compare - -- @treturn int|nil position of first mismatch in *typelist* - local function match (typelist, valuelist) - local n = #typelist - for i = 1, n do -- normal parameters - local ok = pcall (argcheck, "pcall", i, typelist[i], valuelist[i]) - if not ok then return i end - end - for i = n + 1, maxn (valuelist) do -- additional values against final type - local ok = pcall (argcheck, "pcall", i, typelist[n], valuelist[i]) - if not ok then return i end - end - end - - - --- Compare *check* against type of *actual* - -- @string check extended type name expected - -- @param actual object being typechecked - -- @treturn boolean `true` if *actual* is of type *check*, otherwise - -- `false` - local function checktype (check, actual) - if check == "any" and actual ~= nil then - return true - elseif check == "file" and io_type (actual) == "file" then - return true - end - - local actualtype = type (actual) - if check == actualtype then - return true - elseif check == "bool" and actualtype == "boolean" then - return true - elseif check == "#table" then - if actualtype == "table" and next (actual) then - return true - end - elseif check == "function" or check == "func" then - if actualtype == "function" or - (getmetatable (actual) or {}).__call ~= nil - then - return true - end - elseif check == "int" then - if actualtype == "number" and actual == math_floor (actual) then - return true - end - elseif type (check) == "string" and check:sub (1, 1) == ":" then - if check == actual then - return true - end - end - - actualtype = _type (actual) - if check == actualtype then - return true - elseif check == "list" or check == "#list" then - if actualtype == "table" or actualtype == "List" then - local len, count = len (actual), 0 - local i = next (actual) - repeat - if i ~= nil then count = count + 1 end - i = next (actual, i) - until i == nil or count > len - if count == len and (check == "list" or count > 0) then - return true - end - end - elseif check == "object" then - if actualtype ~= "table" and type (actual) == "table" then - return true - end - end - - return false - end - - - local function empty (t) return not next (t) end - - -- Pattern to normalize: [types...] to [types]... - local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" - - --- Diagnose mismatches between *valuelist* and type *permutations*. - -- @tparam table valuelist list of actual values to be checked - -- @tparam table argt table of precalculated values and handler functiens - local function diagnose (valuelist, argt) - local permutations = argt.permutations - - local bestmismatch, t = 0 - for i, typelist in _ipairs (permutations) do - local mismatch = match (typelist, valuelist) - if mismatch == nil then - bestmismatch, t = nil, nil - break -- every *valuelist* matched types from this *typelist* - elseif mismatch > bestmismatch then - bestmismatch, t = mismatch, permutations[i] - end - end - - if bestmismatch ~= nil then - -- Report an error for all possible types at bestmismatch index. - local i, expected = bestmismatch - if t.dots and i > #t then - expected = typesplit (t[#t]) - else - expected = projectuniq (i, permutations) - end - - -- This relies on the `permute()` algorithm leaving the longest - -- possible permutation (with dots if necessary) at permutations[1]. - local typelist = permutations[1] - - -- For "container of things", check all elements are a thing too. - if typelist[i] then - local check, contents = typelist[i]:match "^(%S+) of (%S-)s?$" - if contents and type (valuelist[i]) == "table" then - for k, v in _pairs (valuelist[i]) do - if not checktype (contents, v) then - argt.badtype (i, extramsg_mismatch (expected, v, k), 3) - end - end - end - end - - -- Otherwise the argument type itself was mismatched. - if t.dots or #t >= maxn (valuelist) then - argt.badtype (i, extramsg_mismatch (expected, valuelist[i]), 3) - end - end - - local n, t = maxn (valuelist), t or permutations[1] - if t and t.dots == nil and n > #t then - argt.badtype (#t + 1, extramsg_toomany (argt.bad, #t, n), 3) - end - end - - - function argcheck (name, i, expected, actual, level) - level = level or 2 - expected = typesplit (expected) - - -- Check actual has one of the types from expected - local ok = false - for _, expect in _ipairs (expected) do - local check, contents = expect:match "^(%S+) of (%S-)s?$" - check = check or expect - - -- Does the type of actual check out? - ok = checktype (check, actual) - - -- For "table of things", check all elements are a thing too. - if ok and contents and type (actual) == "table" then - for k, v in _pairs (actual) do - if not checktype (contents, v) then - argerror (name, i, extramsg_mismatch (expected, v, k), level + 1) - end - end - end - if ok then break end - end - - if not ok then - argerror (name, i, extramsg_mismatch (expected, actual), level + 1) - end - end - - - -- Pattern to extract: fname ([types]?[, types]*) - local args_pat = "^%s*([%w_][%.%:%d%w_]*)%s*%(%s*(.*)%s*%)" - - function argscheck (decl, inner) - -- Parse "fname (argtype, argtype, argtype...)". - local fname, argtypes = decl:match (args_pat) - if argtypes == "" then - argtypes = {} - elseif argtypes then - argtypes = split (argtypes, "%s*,%s*") - else - fname = decl:match "^%s*([%w_][%.%:%d%w_]*)" - end - - -- Precalculate vtables once to make multiple calls faster. - local input, output = { - bad = "argument", - badtype = function (i, extramsg, level) - level = level or 1 - argerror (fname, i, extramsg, level + 1) - end, - permutations = permute (argtypes), - } - - -- Parse "... => returntype, returntype, returntype...". - local returntypes = decl:match "=>%s*(.+)%s*$" - if returntypes then - local i, permutations = 0, {} - for _, group in _ipairs (split (returntypes, "%s+or%s+")) do - returntypes = split (group, ",%s*") - for _, t in _ipairs (permute (returntypes)) do - i = i + 1 - permutations[i] = t - end - end - - -- Ensure the longest permutation is first in the list. - table_sort (permutations, function (a, b) return #a > #b end) - - output = { - bad = "result", - badtype = function (i, extramsg, level) - level = level or 1 - resulterror (fname, i, extramsg, level + 1) - end, - permutations = permutations, - } - end - - return function (...) - local argt = {...} - - -- Don't check type of self if fname has a ':' in it. - if fname:find (":") then table_remove (argt, 1) end - - -- Diagnose bad inputs. - diagnose (argt, input) - - -- Propagate outer environment to inner function. - local x = math_max -- ??? FIXME: getfenv(1) fails if we remove this ??? - setfenv (inner, getfenv (1)) - - -- Execute. - local results = {inner (...)} - - -- Diagnose bad outputs. - if returntypes then - diagnose (results, output) - end - - return unpack (results, 1, maxn (results)) - end - end - -else - - -- Turn off argument checking if _DEBUG is false, or a table containing - -- a false valued `argcheck` field. - - argcheck = nop - argscheck = function (decl, inner) return inner end - -end - - local function say (n, ...) local level, argt = n, {...} if type (n) ~= "number" then @@ -651,175 +117,7 @@ end -M = { - --- Gradual Typing - -- @section typing - - --- Check the type of an argument against expected types. - -- Equivalent to luaL_argcheck in the Lua C API. - -- - -- Call `argerror` if there is a type mismatch. - -- - -- Argument `actual` must match one of the types from in `expected`, each - -- of which can be the name of a primitive Lua type, a stdlib object type, - -- or one of the special options below: - -- - -- #table accept any non-empty table - -- any accept any non-nil argument type - -- file accept an open file object - -- function accept a function, or object with a __call metamethod - -- int accept an integer valued number - -- list accept a table where all keys are a contiguous 1-based integer range - -- #list accept any non-empty list - -- object accept any std.Object derived type - -- :foo accept only the exact string ":foo", works for any :-prefixed string - -- - -- The `:foo` format allows for type-checking of self-documenting - -- boolean-like constant string parameters predicated on `nil` versus - -- `:option` instead of `false` versus `true`. Or you could support - -- both: - -- - -- argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa) - -- - -- A very common pattern is to have a list of possible types including - -- "nil" when the argument is optional. Rather than writing long-hand - -- as above, prepend a question mark to the list of types and omit the - -- explicit "nil" entry: - -- - -- argcheck ("table.copy", 2, "?boolean|:nometa", predicate) - -- - -- Normally, you should not need to use the `level` parameter, as the - -- default is to blame the caller of the function using `argcheck` in - -- error messages; which is almost certainly what you want. - -- @function argcheck - -- @string name function to blame in error message - -- @int i argument number to blame in error message - -- @string expected specification for acceptable argument types - -- @param actual argument passed - -- @int[opt=2] level call stack level to blame for the error - -- @usage - -- local function case (with, branches) - -- argcheck ("std.functional.case", 2, "#table", branches) - -- ... - argcheck = argcheck, - - --- Raise a bad argument error. - -- Equivalent to luaL_argerror in the Lua C API. This function does not - -- return. The `level` argument behaves just like the core `error` - -- function. - -- @function argerror - -- @string name function to callout in error message - -- @int i argument number - -- @string[opt] extramsg additional text to append to message inside parentheses - -- @int[opt=1] level call stack level to blame for the error - -- @see resulterror - -- @see extramsg_mismatch - -- @usage - -- local function slurp (file) - -- local h, err = input_handle (file) - -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end - -- ... - argerror = argerror, - - --- Wrap a function definition with argument type and arity checking. - -- In addition to checking that each argument type matches the corresponding - -- element in the *types* table with `argcheck`, if the final element of - -- *types* ends with an ellipsis, remaining unchecked arguments are checked - -- against that type: - -- - -- format = argscheck ("string.format (string, ?any...)", string.format) - -- - -- A colon in the function name indicates that the argument type list does - -- not have a type for `self`: - -- - -- format = argscheck ("string:format (?any...)", string.format) - -- - -- If an argument can be omitted entirely, then put its type specification - -- in square brackets: - -- - -- insert = argscheck ("table.insert (table, [int], ?any)", table.insert) - -- - -- Similarly return types can be checked with the same list syntax as - -- arguments: - -- - -- len = argscheck ("string.len (string) => int", string.len) - -- - -- Additionally, variant return type lists can be listed like this: - -- - -- open = argscheck ("io.open (string, ?string) => file or nil, string", - -- io.open) - -- - -- @function argscheck - -- @string decl function type declaration string - -- @func inner function to wrap with argument checking - -- @usage - -- local case = argscheck ("std.functional.case (?any, #table) => [any...]", - -- function (with, branches) - -- ... - -- end) - argscheck = argscheck, - - --- Format a type mismatch error. - -- @function extramsg_mismatch - -- @string expected a pipe delimited list of matchable types - -- @param actual the actual argument to match with - -- @number[opt] index erroring container element index - -- @treturn string formatted *extramsg* for this mismatch for @{argerror} - -- @see argerror - -- @see resulterror - -- @usage - -- if fmt ~= nil and type (fmt) ~= "string" then - -- argerror ("format", 1, extramsg_mismatch ("?string", fmt)) - -- end - extramsg_mismatch = function (expected, actual, index) - return extramsg_mismatch (typesplit (expected), actual, index) - end, - - --- Format a too many things error. - -- @function extramsg_toomany - -- @string bad the thing there are too many of - -- @int expected maximum number of *bad* things expected - -- @int actual actual number of *bad* things that triggered the error - -- @see argerror - -- @see resulterror - -- @see extramsg_mismatch - -- @usage - -- if maxn (argt) > 7 then - -- argerror ("sevenses", 8, extramsg_toomany ("argument", 7, maxn (argt))) - -- end - extramsg_toomany = extramsg_toomany, - - --- Compact permutation list into a list of valid types at each argument. - -- Eliminate bracketed types by combining all valid types at each position - -- for all permutations of *typelist*. - -- @function parsetypes - -- @tparam list types a normalized list of type names - -- @treturn list valid types for each positional parameter - parsetypes = parsetypes, - - --- Raise a bad result error. - -- Like @{argerror} for bad results. This function does not - -- return. The `level` argument behaves just like the core `error` - -- function. - -- @function resulterror - -- @string name function to callout in error message - -- @int i result number - -- @string[opt] extramsg additional text to append to message inside parentheses - -- @int[opt=1] level call stack level to blame for the error - -- @usage - -- local function slurp (file) - -- ... - -- if type (result) ~= "string" then resulterror ("std.io.slurp", 1, err, 2) end - resulterror = resulterror, - - --- Split a typespec string into a table of normalized type names. - -- @function typesplit - -- @tparam string|table either `"?bool|:nometa"` or `{"boolean", ":nometa"}` - -- @treturn table a new list with duplicates removed and leading "?"s - -- replaced by a "nil" element - typesplit = typesplit, - - +local M = { --- Function Environments -- @section environments @@ -827,14 +125,14 @@ M = { -- @function getfenv -- @tparam int|function|functable fn target function, or stack level -- @treturn table environment of *fn* - getfenv = getfenv, + 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, + setfenv = _setfenv, --- Functions diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index db2ef12..3e5fcb4 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -23,17 +23,31 @@ local M = false if not require "std.debug_init"._DEBUG.deprecate then + local error = error local getmetatable = getmetatable + local next = next local pairs = pairs + local pcall = pcall local type = type local io_stderr = io.stderr local io_type = io.type local math_ceil = math.ceil + local math_floor = math.floor + local math_max = math.max + local string_find = string.find + local string_format = string.format + local string_gsub = string.gsub + local string_match = string.match + local table_concat = table.concat + local table_insert = table.insert + local table_remove = table.remove + local table_sort = table.sort local table_unpack = table.unpack or unpack local _, deprecated = { -- Adding anything else here will probably cause a require loop. + debug_init = require "std.debug_init", maturity = require "std.maturity", std = require "std.base", strict = require "std.strict", @@ -44,13 +58,22 @@ if not require "std.debug_init"._DEBUG.deprecate then if not _.ok then deprecated = {} end + local _DEBUG = _.debug_init._DEBUG + local _getfenv = _.std.debug.getfenv local _ipairs = _.std.ipairs local _pairs = _.std.pairs + local _setfenv = _.std.debug.setfenv + local _tostring = _.std.tostring local DEPRECATED = _.maturity.DEPRECATED local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG + local copy = _.std.base.copy local leaves = _.std.tree.leaves local len = _.std.operator.len + local maxn = _.std.table.maxn + local nop = _.std.functional.nop local sortkeys = _.std.base.sortkeys + local split = _.std.string.split + local unpack = _.std.table.unpack -- Only the above symbols are used below this line. local _, _ENV = nil, _.strict {} @@ -61,6 +84,458 @@ if not require "std.debug_init"._DEBUG.deprecate then --[[ ========== ]]-- + local function raise (bad, to, name, i, extramsg, level) + level = level or 1 + local s = string_format ("bad %s #%d %s '%s'", bad, i, to, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) + end + + + local function argerror (name, i, extramsg, level) + level = level or 1 + raise ("argument", "to", name, i, extramsg, level + 1) + end + + + local function resulterror (name, i, extramsg, level) + level = level or 1 + raise ("result", "from", name, i, extramsg, level + 1) + end + + + local function extramsg_toomany (bad, expected, actual) + local s = "no more than %d %s%s expected, got %d" + return string_format (s, expected, bad, expected == 1 and "" or "s", actual) + end + + + --- Concatenate a table of strings using ", " and " or " delimiters. + -- @tparam table alternatives a table of strings + -- @treturn string string of elements from alternatives delimited by ", " + -- and " or " + local function concat (alternatives) + if len (alternatives) > 1 then + local t = copy (alternatives) + local top = table_remove (t) + t[#t] = t[#t] .. " or " .. top + alternatives = t + end + return table_concat (alternatives, ", ") + end + + + local function _type (x) + return (getmetatable (x) or {})._type or io_type (x) or type (x) + end + + + local function extramsg_mismatch (expectedtypes, actual, index) + local actualtype = _type (actual) or type (actual) + + -- Tidy up actual type for display. + if actualtype == "nil" then + actualtype = "no value" + elseif actualtype == "string" and actual:sub (1, 1) == ":" then + actualtype = actual + elseif type (actual) == "table" and next (actual) == nil then + local matchstr = "," .. table_concat (expectedtypes, ",") .. "," + if actualtype == "table" and matchstr == ",#list," then + actualtype = "empty list" + elseif actualtype == "table" or matchstr:match ",#" then + actualtype = "empty " .. actualtype + end + end + + if index then + actualtype = actualtype .. " at index " .. _tostring (index) + end + + -- Tidy up expected types for display. + local expectedstr = expectedtypes + if type (expectedtypes) == "table" then + local t = {} + for i, v in _ipairs (expectedtypes) do + if v == "func" then + t[i] = "function" + elseif v == "bool" then + t[i] = "boolean" + elseif v == "any" then + t[i] = "any value" + elseif v == "file" then + t[i] = "FILE*" + elseif not index then + t[i] = v:match "(%S+) of %S+" or v + else + t[i] = v + end + end + expectedstr = (concat (t) .. " expected"): + gsub ("#table", "non-empty table"): + gsub ("#list", "non-empty list"): + gsub ("(%S+ of [^,%s]-)s? ", "%1s "): + gsub ("(%S+ of [^,%s]-)s?,", "%1s,"): + gsub ("(s, [^,%s]-)s? ", "%1s "): + gsub ("(s, [^,%s]-)s?,", "%1s,"): + gsub ("(of .-)s? or ([^,%s]-)s? ", "%1s or %2s ") + end + + return expectedstr .. ", got " .. actualtype + end + + + --- Strip trailing ellipsis from final argument if any, storing maximum + -- number of values that can be matched directly in `t.maxvalues`. + -- @tparam table t table to act on + -- @string v element added to *t*, to match against ... suffix + -- @treturn table *t* with ellipsis stripped and maxvalues field set + local function markdots (t, v) + return (string_gsub (v, "%.%.%.$", function () t.dots = true return "" end)) + end + + + --- Calculate permutations of type lists with and without [optionals]. + -- @tparam table t a list of expected types by argument position + -- @treturn table set of possible type lists + local function permute (t) + if t[#t] then t[#t] = string_gsub (t[#t], "%]%.%.%.$", "...]") end + + local p = {{}} + for i, v in _ipairs (t) do + local optional = string_match (v, "%[(.+)%]") + + if optional == nil then + -- Append non-optional type-spec to each permutation. + for b = 1, #p do + table_insert (p[b], markdots (p[b], v)) + end + else + -- Duplicate all existing permutations, and add optional type-spec + -- to the unduplicated permutations. + local o = #p + for b = 1, o do + p[b + o] = copy (p[b]) + table_insert (p[b], markdots (p[b], optional)) + end + end + end + return p + end + + + local function typesplit (types) + if type (types) == "string" then + types = split (string_gsub (types, "%s+or%s+", "|"), "%s*|%s*") + end + local r, seen, add_nil = {}, {}, false + for _, v in _ipairs (types) do + local m = string_match (v, "^%?(.+)$") + if m then + add_nil, v = true, m + end + if not seen[v] then + r[#r + 1] = v + seen[v] = true + end + end + if add_nil then + r[#r + 1] = "nil" + end + return r + end + + + local function projectuniq (fkey, tt) + -- project + local t = {} + for _, u in _ipairs (tt) do + t[#t + 1] = u[fkey] + end + + -- split and remove duplicates + local r, s = {}, {} + for _, e in _ipairs (t) do + for _, v in _ipairs (typesplit (e)) do + if s[v] == nil then + r[#r + 1], s[v] = v, true + end + end + end + return r + end + + + local function parsetypes (types) + local r, permutations = {}, permute (types) + for i = 1, #permutations[1] do + r[i] = projectuniq (i, permutations) + end + r.dots = permutations[1].dots + return r + end + + + + local argcheck, argscheck -- forward declarations + + if _DEBUG.argcheck then + + --- Return index of the first mismatch between types and values, or `nil`. + -- @tparam table typelist a list of expected types + -- @tparam table valuelist a table of arguments to compare + -- @treturn int|nil position of first mismatch in *typelist* + local function match (typelist, valuelist) + local n = #typelist + for i = 1, n do -- normal parameters + local ok = pcall (argcheck, "pcall", i, typelist[i], valuelist[i]) + if not ok then return i end + end + for i = n + 1, maxn (valuelist) do -- additional values against final type + local ok = pcall (argcheck, "pcall", i, typelist[n], valuelist[i]) + if not ok then return i end + end + end + + + --- Compare *check* against type of *actual* + -- @string check extended type name expected + -- @param actual object being typechecked + -- @treturn boolean `true` if *actual* is of type *check*, otherwise + -- `false` + local function checktype (check, actual) + if check == "any" and actual ~= nil then + return true + elseif check == "file" and io_type (actual) == "file" then + return true + end + + local actualtype = type (actual) + if check == actualtype then + return true + elseif check == "bool" and actualtype == "boolean" then + return true + elseif check == "#table" then + if actualtype == "table" and next (actual) then + return true + end + elseif check == "function" or check == "func" then + if actualtype == "function" or + (getmetatable (actual) or {}).__call ~= nil + then + return true + end + elseif check == "int" then + if actualtype == "number" and actual == math_floor (actual) then + return true + end + elseif type (check) == "string" and check:sub (1, 1) == ":" then + if check == actual then + return true + end + end + + actualtype = _type (actual) + if check == actualtype then + return true + elseif check == "list" or check == "#list" then + if actualtype == "table" or actualtype == "List" then + local len, count = len (actual), 0 + local i = next (actual) + repeat + if i ~= nil then count = count + 1 end + i = next (actual, i) + until i == nil or count > len + if count == len and (check == "list" or count > 0) then + return true + end + end + elseif check == "object" then + if actualtype ~= "table" and type (actual) == "table" then + return true + end + end + + return false + end + + + local function empty (t) return not next (t) end + + -- Pattern to normalize: [types...] to [types]... + local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" + + --- Diagnose mismatches between *valuelist* and type *permutations*. + -- @tparam table valuelist list of actual values to be checked + -- @tparam table argt table of precalculated values and handler functiens + local function diagnose (valuelist, argt) + local permutations = argt.permutations + + local bestmismatch, t = 0 + for i, typelist in _ipairs (permutations) do + local mismatch = match (typelist, valuelist) + if mismatch == nil then + bestmismatch, t = nil, nil + break -- every *valuelist* matched types from this *typelist* + elseif mismatch > bestmismatch then + bestmismatch, t = mismatch, permutations[i] + end + end + + if bestmismatch ~= nil then + -- Report an error for all possible types at bestmismatch index. + local i, expected = bestmismatch + if t.dots and i > #t then + expected = typesplit (t[#t]) + else + expected = projectuniq (i, permutations) + end + + -- This relies on the `permute()` algorithm leaving the longest + -- possible permutation (with dots if necessary) at permutations[1]. + local typelist = permutations[1] + + -- For "container of things", check all elements are a thing too. + if typelist[i] then + local check, contents = string_match (typelist[i], "^(%S+) of (%S-)s?$") + if contents and type (valuelist[i]) == "table" then + for k, v in _pairs (valuelist[i]) do + if not checktype (contents, v) then + argt.badtype (i, extramsg_mismatch (expected, v, k), 3) + end + end + end + end + + -- Otherwise the argument type itself was mismatched. + if t.dots or #t >= maxn (valuelist) then + argt.badtype (i, extramsg_mismatch (expected, valuelist[i]), 3) + end + end + + local n, t = maxn (valuelist), t or permutations[1] + if t and t.dots == nil and n > #t then + argt.badtype (#t + 1, extramsg_toomany (argt.bad, #t, n), 3) + end + end + + + function argcheck (name, i, expected, actual, level) + level = level or 2 + expected = typesplit (expected) + + -- Check actual has one of the types from expected + local ok = false + for _, expect in _ipairs (expected) do + local check, contents = string_match (expect, "^(%S+) of (%S-)s?$") + check = check or expect + + -- Does the type of actual check out? + ok = checktype (check, actual) + + -- For "table of things", check all elements are a thing too. + if ok and contents and type (actual) == "table" then + for k, v in _pairs (actual) do + if not checktype (contents, v) then + argerror (name, i, extramsg_mismatch (expected, v, k), level + 1) + end + end + end + if ok then break end + end + + if not ok then + argerror (name, i, extramsg_mismatch (expected, actual), level + 1) + end + end + + + -- Pattern to extract: fname ([types]?[, types]*) + local args_pat = "^%s*([%w_][%.%:%d%w_]*)%s*%(%s*(.*)%s*%)" + + function argscheck (decl, inner) + -- Parse "fname (argtype, argtype, argtype...)". + local fname, argtypes = string_match (decl, args_pat) + if argtypes == "" then + argtypes = {} + elseif argtypes then + argtypes = split (argtypes, "%s*,%s*") + else + fname = string_match (decl, "^%s*([%w_][%.%:%d%w_]*)") + end + + -- Precalculate vtables once to make multiple calls faster. + local input, output = { + bad = "argument", + badtype = function (i, extramsg, level) + level = level or 1 + argerror (fname, i, extramsg, level + 1) + end, + permutations = permute (argtypes), + } + + -- Parse "... => returntype, returntype, returntype...". + local returntypes = string_match (decl, "=>%s*(.+)%s*$") + if returntypes then + local i, permutations = 0, {} + for _, group in _ipairs (split (returntypes, "%s+or%s+")) do + returntypes = split (group, ",%s*") + for _, t in _ipairs (permute (returntypes)) do + i = i + 1 + permutations[i] = t + end + end + + -- Ensure the longest permutation is first in the list. + table_sort (permutations, function (a, b) return #a > #b end) + + output = { + bad = "result", + badtype = function (i, extramsg, level) + level = level or 1 + resulterror (fname, i, extramsg, level + 1) + end, + permutations = permutations, + } + end + + return function (...) + local argt = {...} + + -- Don't check type of self if fname has a ':' in it. + if string_find (fname, ":") then table_remove (argt, 1) end + + -- Diagnose bad inputs. + diagnose (argt, input) + + -- Propagate outer environment to inner function. + local x = math_max -- ??? FIXME: getfenv(1) fails if we remove this ??? + _setfenv (inner, _getfenv (1)) + + -- Execute. + local results = {inner (...)} + + -- Diagnose bad outputs. + if returntypes then + diagnose (results, output) + end + + return unpack (results) + end + end + + else + + -- Turn off argument checking if _DEBUG is false, or a table containing + -- a false valued `argcheck` field. + + argcheck = nop + argscheck = function (decl, inner) return inner end + + end + + local function collect (ifn, ...) local argt, r = {...}, {} @@ -153,6 +628,10 @@ if not require "std.debug_init"._DEBUG.deprecate then return DEPRECATED (RELEASE, "'std." .. old .. "'", "use 'std." .. new .. "' instead", fn) end + local function XX (base, fn) + return DEPRECATED (RELEASE, "'std.debug." .. base .. "'", "use 'std.argcheck." .. base .. "' instead", fn) + end + local function acyclic_merge (dest, src) for k, v in pairs (src) do if type (v) == "table" then @@ -166,6 +645,19 @@ if not require "std.debug_init"._DEBUG.deprecate then end M = acyclic_merge ({ + debug = { + argcheck = XX ("argcheck", argcheck), + argerror = XX ("argerror", argerror), + argscheck = XX ("argscheck", argscheck), + extramsg_mismatch = XX ("extramsg_mismatch", function (expected, actual, index) + return extramsg_mismatch (typesplit (expected), actual, index) + end), + extramsg_toomany = XX ("extramsg_toomany", extramsg_toomany), + parsetypes = XX ("parsetypes", parsetypes), + resulterror = XX ("resulterror", resulterror), + typesplit = XX ("typesplit", typesplit), + }, + object = { type = function (x) local r = (getmetatable (x) or {})._type diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 54b5256..230c021 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -20,14 +20,14 @@ local table_remove = table.remove local _ = { - debug = require "std.debug", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local callable = _.std.functional.callable local copy = _.std.base.copy local ielems = _.std.ielems diff --git a/lib/std/init.lua b/lib/std/init.lua index 0cf29a7..705520d 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -36,16 +36,16 @@ local string_match = string.match local _ = { - debug = require "std.debug", strict = require "std.strict", std = require "std.base", + typing = require "std.typing", } local _ipairs = _.std.ipairs local _pairs = _.std.pairs local _require = _.std.require local _tostring = _.std.tostring -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local compare = _.std.list.compare local copy = _.std.base.copy local elems = _.std.elems diff --git a/lib/std/io.lua b/lib/std/io.lua index 0c751b7..6253043 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -34,15 +34,15 @@ local table_insert = table.insert local _ = { - debug = require "std.debug", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local _ipairs = _.std.ipairs local _tostring = _.std.tostring -local argerror = _.debug.argerror -local argscheck = _.debug.argscheck +local argerror = _.typing.argerror +local argscheck = _.typing.argscheck local catfile = _.std.io.catfile local copy = _.std.base.copy local dirsep = _.std.package.dirsep diff --git a/lib/std/list.lua b/lib/std/list.lua index 22b2af1..1a4d860 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -22,10 +22,10 @@ local math_max = math.max local _ = { - debug = require "std.debug", object = require "std.object", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local Module = _.std.object.Module @@ -33,7 +33,7 @@ local Object = _.object.prototype local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local compare = _.std.list.compare local len = _.std.operator.len local merge = _.std.base.merge diff --git a/lib/std/math.lua b/lib/std/math.lua index 0433e07..e893a97 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -17,12 +17,12 @@ local math = math local math_floor = math.floor local _ = { - debug = require "std.debug", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local copy = _.std.base.copy local merge = _.std.base.merge diff --git a/lib/std/object.lua b/lib/std/object.lua index 9d3af83..2e1e890 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -27,16 +27,16 @@ local getmetatable = getmetatable local _ = { container = require "std.container", - debug = require "std.debug", maturity = require "std.maturity", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local Container = _.container.prototype local Module = _.std.object.Module -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local getmetamethod = _.std.getmetamethod local mapfields = _.std.object.mapfields local merge = _.std.base.merge diff --git a/lib/std/package.lua b/lib/std/package.lua index 17c13e1..dce30b0 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -47,12 +47,12 @@ local table_remove = table.remove local _ = { - debug = require "std.debug", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local catfile = _.std.io.catfile local escape_pattern = _.std.string.escape_pattern local invert = _.std.table.invert diff --git a/lib/std/set.lua b/lib/std/set.lua index 9d518ff..dba2462 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -35,9 +35,9 @@ local table_sort = table.sort local _ = { container = require "std.container", - debug = require "std.debug", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local Container = _.container.prototype @@ -45,7 +45,7 @@ local Module = _.std.object.Module local _pairs = _.std.pairs local _tostring = _.std.tostring -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local pickle = _.std.string.pickle diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index cc39e5a..8640ff3 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -34,16 +34,16 @@ local table_concat = table.concat local _ = { - debug = require "std.debug", object = require "std.object", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local Module = _.std.object.Module local Object = _.object.prototype -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local merge = _.std.base.merge diff --git a/lib/std/string.lua b/lib/std/string.lua index ad921dc..687435a 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -25,18 +25,18 @@ local table_insert = table.insert local _ = { - debug = require "std.debug", maturity = require "std.maturity", std = require "std.base", strbuf = require "std.strbuf", strict = require "std.strict", + typing = require "std.typing", } local StrBuf = _.strbuf.prototype local _tostring = _.std.tostring local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local copy = _.std.base.copy local escape_pattern = _.std.string.escape_pattern local len = _.std.operator.len diff --git a/lib/std/table.lua b/lib/std/table.lua index 7ccf272..c40880e 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -22,15 +22,15 @@ local table_insert = table.insert local _ = { - debug = require "std.debug", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argscheck = _.debug.argscheck -local argerror = _.std.debug.argerror +local argscheck = _.typing.argscheck +local argerror = _.typing.argerror local collect = _.std.functional.collect local copy = _.std.base.copy local invert = _.std.table.invert diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 099d203..80106c5 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -37,10 +37,10 @@ local table_remove = table.remove local _ = { container = require "std.container", - debug = require "std.debug", operator = require "std.operator", std = require "std.base", strict = require "std.strict", + typing = require "std.typing", } local Container = _.container.prototype @@ -48,7 +48,7 @@ local Module = _.std.object.Module local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argscheck = _.debug.argscheck +local argscheck = _.typing.argscheck local get = _.operator.get local ielems = _.std.ielems local last = _.std.base.last diff --git a/lib/std/typing.lua b/lib/std/typing.lua new file mode 100644 index 0000000..b12b8ef --- /dev/null +++ b/lib/std/typing.lua @@ -0,0 +1,690 @@ +--[[-- + Gradual typing facilities for function calls. + + The behaviour of the functions in this module are controlled by the value + of the global `_DEBUG`. Not setting `_DEBUG` prior to requiring **any** of + stdlib's modules is equivalent to having `_DEBUG = true`. + + The first line of Lua code in production quality projects that use stdlib + should be either: + + _DEBUG = false + + or alternatively, if you need to be careful not to damage the global + environment: + + local init = require "std.debug_init" + init._DEBUG = false + + This mitigates almost all of the overhead of type checking in the stdlib + API functions. + + @module std.typing +]] + + +local error = error +local getmetatable = getmetatable +local next = next +local pcall = pcall +local type = type + +local io_type = io.type +local math_floor = math.floor +local math_max = math.max +local string_find = string.find +local string_format = string.format +local string_gsub = string.gsub +local string_match = string.match +local table_concat = table.concat +local table_insert = table.insert +local table_remove = table.remove +local table_sort = table.sort + + +local _ = { + debug_init = require "std.debug_init", + std = require "std.base", + strict = require "std.strict", +} + +local _DEBUG = _.debug_init._DEBUG +local _getfenv = _.std.debug.getfenv +local _ipairs = _.std.ipairs +local _pairs = _.std.pairs +local _setfenv = _.std.debug.setfenv +local _tostring = _.std.tostring +local copy = _.std.base.copy +local len = _.std.operator.len +local maxn = _.std.table.maxn +local nop = _.std.functional.nop +local split = _.std.string.split +local unpack = _.std.table.unpack + + +local _, _ENV = nil, _.strict {} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local function raise (bad, to, name, i, extramsg, level) + level = level or 1 + local s = string_format ("bad %s #%d %s '%s'", bad, i, to, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) +end + + +local function argerror (name, i, extramsg, level) + level = level or 1 + raise ("argument", "to", name, i, extramsg, level + 1) +end + + +local function resulterror (name, i, extramsg, level) + level = level or 1 + raise ("result", "from", name, i, extramsg, level + 1) +end + + +local function extramsg_toomany (bad, expected, actual) + local s = "no more than %d %s%s expected, got %d" + return string_format (s, expected, bad, expected == 1 and "" or "s", actual) +end + + +--- Concatenate a table of strings using ", " and " or " delimiters. +-- @tparam table alternatives a table of strings +-- @treturn string string of elements from alternatives delimited by ", " +-- and " or " +local function concat (alternatives) + if len (alternatives) > 1 then + local t = copy (alternatives) + local top = table_remove (t) + t[#t] = t[#t] .. " or " .. top + alternatives = t + end + return table_concat (alternatives, ", ") +end + + +local function _type (x) + return (getmetatable (x) or {})._type or io_type (x) or type (x) +end + + +local function extramsg_mismatch (expectedtypes, actual, index) + local actualtype = _type (actual) or type (actual) + + -- Tidy up actual type for display. + if actualtype == "nil" then + actualtype = "no value" + elseif actualtype == "string" and actual:sub (1, 1) == ":" then + actualtype = actual + elseif type (actual) == "table" and next (actual) == nil then + local matchstr = "," .. table_concat (expectedtypes, ",") .. "," + if actualtype == "table" and matchstr == ",#list," then + actualtype = "empty list" + elseif actualtype == "table" or matchstr:match ",#" then + actualtype = "empty " .. actualtype + end + end + + if index then + actualtype = actualtype .. " at index " .. _tostring (index) + end + + -- Tidy up expected types for display. + local expectedstr = expectedtypes + if type (expectedtypes) == "table" then + local t = {} + for i, v in _ipairs (expectedtypes) do + if v == "func" then + t[i] = "function" + elseif v == "bool" then + t[i] = "boolean" + elseif v == "any" then + t[i] = "any value" + elseif v == "file" then + t[i] = "FILE*" + elseif not index then + t[i] = v:match "(%S+) of %S+" or v + else + t[i] = v + end + end + expectedstr = (concat (t) .. " expected"): + gsub ("#table", "non-empty table"): + gsub ("#list", "non-empty list"): + gsub ("(%S+ of [^,%s]-)s? ", "%1s "): + gsub ("(%S+ of [^,%s]-)s?,", "%1s,"): + gsub ("(s, [^,%s]-)s? ", "%1s "): + gsub ("(s, [^,%s]-)s?,", "%1s,"): + gsub ("(of .-)s? or ([^,%s]-)s? ", "%1s or %2s ") + end + + return expectedstr .. ", got " .. actualtype +end + + +--- Strip trailing ellipsis from final argument if any, storing maximum +-- number of values that can be matched directly in `t.maxvalues`. +-- @tparam table t table to act on +-- @string v element added to *t*, to match against ... suffix +-- @treturn table *t* with ellipsis stripped and maxvalues field set +local function markdots (t, v) + return (string_gsub (v, "%.%.%.$", function () t.dots = true return "" end)) +end + + +--- Calculate permutations of type lists with and without [optionals]. +-- @tparam table t a list of expected types by argument position +-- @treturn table set of possible type lists +local function permute (t) + if t[#t] then t[#t] = string_gsub (t[#t], "%]%.%.%.$", "...]") end + + local p = {{}} + for i, v in _ipairs (t) do + local optional = string_match (v, "%[(.+)%]") + + if optional == nil then + -- Append non-optional type-spec to each permutation. + for b = 1, #p do + table_insert (p[b], markdots (p[b], v)) + end + else + -- Duplicate all existing permutations, and add optional type-spec + -- to the unduplicated permutations. + local o = #p + for b = 1, o do + p[b + o] = copy (p[b]) + table_insert (p[b], markdots (p[b], optional)) + end + end + end + return p +end + + +local function typesplit (types) + if type (types) == "string" then + types = split (string_gsub (types, "%s+or%s+", "|"), "%s*|%s*") + end + local r, seen, add_nil = {}, {}, false + for _, v in _ipairs (types) do + local m = string_match (v, "^%?(.+)$") + if m then + add_nil, v = true, m + end + if not seen[v] then + r[#r + 1] = v + seen[v] = true + end + end + if add_nil then + r[#r + 1] = "nil" + end + return r +end + + +local function projectuniq (fkey, tt) + -- project + local t = {} + for _, u in _ipairs (tt) do + t[#t + 1] = u[fkey] + end + + -- split and remove duplicates + local r, s = {}, {} + for _, e in _ipairs (t) do + for _, v in _ipairs (typesplit (e)) do + if s[v] == nil then + r[#r + 1], s[v] = v, true + end + end + end + return r +end + + +local function parsetypes (types) + local r, permutations = {}, permute (types) + for i = 1, #permutations[1] do + r[i] = projectuniq (i, permutations) + end + r.dots = permutations[1].dots + return r +end + + + +local argcheck, argscheck -- forward declarations + +if _DEBUG.argcheck then + + --- Return index of the first mismatch between types and values, or `nil`. + -- @tparam table typelist a list of expected types + -- @tparam table valuelist a table of arguments to compare + -- @treturn int|nil position of first mismatch in *typelist* + local function match (typelist, valuelist) + local n = #typelist + for i = 1, n do -- normal parameters + local ok = pcall (argcheck, "pcall", i, typelist[i], valuelist[i]) + if not ok then return i end + end + for i = n + 1, maxn (valuelist) do -- additional values against final type + local ok = pcall (argcheck, "pcall", i, typelist[n], valuelist[i]) + if not ok then return i end + end + end + + + --- Compare *check* against type of *actual* + -- @string check extended type name expected + -- @param actual object being typechecked + -- @treturn boolean `true` if *actual* is of type *check*, otherwise + -- `false` + local function checktype (check, actual) + if check == "any" and actual ~= nil then + return true + elseif check == "file" and io_type (actual) == "file" then + return true + end + + local actualtype = type (actual) + if check == actualtype then + return true + elseif check == "bool" and actualtype == "boolean" then + return true + elseif check == "#table" then + if actualtype == "table" and next (actual) then + return true + end + elseif check == "function" or check == "func" then + if actualtype == "function" or + (getmetatable (actual) or {}).__call ~= nil + then + return true + end + elseif check == "int" then + if actualtype == "number" and actual == math_floor (actual) then + return true + end + elseif type (check) == "string" and check:sub (1, 1) == ":" then + if check == actual then + return true + end + end + + actualtype = _type (actual) + if check == actualtype then + return true + elseif check == "list" or check == "#list" then + if actualtype == "table" or actualtype == "List" then + local len, count = len (actual), 0 + local i = next (actual) + repeat + if i ~= nil then count = count + 1 end + i = next (actual, i) + until i == nil or count > len + if count == len and (check == "list" or count > 0) then + return true + end + end + elseif check == "object" then + if actualtype ~= "table" and type (actual) == "table" then + return true + end + end + + return false + end + + + local function empty (t) return not next (t) end + + -- Pattern to normalize: [types...] to [types]... + local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" + + --- Diagnose mismatches between *valuelist* and type *permutations*. + -- @tparam table valuelist list of actual values to be checked + -- @tparam table argt table of precalculated values and handler functiens + local function diagnose (valuelist, argt) + local permutations = argt.permutations + + local bestmismatch, t = 0 + for i, typelist in _ipairs (permutations) do + local mismatch = match (typelist, valuelist) + if mismatch == nil then + bestmismatch, t = nil, nil + break -- every *valuelist* matched types from this *typelist* + elseif mismatch > bestmismatch then + bestmismatch, t = mismatch, permutations[i] + end + end + + if bestmismatch ~= nil then + -- Report an error for all possible types at bestmismatch index. + local i, expected = bestmismatch + if t.dots and i > #t then + expected = typesplit (t[#t]) + else + expected = projectuniq (i, permutations) + end + + -- This relies on the `permute()` algorithm leaving the longest + -- possible permutation (with dots if necessary) at permutations[1]. + local typelist = permutations[1] + + -- For "container of things", check all elements are a thing too. + if typelist[i] then + local check, contents = string_match (typelist[i], "^(%S+) of (%S-)s?$") + if contents and type (valuelist[i]) == "table" then + for k, v in _pairs (valuelist[i]) do + if not checktype (contents, v) then + argt.badtype (i, extramsg_mismatch (expected, v, k), 3) + end + end + end + end + + -- Otherwise the argument type itself was mismatched. + if t.dots or #t >= maxn (valuelist) then + argt.badtype (i, extramsg_mismatch (expected, valuelist[i]), 3) + end + end + + local n, t = maxn (valuelist), t or permutations[1] + if t and t.dots == nil and n > #t then + argt.badtype (#t + 1, extramsg_toomany (argt.bad, #t, n), 3) + end + end + + + function argcheck (name, i, expected, actual, level) + level = level or 2 + expected = typesplit (expected) + + -- Check actual has one of the types from expected + local ok = false + for _, expect in _ipairs (expected) do + local check, contents = string_match (expect, "^(%S+) of (%S-)s?$") + check = check or expect + + -- Does the type of actual check out? + ok = checktype (check, actual) + + -- For "table of things", check all elements are a thing too. + if ok and contents and type (actual) == "table" then + for k, v in _pairs (actual) do + if not checktype (contents, v) then + argerror (name, i, extramsg_mismatch (expected, v, k), level + 1) + end + end + end + if ok then break end + end + + if not ok then + argerror (name, i, extramsg_mismatch (expected, actual), level + 1) + end + end + + + -- Pattern to extract: fname ([types]?[, types]*) + local args_pat = "^%s*([%w_][%.%:%d%w_]*)%s*%(%s*(.*)%s*%)" + + function argscheck (decl, inner) + -- Parse "fname (argtype, argtype, argtype...)". + local fname, argtypes = string_match (decl, args_pat) + if argtypes == "" then + argtypes = {} + elseif argtypes then + argtypes = split (argtypes, "%s*,%s*") + else + fname = string_match (decl, "^%s*([%w_][%.%:%d%w_]*)") + end + + -- Precalculate vtables once to make multiple calls faster. + local input, output = { + bad = "argument", + badtype = function (i, extramsg, level) + level = level or 1 + argerror (fname, i, extramsg, level + 1) + end, + permutations = permute (argtypes), + } + + -- Parse "... => returntype, returntype, returntype...". + local returntypes = string_match (decl, "=>%s*(.+)%s*$") + if returntypes then + local i, permutations = 0, {} + for _, group in _ipairs (split (returntypes, "%s+or%s+")) do + returntypes = split (group, ",%s*") + for _, t in _ipairs (permute (returntypes)) do + i = i + 1 + permutations[i] = t + end + end + + -- Ensure the longest permutation is first in the list. + table_sort (permutations, function (a, b) return #a > #b end) + + output = { + bad = "result", + badtype = function (i, extramsg, level) + level = level or 1 + resulterror (fname, i, extramsg, level + 1) + end, + permutations = permutations, + } + end + + return function (...) + local argt = {...} + + -- Don't check type of self if fname has a ':' in it. + if string_find (fname, ":") then table_remove (argt, 1) end + + -- Diagnose bad inputs. + diagnose (argt, input) + + -- Propagate outer environment to inner function. + local x = math_max -- ??? FIXME: getfenv(1) fails if we remove this ??? + _setfenv (inner, _getfenv (1)) + + -- Execute. + local results = {inner (...)} + + -- Diagnose bad outputs. + if returntypes then + diagnose (results, output) + end + + return unpack (results) + end + end + +else + + -- Turn off argument checking if _DEBUG is false, or a table containing + -- a false valued `argcheck` field. + + argcheck = nop + argscheck = function (decl, inner) return inner end + +end + + +return { + --- Check the type of an argument against expected types. + -- Equivalent to luaL_argcheck in the Lua C API. + -- + -- Call `argerror` if there is a type mismatch. + -- + -- Argument `actual` must match one of the types from in `expected`, each + -- of which can be the name of a primitive Lua type, a stdlib object type, + -- or one of the special options below: + -- + -- #table accept any non-empty table + -- any accept any non-nil argument type + -- file accept an open file object + -- function accept a function, or object with a __call metamethod + -- int accept an integer valued number + -- list accept a table where all keys are a contiguous 1-based integer range + -- #list accept any non-empty list + -- object accept any std.Object derived type + -- :foo accept only the exact string ":foo", works for any :-prefixed string + -- + -- The `:foo` format allows for type-checking of self-documenting + -- boolean-like constant string parameters predicated on `nil` versus + -- `:option` instead of `false` versus `true`. Or you could support + -- both: + -- + -- argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa) + -- + -- A very common pattern is to have a list of possible types including + -- "nil" when the argument is optional. Rather than writing long-hand + -- as above, prepend a question mark to the list of types and omit the + -- explicit "nil" entry: + -- + -- argcheck ("table.copy", 2, "?boolean|:nometa", predicate) + -- + -- Normally, you should not need to use the `level` parameter, as the + -- default is to blame the caller of the function using `argcheck` in + -- error messages; which is almost certainly what you want. + -- @function argcheck + -- @string name function to blame in error message + -- @int i argument number to blame in error message + -- @string expected specification for acceptable argument types + -- @param actual argument passed + -- @int[opt=2] level call stack level to blame for the error + -- @usage + -- local function case (with, branches) + -- argcheck ("std.functional.case", 2, "#table", branches) + -- ... + argcheck = argcheck, + + --- Raise a bad argument error. + -- Equivalent to luaL_argerror in the Lua C API. This function does not + -- return. The `level` argument behaves just like the core `error` + -- function. + -- @function argerror + -- @string name function to callout in error message + -- @int i argument number + -- @string[opt] extramsg additional text to append to message inside parentheses + -- @int[opt=1] level call stack level to blame for the error + -- @see resulterror + -- @see extramsg_mismatch + -- @usage + -- local function slurp (file) + -- local h, err = input_handle (file) + -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end + -- ... + argerror = argerror, + + --- Wrap a function definition with argument type and arity checking. + -- In addition to checking that each argument type matches the corresponding + -- element in the *types* table with `argcheck`, if the final element of + -- *types* ends with an ellipsis, remaining unchecked arguments are checked + -- against that type: + -- + -- format = argscheck ("string.format (string, ?any...)", string.format) + -- + -- A colon in the function name indicates that the argument type list does + -- not have a type for `self`: + -- + -- format = argscheck ("string:format (?any...)", string.format) + -- + -- If an argument can be omitted entirely, then put its type specification + -- in square brackets: + -- + -- insert = argscheck ("table.insert (table, [int], ?any)", table.insert) + -- + -- Similarly return types can be checked with the same list syntax as + -- arguments: + -- + -- len = argscheck ("string.len (string) => int", string.len) + -- + -- Additionally, variant return type lists can be listed like this: + -- + -- open = argscheck ("io.open (string, ?string) => file or nil, string", + -- io.open) + -- + -- @function argscheck + -- @string decl function type declaration string + -- @func inner function to wrap with argument checking + -- @usage + -- local case = argscheck ("std.functional.case (?any, #table) => [any...]", + -- function (with, branches) + -- ... + -- end) + argscheck = argscheck, + + --- Format a type mismatch error. + -- @function extramsg_mismatch + -- @string expected a pipe delimited list of matchable types + -- @param actual the actual argument to match with + -- @number[opt] index erroring container element index + -- @treturn string formatted *extramsg* for this mismatch for @{argerror} + -- @see argerror + -- @see resulterror + -- @usage + -- if fmt ~= nil and type (fmt) ~= "string" then + -- argerror ("format", 1, extramsg_mismatch ("?string", fmt)) + -- end + extramsg_mismatch = function (expected, actual, index) + return extramsg_mismatch (typesplit (expected), actual, index) + end, + + --- Format a too many things error. + -- @function extramsg_toomany + -- @string bad the thing there are too many of + -- @int expected maximum number of *bad* things expected + -- @int actual actual number of *bad* things that triggered the error + -- @see argerror + -- @see resulterror + -- @see extramsg_mismatch + -- @usage + -- if maxn (argt) > 7 then + -- argerror ("sevenses", 8, extramsg_toomany ("argument", 7, maxn (argt))) + -- end + extramsg_toomany = extramsg_toomany, + + --- Compact permutation list into a list of valid types at each argument. + -- Eliminate bracketed types by combining all valid types at each position + -- for all permutations of *typelist*. + -- @function parsetypes + -- @tparam list types a normalized list of type names + -- @treturn list valid types for each positional parameter + parsetypes = parsetypes, + + --- Raise a bad result error. + -- Like @{argerror} for bad results. This function does not + -- return. The `level` argument behaves just like the core `error` + -- function. + -- @function resulterror + -- @string name function to callout in error message + -- @int i result number + -- @string[opt] extramsg additional text to append to message inside parentheses + -- @int[opt=1] level call stack level to blame for the error + -- @usage + -- local function slurp (file) + -- ... + -- if type (result) ~= "string" then resulterror ("std.io.slurp", 1, err, 2) end + resulterror = resulterror, + + --- Split a typespec string into a table of normalized type names. + -- @function typesplit + -- @tparam string|table either `"?bool|:nometa"` or `{"boolean", ":nometa"}` + -- @treturn table a new list with duplicates removed and leading "?"s + -- replaced by a "nil" element + typesplit = typesplit, +} diff --git a/local.mk b/local.mk index 24d08d4..04dbccd 100644 --- a/local.mk +++ b/local.mk @@ -84,6 +84,7 @@ dist_luastd_DATA = \ lib/std/table.lua \ lib/std/tree.lua \ lib/std/tuple.lua \ + lib/std/typing.lua \ lib/std/version.lua \ $(NOTHING_ELSE) @@ -165,6 +166,7 @@ dist_docmodules_DATA += \ $(docmodules).maturity.html \ $(docmodules).optparse.html \ $(docmodules).strict.html \ + $(docmodules).typing.html \ $(NOTHING_ELSE) dist_docobjects_DATA += \ diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 891278e..7aa5d49 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -3,13 +3,20 @@ before: | this_module = "std.debug" global_table = "_G" - extend_base = { "argcheck", "argerror", "argscheck", "extramsg_mismatch", - "extramsg_toomany", "getfenv", "parsetypes", "resulterror", - "setfenv", "say", "toomanyargmsg", "typesplit", "trace", - "_setdebug" } + extend_base = { "getfenv", "setfenv", "say", "trace", "_setdebug" } + + deprecations = { "argcheck", "argerror", "argscheck", "extramsg_mismatch", + "extramsg_toomany", "parsetypes", "resulterror", + "toomanyargmsg", "typesplit" } + + setdebug { deprecate = false } + + deprecate_on = bind (deprecation, {"nil", this_module}) + deprecate_off = bind (deprecation, {false, this_module}) M = require (this_module) + specify std.debug: - context when required: - context by name: @@ -20,8 +27,11 @@ specify std.debug: expect (show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core debug table: + apis = {} + for _, v in ipairs (extend_base) do apis[#apis + 1] = v end + for _, v in ipairs (deprecations) do apis[#apis + 1] = v end expect (show_apis {from=base_module, not_in=this_module}). - to_contain.a_permutation_of (extend_base) + to_contain.a_permutation_of (apis) - context via the std module: - it does not touch the global table: @@ -36,7 +46,7 @@ specify std.debug: - before: | function mkstack (level) return string.format ([[ - _DEBUG = true -- line 1 + _DEBUG = nil -- line 1 local debug = require "std.debug" -- line 2 function ohnoes () -- line 3 debug.resulterror ("ohnoes", 1, nil, %s) -- line 4 @@ -51,6 +61,12 @@ specify std.debug: f = M.resulterror + - it writes a deprecation warning: + expect (deprecate_on ("resulterror", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("resulterror", "{}")). + not_to_contain_error "was deprecated" + - it blames the call site by default: | expect (luaproc (mkstack ())).to_contain_error ":4: bad result" - it honors optional call stack level reporting: | @@ -68,7 +84,7 @@ specify std.debug: - before: | function mkstack (level) return string.format ([[ - _DEBUG = true -- line 1 + _DEBUG = nil -- line 1 local debug = require "std.debug" -- line 2 function ohnoes () -- line 3 debug.argerror ("ohnoes", 1, nil, %s) -- line 4 @@ -83,6 +99,12 @@ specify std.debug: f, badarg = init (M, this_module, "argerror") + - it writes a deprecation warning: + expect (deprecate_on ("argerror", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("argerror", "{}")). + not_to_contain_error "was deprecated" + - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" expect (f ()).to_raise (badarg (1, "string")) @@ -122,21 +144,28 @@ specify std.debug: function mkstack (level, debugp) return string.format ([[ - _DEBUG = %s -- line 1 - local debug = require "std.debug" -- line 2 - function ohnoes (t) -- line 3 - debug.argcheck ("ohnoes", 1, "table", t, %s) -- line 4 - end -- line 5 - function caller () -- line 6 - local r = ohnoes "not a table" -- line 7 - return "not a tail call" -- line 8 - end -- line 9 - caller () -- line 10 + _DEBUG = %s -- line 1 + local debug = require "std.debug" -- line 2 + require "std.debug_init"._DEBUG.deprecate = false -- line 3 + function ohnoes (t) -- line 4 + debug.argcheck ("ohnoes", 1, "table", t, %s) -- line 5 + end -- line 6 + function caller () -- line 7 + local r = ohnoes "not a table" -- line 8 + return "not a tail call" -- line 9 + end -- line 10 + caller () -- line 11 ]], tostring (debugp), tostring (level)) end f, badarg = init (M, this_module, "argcheck") + - it writes a deprecation warning: + expect (deprecate_on ("argcheck", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("argcheck", "{}")). + not_to_contain_error "was deprecated" + - it diagnoses missing arguments: pending "Lua 5.1 support is dropped" expect (f ()).to_raise (badarg (1, "string")) @@ -154,11 +183,11 @@ specify std.debug: expect (f ("foo", 1, "bar", 2, 3, false)).to_raise (badarg (6)) - it blames the calling function by default: | - expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" + expect (luaproc (mkstack ())).to_contain_error ":8: bad argument" - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" - expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" - expect (luaproc (mkstack (3))).to_contain_error ":10: bad argument" + expect (luaproc (mkstack (1))).to_contain_error ":5: bad argument" + expect (luaproc (mkstack (2))).to_contain_error ":8: bad argument" + expect (luaproc (mkstack (3))).to_contain_error ":11: bad argument" - it can be disabled by setting _DEBUG to false: expect (luaproc (mkstack (nil, false))). not_to_contain_error "bad argument" @@ -522,6 +551,12 @@ specify std.debug: _, badarg, badresult = init (M, "", "inner") id = function (...) return ... end + - it writes a deprecation warning: + expect (deprecate_on ("argscheck", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("argscheck", "{}")). + not_to_contain_error "was deprecated" + - it returns the wrapped function: expect (wrapped).not_to_be (inner) expect (wrapped ()).to_be "MAGIC" @@ -893,6 +928,12 @@ specify std.debug: - before: f = M.extramsg_mismatch + - it writes a deprecation warning: + expect (deprecate_on ("extramsg_mismatch", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("extramsg_mismatch", "{}")). + not_to_contain_error "was deprecated" + - it returns the expected types: expect (f "nil").to_contain "nil expected, " expect (f "bool").to_contain "boolean expected, " @@ -920,6 +961,12 @@ specify std.debug: - before: f = M.extramsg_toomany + - it writes a deprecation warning: + expect (deprecate_on ("extramsg_toomany", "{}")). + to_contain_error "was deprecated" + expect (deprecate_off ("extramsg_toomany", "{}")). + not_to_contain_error "was deprecated" + - it returns the expected thing: expect (f ("mojo", 1, 2)).to_contain "no more than 1 mojo" - it uses singular thing when 1 is expected: diff --git a/specs/specs.mk b/specs/specs.mk index 86158f0..e57b601 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -31,6 +31,7 @@ specl_SPECS = \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ $(srcdir)/specs/tuple_spec.yaml \ + $(srcdir)/specs/typing_spec.yaml \ $(srcdir)/specs/std_spec.yaml \ $(NOTHING_ELSE) diff --git a/specs/typing_spec.yaml b/specs/typing_spec.yaml new file mode 100644 index 0000000..1d6a156 --- /dev/null +++ b/specs/typing_spec.yaml @@ -0,0 +1,872 @@ +before: + this_module = "std.typing" + + M = require (this_module) + +specify std.typing: +- describe require: + - it does not perturb the global namespace: + expect (show_apis {added_to="_G", by="std.typing"}). + to_equal {} + + +- describe resulterror: + - before: | + function mkstack (level) + return string.format ([[ + _DEBUG = true -- line 1 + local typing = require "std.typing" -- line 2 + function ohnoes () -- line 3 + typing.resulterror ("ohnoes", 1, nil, %s) -- line 4 + end -- line 5 + function caller () -- line 6 + local r = ohnoes () -- line 7 + return "not a tail call" -- line 8 + end -- line 9 + caller () -- line 10 + ]], tostring (level)) + end + + f = M.resulterror + + - it blames the call site by default: | + expect (luaproc (mkstack ())).to_contain_error ":4: bad result" + - it honors optional call stack level reporting: | + expect (luaproc (mkstack (1))).to_contain_error ":4: bad result" + expect (luaproc (mkstack (2))).to_contain_error ":7: bad result" + - it reports the calling function name: + expect (f ('expect', 1)).to_raise "'expect'" + - it reports the argument number: | + expect (f ('expect', 12345)).to_raise "#12345" + - it reports extra message in parentheses: + expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" + + +- describe argerror: + - before: | + function mkstack (level) + return string.format ([[ + _DEBUG = true -- line 1 + local typing = require "std.typing" -- line 2 + function ohnoes () -- line 3 + typing.argerror ("ohnoes", 1, nil, %s) -- line 4 + end -- line 5 + function caller () -- line 6 + local r = ohnoes () -- line 7 + return "not a tail call" -- line 8 + end -- line 9 + caller () -- line 10 + ]], tostring (level)) + end + + f, badarg = init (M, this_module, "argerror") + + - it diagnoses missing arguments: + pending "Lua 5.1 support is dropped" + expect (f ()).to_raise (badarg (1, "string")) + expect (f "foo").to_raise (badarg (2, "int")) + - it diagnoses wrong argument types: + pending "Lua 5.1 support is dropped" + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (f ("foo", 1, false)). + to_raise (badarg (3, "string or nil", "boolean")) + expect (f ("foo", 1, "bar", false)). + to_raise (badarg (4, "int or nil", "boolean")) + - it diagnoses too many arguments: + pending "Lua 5.1 support is dropped" + expect (f ("foo", 1, "bar", 2, false)).to_raise (badarg (5)) + + - it blames the call site by default: | + expect (luaproc (mkstack ())).to_contain_error ":4: bad argument" + - it honors optional call stack level reporting: | + expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" + expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" + - it reports the calling function name: + expect (f ('expect', 1)).to_raise "'expect'" + - it reports the argument number: | + expect (f ('expect', 12345)).to_raise "#12345" + - it reports extra message in parentheses: + expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" + + +- describe argcheck: + - before: | + Object = require "std.object".prototype + List = Object { _type = "List" } + Foo = Object { _type = "Foo" } + + function fn (...) return M.argcheck ('expect', 1, ...) end + + function mkstack (level, debugp) + return string.format ([[ + _DEBUG = %s -- line 1 + local typing = require "std.typing" -- line 2 + function ohnoes (t) -- line 3 + typing.argcheck ("ohnoes", 1, "table", t, %s) -- line 4 + end -- line 5 + function caller () -- line 6 + local r = ohnoes "not a table" -- line 7 + return "not a tail call" -- line 8 + end -- line 9 + caller () -- line 10 + ]], tostring (debugp), tostring (level)) + end + + f, badarg = init (M, this_module, "argcheck") + + - it diagnoses missing arguments: + pending "Lua 5.1 support is dropped" + expect (f ()).to_raise (badarg (1, "string")) + expect (f "foo").to_raise (badarg (2, "int")) + expect (f ("foo", 1)).to_raise (badarg (3, "string")) + - it diagnoses wrong argument types: + pending "Lua 5.1 support is dropped" + expect (f (false)).to_raise (badarg (1, "string", "boolean")) + expect (f ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (f ("foo", 1, false)).to_raise (badarg (3, "string", "boolean")) + expect (f ("foo", 1, "bar", 2, false)). + to_raise (badarg (5, "int or nil", "boolean")) + - it diagnoses too many arguments: + pending "Lua 5.1 support is dropped" + expect (f ("foo", 1, "bar", 2, 3, false)).to_raise (badarg (6)) + + - it blames the calling function by default: | + expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" + - it honors optional call stack level reporting: | + expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" + expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" + expect (luaproc (mkstack (3))).to_contain_error ":10: bad argument" + - it can be disabled by setting _DEBUG to false: + expect (luaproc (mkstack (nil, false))). + not_to_contain_error "bad argument" + - it can be disabled by setting _DEBUG.argcheck to false: + expect (luaproc (mkstack (nil, "{ argcheck = false }"))). + not_to_contain_error "bad argument" + - it is not disabled by setting _DEBUG.argcheck to true: + expect (luaproc (mkstack (nil, "{ argcheck = true }"))). + to_contain_error "bad argument" + - it is not disabled by leaving _DEBUG.argcheck unset: + expect (luaproc (mkstack (nil, "{}"))). + to_contain_error "bad argument" + + - context with primitives: + - it diagnoses missing types: + expect (fn ("bool", nil)).to_raise "boolean expected, got no value" + expect (fn ("boolean", nil)).to_raise "boolean expected, got no value" + expect (fn ("file", nil)).to_raise "FILE* expected, got no value" + expect (fn ("number", nil)).to_raise "number expected, got no value" + expect (fn ("string", nil)).to_raise "string expected, got no value" + expect (fn ("table", nil)).to_raise "table expected, got no value" + - it diagnoses mismatched types: + expect (fn ("bool", {0})).to_raise "boolean expected, got table" + expect (fn ("boolean", {0})).to_raise "boolean expected, got table" + expect (fn ("file", {0})).to_raise "FILE* expected, got table" + expect (fn ("number", {0})).to_raise "number expected, got table" + expect (fn ("string", {0})).to_raise "string expected, got table" + expect (fn ("table", false)).to_raise "table expected, got boolean" + - it matches types: + expect (fn ("bool", true)).not_to_raise "any error" + expect (fn ("boolean", true)).not_to_raise "any error" + expect (fn ("file", io.stderr)).not_to_raise "any error" + expect (fn ("number", 1)).not_to_raise "any error" + expect (fn ("string", "s")).not_to_raise "any error" + expect (fn ("table", {})).not_to_raise "any error" + expect (fn ("table", require "std.object")).not_to_raise "any error" + + - context with int: + - it diagnoses missing types: + expect (fn ("int", nil)).to_raise "int expected, got no value" + - it diagnoses mismatched types: + expect (fn ("int", false)).to_raise "int expected, got boolean" + expect (fn ("int", 1.234)).to_raise "int expected, got number" + expect (fn ("int", 1234e-3)).to_raise "int expected, got number" + - it matches types: + expect (fn ("int", 1)).not_to_raise "any error" + expect (fn ("int", 1.0)).not_to_raise "any error" + expect (fn ("int", 0x1234)).not_to_raise "any error" + expect (fn ("int", 1.234e3)).not_to_raise "any error" + - context with constant string: + - it diagnoses missing types: + expect (fn (":foo", nil)).to_raise ":foo expected, got no value" + - it diagnoses mismatched types: + expect (fn (":foo", false)).to_raise ":foo expected, got boolean" + expect (fn (":foo", ":bar")).to_raise ":foo expected, got :bar" + expect (fn (":foo", "foo")).to_raise ":foo expected, got string" + - it matches types: + expect (fn (":foo", ":foo")).not_to_raise "any error" + - context with callable types: + - it diagnoses missing types: + expect (fn ("func", nil)).to_raise "function expected, got no value" + expect (fn ("function", nil)).to_raise "function expected, got no value" + - it diagnoses mismatched types: + expect (fn ("func", {0})).to_raise "function expected, got table" + expect (fn ("function", {0})).to_raise "function expected, got table" + - it matches types: + expect (fn ("func", function () end)).not_to_raise "any error" + expect (fn ("func", setmetatable ({}, {__call = function () end}))). + not_to_raise "any error" + expect (fn ("function", function () end)).not_to_raise "any error" + expect (fn ("function", setmetatable ({}, {__call = function () end}))). + not_to_raise "any error" + - context with table of homogenous elements: + - it diagnoses missing types: + expect (fn ("table of boolean", nil)). + to_raise "table expected, got no value" + expect (fn ("table of booleans", nil)). + to_raise "table expected, got no value" + - it diagnoses mismatched types: + expect (fn ("table of file", io.stderr)). + to_raise "table expected, got file" + expect (fn ("table of files", io.stderr)). + to_raise "table expected, got file" + - it diagnoses mismatched element types: + expect (fn ("table of number", {false})). + to_raise "table of numbers expected, got boolean at index 1" + expect (fn ("table of numbers", {1, 2, "3"})). + to_raise "table of numbers expected, got string at index 3" + expect (fn ("table of numbers", {a=1, b=2, c="3"})). + to_raise "table of numbers expected, got string at index c" + - it matches types: + expect (fn ("table of string", {})).not_to_raise "any error" + expect (fn ("table of string", {"foo"})).not_to_raise "any error" + expect (fn ("table of string", {"f", "o", "o"})).not_to_raise "any error" + expect (fn ("table of string", {b="b", a="a", r="r"})).not_to_raise "any error" + - context with non-empty table types: + - it diagnoses missing types: + expect (fn ("#table", nil)). + to_raise "non-empty table expected, got no value" + - it diagnoses mismatched types: + expect (fn ("#table", false)). + to_raise "non-empty table expected, got boolean" + expect (fn ("#table", {})). + to_raise "non-empty table expected, got empty table" + - it matches types: + expect (fn ("#table", {0})).not_to_raise "any error" + - context with non-empty table of homogenous elements: + - it diagnoses missing types: + expect (fn ("#table of boolean", nil)). + to_raise "non-empty table expected, got no value" + expect (fn ("#table of booleans", nil)). + to_raise "non-empty table expected, got no value" + - it diagnoses mismatched types: + expect (fn ("#table of file", {})). + to_raise "non-empty table expected, got empty table" + expect (fn ("#table of file", io.stderr)). + to_raise "non-empty table expected, got file" + - it diagnoses mismatched element types: + expect (fn ("#table of number", {false})). + to_raise "non-empty table of numbers expected, got boolean at index 1" + expect (fn ("#table of numbers", {1, 2, "3"})). + to_raise "non-empty table of numbers expected, got string at index 3" + expect (fn ("#table of numbers", {a=1, b=2, c="3"})). + to_raise "non-empty table of numbers expected, got string at index c" + - it matches types: + expect (fn ("#table of string", {"foo"})).not_to_raise "any error" + expect (fn ("#table of string", {"f", "o", "o"})).not_to_raise "any error" + expect (fn ("#table of string", {b="b", a="a", r="r"})).not_to_raise "any error" + - context with list: + - it diagnonses missing types: + expect (fn ("list", nil)). + to_raise "list expected, got no value" + - it diagnoses mismatched types: + expect (fn ("list", false)). + to_raise "list expected, got boolean" + expect (fn ("list", {foo=1})). + to_raise "list expected, got table" + expect (fn ("list", Object)). + to_raise "list expected, got Object" + - it matches types: + expect (fn ("list", {})).not_to_raise "any error" + expect (fn ("list", {1})).not_to_raise "any error" + - context with list of homogenous elements: + - it diagnoses missing types: + expect (fn ("list of boolean", nil)). + to_raise "list expected, got no value" + expect (fn ("list of booleans", nil)). + to_raise "list expected, got no value" + - it diagnoses mismatched types: + expect (fn ("list of file", io.stderr)). + to_raise "list expected, got file" + expect (fn ("list of files", io.stderr)). + to_raise "list expected, got file" + expect (fn ("list of files", {file=io.stderr})). + to_raise "list expected, got table" + - it diagnoses mismatched element types: + expect (fn ("list of number", {false})). + to_raise "list of numbers expected, got boolean at index 1" + expect (fn ("list of numbers", {1, 2, "3"})). + to_raise "list of numbers expected, got string at index 3" + - it matches types: + expect (fn ("list of string", {})).not_to_raise "any error" + expect (fn ("list of string", {"foo"})).not_to_raise "any error" + expect (fn ("list of string", {"f", "o", "o"})).not_to_raise "any error" + - context with non-empty list: + - it diagnonses missing types: + expect (fn ("#list", nil)). + to_raise "non-empty list expected, got no value" + - it diagnoses mismatched types: + expect (fn ("#list", false)). + to_raise "non-empty list expected, got boolean" + expect (fn ("#list", {})). + to_raise "non-empty list expected, got empty list" + expect (fn ("#list", {foo=1})). + to_raise "non-empty list expected, got table" + expect (fn ("#list", Object)). + to_raise "non-empty list expected, got empty Object" + expect (fn ("#list", List {})). + to_raise "non-empty list expected, got empty List" + - it matches types: + expect (fn ("#list", {1})).not_to_raise "any error" + - context with non-empty list of homogenous elements: + - it diagnoses missing types: + expect (fn ("#list of boolean", nil)). + to_raise "non-empty list expected, got no value" + expect (fn ("#list of booleans", nil)). + to_raise "non-empty list expected, got no value" + - it diagnoses mismatched types: + expect (fn ("#list of file", {})). + to_raise "non-empty list expected, got empty table" + expect (fn ("#list of file", io.stderr)). + to_raise "non-empty list expected, got file" + expect (fn ("#list of files", {file=io.stderr})). + to_raise "non-empty list expected, got table" + - it diagnoses mismatched element types: + expect (fn ("#list of number", {false})). + to_raise "non-empty list of numbers expected, got boolean at index 1" + expect (fn ("#list of numbers", {1, 2, "3"})). + to_raise "non-empty list of numbers expected, got string at index 3" + - it matches types: + expect (fn ("#list of string", {"foo"})).not_to_raise "any error" + expect (fn ("#list of string", {"f", "o", "o"})).not_to_raise "any error" + - context with container: + - it diagnoses missing types: + expect (fn ("List of boolean", nil)). + to_raise "List expected, got no value" + expect (fn ("List of booleans", nil)). + to_raise "List expected, got no value" + - it diagnoses mismatched types: + expect (fn ("List of file", io.stderr)). + to_raise "List expected, got file" + expect (fn ("List of files", io.stderr)). + to_raise "List expected, got file" + expect (fn ("List of files", {file=io.stderr})). + to_raise "List expected, got table" + - it diagnoses mismatched element types: + expect (fn ("List of number", List {false})). + to_raise "List of numbers expected, got boolean at index 1" + expect (fn ("List of numbers", List {1, 2, "3"})). + to_raise "List of numbers expected, got string at index 3" + - it matches types: + expect (fn ("list of string", List {})).not_to_raise "any error" + expect (fn ("list of string", List {"foo"})).not_to_raise "any error" + expect (fn ("list of string", List {"f", "o", "o"})).not_to_raise "any error" + - context with object: + - it diagnoses missing types: + expect (fn ("object", nil)).to_raise "object expected, got no value" + expect (fn ("Object", nil)).to_raise "Object expected, got no value" + expect (fn ("Foo", nil)).to_raise "Foo expected, got no value" + expect (fn ("any", nil)).to_raise "any value expected, got no value" + - it diagnoses mismatched types: + expect (fn ("object", {0})).to_raise "object expected, got table" + expect (fn ("Object", {0})).to_raise "Object expected, got table" + expect (fn ("object", {_type="Object"})).to_raise "object expected, got table" + expect (fn ("Object", {_type="Object"})).to_raise "Object expected, got table" + expect (fn ("Object", Foo)).to_raise "Object expected, got Foo" + expect (fn ("Foo", {0})).to_raise "Foo expected, got table" + expect (fn ("Foo", Object)).to_raise "Foo expected, got Object" + - it matches types: + expect (fn ("object", Object)).not_to_raise "any error" + expect (fn ("object", Object {})).not_to_raise "any error" + expect (fn ("object", Foo)).not_to_raise "any error" + expect (fn ("object", Foo {})).not_to_raise "any error" + - it matches anything: + expect (fn ("any", true)).not_to_raise "any error" + expect (fn ("any", {})).not_to_raise "any error" + expect (fn ("any", Object)).not_to_raise "any error" + expect (fn ("any", Foo {})).not_to_raise "any error" + - context with a list of valid types: + - it diagnoses missing elements: + expect (fn ("string|table", nil)). + to_raise "string or table expected, got no value" + expect (fn ("string|list|#table", nil)). + to_raise "string, list or non-empty table expected, got no value" + expect (fn ("string|number|list|object", nil)). + to_raise "string, number, list or object expected, got no value" + - it diagnoses mismatched elements: + expect (fn ("string|table", false)). + to_raise "string or table expected, got boolean" + expect (fn ("string|#table", {})). + to_raise "string or non-empty table expected, got empty table" + expect (fn ("string|number|#list|object", {})). + to_raise "string, number, non-empty list or object expected, got empty table" + - it matches any type from a list: + expect (fn ("string|table", "foo")).not_to_raise "any error" + expect (fn ("string|table", {})).not_to_raise "any error" + expect (fn ("string|table", {0})).not_to_raise "any error" + expect (fn ("table|table", {})).not_to_raise "any error" + expect (fn ("#table|table", {})).not_to_raise "any error" + - context with an optional type element: + - it diagnoses mismatched elements: + expect (fn ("?boolean", "string")). + to_raise "boolean or nil expected, got string" + expect (fn ("?boolean|:symbol", {})). + to_raise "boolean, :symbol or nil expected, got empty table" + - it matches nil against a single type: + expect (fn ("?any", nil)).not_to_raise "any error" + expect (fn ("?boolean", nil)).not_to_raise "any error" + expect (fn ("?string", nil)).not_to_raise "any error" + - it matches nil against a list of types: + expect (fn ("?boolean|table", nil)).not_to_raise "any error" + expect (fn ("?string|table", nil)).not_to_raise "any error" + expect (fn ("?table|#table", nil)).not_to_raise "any error" + expect (fn ("?#table|table", nil)).not_to_raise "any error" + - it matches nil against a list of optional types: + expect (fn ("?boolean|?table", nil)).not_to_raise "any error" + expect (fn ("?string|?table", nil)).not_to_raise "any error" + expect (fn ("?table|?#table", nil)).not_to_raise "any error" + expect (fn ("?#table|?table", nil)).not_to_raise "any error" + - it matches any named type: + expect (fn ("?any", false)).not_to_raise "any error" + expect (fn ("?boolean", false)).not_to_raise "any error" + expect (fn ("?string", "string")).not_to_raise "any error" + - it matches any type from a list: + expect (fn ("?boolean|table", {})).not_to_raise "any error" + expect (fn ("?string|table", {0})).not_to_raise "any error" + expect (fn ("?table|#table", {})).not_to_raise "any error" + expect (fn ("?#table|table", {})).not_to_raise "any error" + - it matches any type from a list with several optional specifiers: + expect (fn ("?boolean|?table", {})).not_to_raise "any error" + expect (fn ("?string|?table", {0})).not_to_raise "any error" + expect (fn ("?table|?table", {})).not_to_raise "any error" + expect (fn ("?#table|?table", {})).not_to_raise "any error" + + +- describe argscheck: + - before: | + function mkstack (name, spec) + return string.format ([[ + local argscheck = require "std.typing".argscheck -- line 1 + local function caller () -- line 2 + argscheck ("%s", function () end) -- line 3 + end -- line 4 + caller () -- line 5 + ]], tostring (name), tostring (spec)) + end + + f = M.argscheck + + mkmagic = function () return "MAGIC" end + wrapped = f ("inner ()", mkmagic) + + _, badarg, badresult = init (M, "", "inner") + id = function (...) return ... end + + - it returns the wrapped function: + expect (wrapped).not_to_be (inner) + expect (wrapped ()).to_be "MAGIC" + - it does not wrap the function when _ARGCHECK is disabled: | + script = [[ + _DEBUG = false + local debug = require "std.debug_init" + local argscheck = require "std.typing".argscheck + local function inner () return "MAGIC" end + local wrapped = argscheck ("inner (?any)", inner) + os.exit (wrapped == inner and 0 or 1) + ]] + expect (luaproc (script)).to_succeed () + + - context when checking zero argument function: + - it diagnoses too many arguments: + expect (wrapped (false)).to_raise (badarg (1)) + - it accepts correct argument types: + expect (wrapped ()).to_be "MAGIC" + + - context when checking single argument function: + - before: + wrapped = f ("inner (#table)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "non-empty table")) + - it diagnoses wrong argument types: + expect (wrapped {}).to_raise (badarg (1, "non-empty table", "empty table")) + - it diagnoses too many arguments: + expect (wrapped ({1}, 2, nop, "", false)).to_raise (badarg (1, 5)) + - it accepts correct argument types: + expect (wrapped ({1})).to_be "MAGIC" + + - context when checking multi-argument function: + - before: + wrapped = f ("inner (table, function)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "table")) + expect (wrapped ({})).to_raise (badarg (2, "function")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "table", "boolean")) + expect (wrapped ({}, false)).to_raise (badarg (2, "function", "boolean")) + - it diagnoses too many arguments: + expect (wrapped ({}, nop, false)).to_raise (badarg (3)) + - it accepts correct argument types: + expect (wrapped ({}, nop)).to_be "MAGIC" + + - context when checking nil argument function: + - before: + wrapped = f ("inner (?int, string)", mkmagic) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) + expect (wrapped (1, false)).to_raise (badarg (2, "string", "boolean")) + expect (wrapped (nil, false)).to_raise (badarg (2, "string", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, "foo", nop)).to_raise (badarg (3)) + expect (wrapped (nil, "foo", nop)).to_raise (badarg (3)) + - it accepts correct argument types: + expect (wrapped (1, "foo")).to_be "MAGIC" + expect (wrapped (nil, "foo")).to_be "MAGIC" + + - context when checking optional multi-argument function: + - before: + wrapped = f ("inner ([int], string)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "int or string")) + expect (wrapped (1)).to_raise (badarg (2, "string")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "int or string", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, "two", nop)).to_raise (badarg (3)) + - it accepts correct argument types: + expect (wrapped ("two")).to_be "MAGIC" + expect (wrapped (1, "two")).to_be "MAGIC" + + - context when checking final optional multi-argument function: + - before: + wrapped = f ("inner (?any, ?string, [any])", mkmagic) + - it diagnoses wrong argument types: + expect (wrapped (1, false)).to_raise (badarg (2, "string or nil", "boolean")) + expect (wrapped (nil, false)).to_raise (badarg (2, "string or nil", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, "two", 3, false)).to_raise (badarg (4)) + expect (wrapped (nil, "two", 3, false)).to_raise (badarg (4)) + expect (wrapped (1, nil, 3, false)).to_raise (badarg (4)) + expect (wrapped (nil, nil, 3, false)).to_raise (badarg (4)) + - it accepts correct argument types: + expect (wrapped ()).to_be "MAGIC" + expect (wrapped (1)).to_be "MAGIC" + expect (wrapped (nil, "two")).to_be "MAGIC" + expect (wrapped (1, "two")).to_be "MAGIC" + expect (wrapped (nil, nil, 3)).to_be "MAGIC" + expect (wrapped (1, nil, 3)).to_be "MAGIC" + expect (wrapped (nil, "two", 3)).to_be "MAGIC" + expect (wrapped ("one", "two", 3)).to_be "MAGIC" + + - context when checking final ellipsis function: + - before: + wrapped = f ("inner (string, int...)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "string")) + expect (wrapped ("foo")).to_raise (badarg (2, "int")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badarg (12, "int", "boolean")) + - it accepts correct argument types: + expect (wrapped ("foo", 1)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + + - context when checking optional final parameter: + - context with single argument: + - before: + wrapped = f ("inner ([int])", mkmagic) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "int", "boolean")) + - it diagnoses too many arguments: + expect (wrapped (1, nop)).to_raise (badarg (2)) + - it accepts correct argument types: + expect (wrapped ()).to_be "MAGIC" + expect (wrapped (1)).to_be "MAGIC" + - context with trailing ellipsis: + - before: + wrapped = f ("inner (string, [int]...)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "string")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badarg (12, "int", "boolean")) + - it accepts correct argument types: + expect (wrapped ("foo")).to_be "MAGIC" + expect (wrapped ("foo", 1)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + - context with inner ellipsis: + - before: + wrapped = f ("inner (string, [int...])", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "string")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badarg (12, "int", "boolean")) + - it accepts correct argument types: + expect (wrapped ("foo")).to_be "MAGIC" + expect (wrapped ("foo", 1)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2)).to_be "MAGIC" + expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" + + - context when omitting self type: + - before: + me = { + wrapped = f ("me:inner (string)", mkmagic) + } + _, badarg, badresult = init (M, "", "me:inner") + - it diagnoses missing arguments: + expect (me:wrapped ()).to_raise (badarg (1, "string")) + - it diagnoses wrong argument types: + expect (me:wrapped (false)).to_raise (badarg (1, "string", "boolean")) + - it diagnoses too many arguments: + expect (me:wrapped ("foo", false)).to_raise (badarg (2)) + - it accepts correct argument types: + expect (me:wrapped ("foo")).to_be "MAGIC" + + - context with too many args: + - before: + wrapped = f ("inner ([string], int)", mkmagic) + - it diagnoses missing arguments: + expect (wrapped ()).to_raise (badarg (1, "string or int")) + expect (wrapped ("one")).to_raise (badarg (2, "int")) + - it diagnoses wrong argument types: + expect (wrapped (false)).to_raise (badarg (1, "string or int", "boolean")) + expect (wrapped ("one", false)).to_raise (badarg (2, "int", "boolean")) + - it diagnoses too many arguments: + expect (wrapped ("one", 2, false)).to_raise (badarg (3)) + expect (wrapped (1, false)).to_raise (badarg (2)) + - it accepts correct argument types: + expect (wrapped (1)).to_be "MAGIC" + expect (wrapped ("one", 2)).to_be "MAGIC" + + - context when checking single return value function: + - before: | + wrapped = f ("inner (?any...) => #table", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "non-empty table")) + - it diagnoses wrong result types: + expect (wrapped {}). + to_raise (badresult (1, "non-empty table", "empty table")) + - it diagnoses too many results: + expect (wrapped ({1}, 2, nop, "", false)).to_raise (badresult (1, 5)) + - it accepts correct results: + expect ({wrapped {1}}).to_equal {{1}} + + - context with variant single return value function: + - before: + wrapped = f ("inner (?any...) => int or nil", id) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, nop)).to_raise (badresult (2)) + - it accepts correct result types: + expect ({wrapped ()}).to_equal {} + expect ({wrapped (1)}).to_equal {1} + + - context when checking multi-return value function: + - before: + wrapped = f ("inner (?any...) => int, string", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "int")) + expect (wrapped (1)).to_raise (badresult (2, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int", "boolean")) + expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", false)).to_raise (badresult (3)) + - it accepts correct argument types: + expect ({wrapped (1, "two")}).to_equal {1, "two"} + + - context when checking nil return specifier: + - before: + wrapped = f ("inner (?any...) => ?int, string", id) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) + expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) + expect (wrapped (nil, false)).to_raise (badresult (2, "string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "foo", nop)).to_raise (badresult (3)) + expect (wrapped (nil, "foo", nop)).to_raise (badresult (3)) + - it accepts correct result types: + expect ({wrapped (1, "foo")}).to_equal {1, "foo"} + expect ({wrapped (nil, "foo")}).to_equal {[2] = "foo"} + + - context when checking variant multi-return value function: + - before: + wrapped = f ("inner (?any...) => int, string or string", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "int or string")) + expect (wrapped (1)).to_raise (badresult (2, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", nop)).to_raise (badresult (3)) + - it accepts correct result types: + expect ({wrapped ("two")}).to_equal {"two"} + expect ({wrapped (1, "two")}).to_equal {1, "two"} + + - context when checking variant nil,errmsg pattern function: + - before: + wrapped = f ("inner (?any...) => int, string or nil, string", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (2, "string")) + expect (wrapped (1)).to_raise (badresult (2, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) + expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", nop)).to_raise (badresult (3)) + expect (wrapped (nil, "errmsg", nop)).to_raise (badresult (3)) + - it accepts correct result types: + expect ({wrapped (1, "two")}).to_equal {1, "two"} + expect ({wrapped (nil, "errmsg")}).to_equal {[2] = "errmsg"} + + - context when checking optional multi-return value function: + - before: + wrapped = f ("inner (?any...) => [int], string", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "int or string")) + expect (wrapped (1)).to_raise (badresult (2, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int or string", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", nop)).to_raise (badresult (3)) + - it accepts correct result types: + expect ({wrapped ("two")}).to_equal {"two"} + expect ({wrapped (1, "two")}).to_equal {1, "two"} + + - context when checking final optional multi-return value function: + - before: + wrapped = f ("inner (?any...) => ?any, ?string, [any]", id) + - it diagnoses wrong result types: + expect (wrapped (1, false)).to_raise (badresult (2, "string or nil", "boolean")) + expect (wrapped (nil, false)).to_raise (badresult (2, "string or nil", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, "two", 3, false)).to_raise (badresult (4)) + expect (wrapped (nil, "two", 3, false)).to_raise (badresult (4)) + expect (wrapped (1, nil, 3, false)).to_raise (badresult (4)) + expect (wrapped (nil, nil, 3, false)).to_raise (badresult (4)) + - it accepts correct result types: + expect ({wrapped ()}).to_equal {} + expect ({wrapped (1)}).to_equal {1} + expect ({wrapped (nil, "two")}).to_equal {[2]="two"} + expect ({wrapped (1, "two")}).to_equal {1, "two"} + expect ({wrapped (nil, nil, 3)}).to_equal {[3]=3} + expect ({wrapped (1, nil, 3)}).to_equal {1, [3]=3} + expect ({wrapped (nil, "two", 3)}).to_equal {[2]="two", [3]=3} + expect ({wrapped ("one", "two", 3)}).to_equal {"one", "two", 3} + + - context when checking optional final result: + - context with single result: + - before: + wrapped = f ("inner (?any...) => [int]", id) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "int", "boolean")) + - it diagnoses too many results: + expect (wrapped (1, nop)).to_raise (badresult (2)) + - it accepts correct result types: + expect ({wrapped ()}).to_equal {} + expect ({wrapped (1)}).to_equal {1} + - context with trailing ellipsis: + - before: + wrapped = f ("inner (?any...) => string, [int]...", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badresult (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badresult (3, "int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badresult (12, "int", "boolean")) + - it accepts correct result types: + expect ({wrapped ("foo")}).to_equal {"foo"} + expect ({wrapped ("foo", 1)}).to_equal {"foo", 1} + expect ({wrapped ("foo", 1, 2)}).to_equal {"foo", 1, 2} + expect ({wrapped ("foo", 1, 2, 5)}).to_equal {"foo", 1, 2, 5} + - context with inner ellipsis: + - before: + wrapped = f ("inner (?any...) => string, [int...]", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "string")) + - it diagnoses wrong result types: + expect (wrapped (false)).to_raise (badresult (1, "string", "boolean")) + expect (wrapped ("foo", false)).to_raise (badresult (2, "int", "boolean")) + expect (wrapped ("foo", 1, false)).to_raise (badresult (3, "int", "boolean")) + expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). + to_raise (badresult (12, "int", "boolean")) + - it accepts correct result types: + expect ({wrapped ("foo")}).to_equal {"foo"} + expect ({wrapped ("foo", 1)}).to_equal {"foo", 1} + expect ({wrapped ("foo", 1, 2)}).to_equal {"foo", 1, 2} + expect ({wrapped ("foo", 1, 2, 5)}).to_equal {"foo", 1, 2, 5} + + - context with too many results: + - before: + wrapped = f ("inner (?any...) => [string], int", id) + - it diagnoses missing results: + expect (wrapped ()).to_raise (badresult (1, "string or int")) + expect (wrapped "one").to_raise (badresult (2, "int")) + - it diagnoses wrong result types: + expect (wrapped (false)). + to_raise (badresult (1, "string or int", "boolean")) + expect (wrapped ("one", false)). + to_raise (badresult (2, "int", "boolean")) + - it diagnoses too many results: + expect (wrapped ("one", 2, false)).to_raise (badresult (3)) + expect (wrapped (1, false)).to_raise (badresult (2)) + - it accepts correct argument types: + expect ({wrapped (1)}).to_equal {1} + expect ({wrapped ("one", 2)}).to_equal {"one", 2} + + +- describe extramsg_mismatch: + - before: + f = M.extramsg_mismatch + + - it returns the expected types: + expect (f "nil").to_contain "nil expected, " + expect (f "bool").to_contain "boolean expected, " + expect (f "?bool").to_contain "boolean or nil expected, " + expect (f "string|table").to_contain "string or table expected, " + - it returns expected container types: + expect (f ("table of int", nil, 1)).to_contain "table of ints expected, " + expect (f ("table of int|bool", nil, 1)). + to_contain "table of ints or booleans expected, " + expect (f ("table of int|bool|string", nil, 1)). + to_contain "table of ints, booleans or strings expected, " + expect (f ("table of int|bool|string|table", nil, 1)). + to_contain "table of ints, booleans, strings or tables expected, " + - it returns the actual type: + expect (f ("int")).to_contain ", got no value" + expect (f ("int", false)).to_contain ", got boolean" + expect (f ("int", {})).to_contain ", got empty table" + - it returns table field type: + expect (f ("table of int", nil, 1)).to_contain ", got no value at index 1" + expect (f ("table of int", "two", 2)).to_contain ", got string at index 2" + expect (f ("table of int|bool", "five", 3)).to_contain ", got string at index 3" + + +- describe extramsg_toomany: + - before: + f = M.extramsg_toomany + + - it returns the expected thing: + expect (f ("mojo", 1, 2)).to_contain "no more than 1 mojo" + - it uses singular thing when 1 is expected: + expect (f ("argument", 1, 2)).to_contain "no more than 1 argument" + - it uses plural thing otherwise: + expect (f ("thing", 0, 3)).to_contain "no more than 0 things" + expect (f ("result", 2, 3)).to_contain "no more than 2 results" + - it returns the actual count: + expect (f ("bad", 0, 1)).to_contain ", got 1" + expect (f ("bad", 99, 999)).to_contain ", got 999" From a3440103cd948f5cbebdb688a2756471bf6c62a6 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 11 Oct 2015 02:14:05 +0100 Subject: [PATCH 627/703] debug: defeat tail-call elimination when counting stack frames. * specs/debug_spec.yaml (argerror, resulterror): Turn off deprecation warnings in generated script. * lib/std/maturity.lua (DEPRECATED): Be careful not to make the last line return the result of a function call. * lib/std/delete-after/a-year.lua (argcheck, argerror) (extramsg_mismatch, resulterror): Likewise. And add 2 to the frame count for the extra DEPRECATED code path. Signed-off-by: Gary V. Vaughan --- lib/std/delete-after/a-year.lua | 32 ++++++++++++++++--- lib/std/maturity.lua | 19 ++++++++++- specs/debug_spec.yaml | 56 +++++++++++++++++---------------- 3 files changed, 74 insertions(+), 33 deletions(-) diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 3e5fcb4..8ea1488 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -28,6 +28,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local next = next local pairs = pairs local pcall = pcall + local select = select local type = type local io_stderr = io.stderr @@ -629,7 +630,15 @@ if not require "std.debug_init"._DEBUG.deprecate then end local function XX (base, fn) - return DEPRECATED (RELEASE, "'std.debug." .. base .. "'", "use 'std.argcheck." .. base .. "' instead", fn) + return DEPRECATED (RELEASE, "'std.debug." .. base .. "'", "use 'std.argcheck." .. base .. "' instead", fn) or nil + end + + local function result_pack (...) + return {n = select ("#", ...), ...} + end + + local function result_unpack (v) + return table_unpack (v, 1, v.n) end local function acyclic_merge (dest, src) @@ -646,15 +655,28 @@ if not require "std.debug_init"._DEBUG.deprecate then M = acyclic_merge ({ debug = { - argcheck = XX ("argcheck", argcheck), - argerror = XX ("argerror", argerror), + argcheck = XX ("argcheck", function (name, i, expected, actual, level) + -- Add 2 to the level, this anonymous function and XX, being + -- careful not to let tail call elimination remove a stack + -- frame: + local r = result_pack (argcheck (name, i, expected, actual, (level or 1) + 2)) + return result_unpack (r) + end), + argerror = XX ("argerror", function (name, i, extramsg, level) + local r = result_pack (argerror (name, i, extramsg, (level or 1) + 2)) + return result_unpack (r) + end), argscheck = XX ("argscheck", argscheck), extramsg_mismatch = XX ("extramsg_mismatch", function (expected, actual, index) - return extramsg_mismatch (typesplit (expected), actual, index) + local r = result_pack (extramsg_mismatch (typesplit (expected), actual, index)) + return result_unpack (r) end), extramsg_toomany = XX ("extramsg_toomany", extramsg_toomany), parsetypes = XX ("parsetypes", parsetypes), - resulterror = XX ("resulterror", resulterror), + resulterror = XX ("resulterror", function (name, i, extramsg, level) + local r = result_pack (resulterror (name, i, extramsg, (level or 1) + 2)) + return result_unpack (r) + end), typesplit = XX ("typesplit", typesplit), }, diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua index 6808dd8..fc1551d 100644 --- a/lib/std/maturity.lua +++ b/lib/std/maturity.lua @@ -27,10 +27,12 @@ local error = error local pcall = pcall +local select = select local tostring = tostring local io_stderr = io.stderr local string_format = string.format +local table_unpack = table.unpack or unpack local _ = { @@ -64,13 +66,28 @@ local function DEPRECATIONMSG (version, name, extramsg, level) end +local function result_pack (...) + return {n = select ("#", ...), ...} +end + + +local function result_unpack (v) + return table_unpack (v, 1, v.n) +end + + local function DEPRECATED (version, name, extramsg, fn) if fn == nil then fn, extramsg = extramsg, nil end if not _DEBUG.deprecate then return function (...) io_stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) - return fn (...) + + -- `return fn (...)` is subject to tail call elimination, which + -- would lose a stack frame and change the `level` argument + -- required for frame counting functions, so we do this instead: + local r = result_pack (fn (...)) + return result_unpack (r) end end end diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 7aa5d49..43b364c 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -46,16 +46,17 @@ specify std.debug: - before: | function mkstack (level) return string.format ([[ - _DEBUG = nil -- line 1 - local debug = require "std.debug" -- line 2 - function ohnoes () -- line 3 - debug.resulterror ("ohnoes", 1, nil, %s) -- line 4 - end -- line 5 - function caller () -- line 6 - local r = ohnoes () -- line 7 - return "not a tail call" -- line 8 - end -- line 9 - caller () -- line 10 + _DEBUG = nil -- line 1 + local debug = require "std.debug" -- line 2 + require "std.debug_init"._DEBUG.deprecate = false -- line 3 + function ohnoes () -- line 4 + debug.resulterror ("ohnoes", 1, nil, %s) -- line 5 + end -- line 6 + function caller () -- line 7 + local r = ohnoes () -- line 8 + return "not a tail call" -- line 9 + end -- line 10 + caller () -- line 11 ]], tostring (level)) end @@ -68,10 +69,10 @@ specify std.debug: not_to_contain_error "was deprecated" - it blames the call site by default: | - expect (luaproc (mkstack ())).to_contain_error ":4: bad result" + expect (luaproc (mkstack ())).to_contain_error ":5: bad result" - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":4: bad result" - expect (luaproc (mkstack (2))).to_contain_error ":7: bad result" + expect (luaproc (mkstack (1))).to_contain_error ":5: bad result" + expect (luaproc (mkstack (2))).to_contain_error ":8: bad result" - it reports the calling function name: expect (f ('expect', 1)).to_raise "'expect'" - it reports the argument number: | @@ -84,16 +85,17 @@ specify std.debug: - before: | function mkstack (level) return string.format ([[ - _DEBUG = nil -- line 1 - local debug = require "std.debug" -- line 2 - function ohnoes () -- line 3 - debug.argerror ("ohnoes", 1, nil, %s) -- line 4 - end -- line 5 - function caller () -- line 6 - local r = ohnoes () -- line 7 - return "not a tail call" -- line 8 - end -- line 9 - caller () -- line 10 + _DEBUG = nil -- line 1 + local debug = require "std.debug" -- line 2 + require "std.debug_init"._DEBUG.deprecate = false -- line 3 + function ohnoes () -- line 4 + debug.argerror ("ohnoes", 1, nil, %s) -- line 5 + end -- line 6 + function caller () -- line 7 + local r = ohnoes () -- line 8 + return "not a tail call" -- line 9 + end -- line 10 + caller () -- line 11 ]], tostring (level)) end @@ -122,10 +124,10 @@ specify std.debug: expect (f ("foo", 1, "bar", 2, false)).to_raise (badarg (5)) - it blames the call site by default: | - expect (luaproc (mkstack ())).to_contain_error ":4: bad argument" + expect (luaproc (mkstack ())).to_contain_error ":5: bad argument" - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" - expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" + expect (luaproc (mkstack (1))).to_contain_error ":5: bad argument" + expect (luaproc (mkstack (2))).to_contain_error ":8: bad argument" - it reports the calling function name: expect (f ('expect', 1)).to_raise "'expect'" - it reports the argument number: | @@ -183,7 +185,7 @@ specify std.debug: expect (f ("foo", 1, "bar", 2, 3, false)).to_raise (badarg (6)) - it blames the calling function by default: | - expect (luaproc (mkstack ())).to_contain_error ":8: bad argument" + expect (luaproc (mkstack ())).to_contain_error ":5: bad argument" - it honors optional call stack level reporting: | expect (luaproc (mkstack (1))).to_contain_error ":5: bad argument" expect (luaproc (mkstack (2))).to_contain_error ":8: bad argument" From 4a1d93fe2a62b517f771f427715f83a171206b32 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 11 Oct 2015 02:26:14 +0100 Subject: [PATCH 628/703] specs: remove pending examples for type checking type checkers. * specs/debug_spec.yaml (argerror, argcheck): Type checking functions do not type check their own arguments, so remove pending examples mandating that. * specs/typing_spec.yaml (argerror, argcheck): Likewise. Signed-off-by: Gary V. Vaughan --- specs/debug_spec.yaml | 32 -------------------------------- specs/typing_spec.yaml | 32 -------------------------------- 2 files changed, 64 deletions(-) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 43b364c..022bdc4 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -107,22 +107,6 @@ specify std.debug: expect (deprecate_off ("argerror", "{}")). not_to_contain_error "was deprecated" - - it diagnoses missing arguments: - pending "Lua 5.1 support is dropped" - expect (f ()).to_raise (badarg (1, "string")) - expect (f "foo").to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - pending "Lua 5.1 support is dropped" - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (f ("foo", 1, false)). - to_raise (badarg (3, "string or nil", "boolean")) - expect (f ("foo", 1, "bar", false)). - to_raise (badarg (4, "int or nil", "boolean")) - - it diagnoses too many arguments: - pending "Lua 5.1 support is dropped" - expect (f ("foo", 1, "bar", 2, false)).to_raise (badarg (5)) - - it blames the call site by default: | expect (luaproc (mkstack ())).to_contain_error ":5: bad argument" - it honors optional call stack level reporting: | @@ -168,22 +152,6 @@ specify std.debug: expect (deprecate_off ("argcheck", "{}")). not_to_contain_error "was deprecated" - - it diagnoses missing arguments: - pending "Lua 5.1 support is dropped" - expect (f ()).to_raise (badarg (1, "string")) - expect (f "foo").to_raise (badarg (2, "int")) - expect (f ("foo", 1)).to_raise (badarg (3, "string")) - - it diagnoses wrong argument types: - pending "Lua 5.1 support is dropped" - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (f ("foo", 1, false)).to_raise (badarg (3, "string", "boolean")) - expect (f ("foo", 1, "bar", 2, false)). - to_raise (badarg (5, "int or nil", "boolean")) - - it diagnoses too many arguments: - pending "Lua 5.1 support is dropped" - expect (f ("foo", 1, "bar", 2, 3, false)).to_raise (badarg (6)) - - it blames the calling function by default: | expect (luaproc (mkstack ())).to_contain_error ":5: bad argument" - it honors optional call stack level reporting: | diff --git a/specs/typing_spec.yaml b/specs/typing_spec.yaml index 1d6a156..4dd2f0f 100644 --- a/specs/typing_spec.yaml +++ b/specs/typing_spec.yaml @@ -61,22 +61,6 @@ specify std.typing: f, badarg = init (M, this_module, "argerror") - - it diagnoses missing arguments: - pending "Lua 5.1 support is dropped" - expect (f ()).to_raise (badarg (1, "string")) - expect (f "foo").to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - pending "Lua 5.1 support is dropped" - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (f ("foo", 1, false)). - to_raise (badarg (3, "string or nil", "boolean")) - expect (f ("foo", 1, "bar", false)). - to_raise (badarg (4, "int or nil", "boolean")) - - it diagnoses too many arguments: - pending "Lua 5.1 support is dropped" - expect (f ("foo", 1, "bar", 2, false)).to_raise (badarg (5)) - - it blames the call site by default: | expect (luaproc (mkstack ())).to_contain_error ":4: bad argument" - it honors optional call stack level reporting: | @@ -115,22 +99,6 @@ specify std.typing: f, badarg = init (M, this_module, "argcheck") - - it diagnoses missing arguments: - pending "Lua 5.1 support is dropped" - expect (f ()).to_raise (badarg (1, "string")) - expect (f "foo").to_raise (badarg (2, "int")) - expect (f ("foo", 1)).to_raise (badarg (3, "string")) - - it diagnoses wrong argument types: - pending "Lua 5.1 support is dropped" - expect (f (false)).to_raise (badarg (1, "string", "boolean")) - expect (f ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (f ("foo", 1, false)).to_raise (badarg (3, "string", "boolean")) - expect (f ("foo", 1, "bar", 2, false)). - to_raise (badarg (5, "int or nil", "boolean")) - - it diagnoses too many arguments: - pending "Lua 5.1 support is dropped" - expect (f ("foo", 1, "bar", 2, 3, false)).to_raise (badarg (6)) - - it blames the calling function by default: | expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" - it honors optional call stack level reporting: | From a5cdd836cdf718518f6921fdc8de312d251f4e22 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 11 Oct 2015 02:55:05 +0100 Subject: [PATCH 629/703] maint: update NEWS with std.typing changes. * NEWS.md (New features, Deprecations): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS.md b/NEWS.md index 9f272a1..3f87965 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,6 +28,9 @@ _DEBUG.deprecate = true ``` + - New `std.typing` module contains the typechecking functions + previously in `std.debug`. + - Objects and Modules are no longer conflated - what you get back from a `require "std.something"` is now ALWAYS a module: @@ -177,6 +180,12 @@ - `std.string.render` function arguments have been deprecated in favour of a table of named functions backed by defaults. + - `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 deprecated in favour of their namesakes in the newly + factored out `std.typing` module. + ### Bug fixes From 9e8fbc1d751bd03fcf64a4968f1c5a8015b73820 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 11 Oct 2015 22:21:47 +0100 Subject: [PATCH 630/703] functional: bind propagates trailing nils correctly. It's surprisingly fiddly to ensure that trailing nils from `bind` generated functions, and gaps remaining after binding additional args are all propagated correctly. * specs/functional_spec.yaml (propagates nil arguments correctly): Improve these examples to show behaviours for trailing nils. * specs/spec_helper.lua (pack): Add a pack implementation for Lua 5.1, used by improved examples above. * lib/std/functional.lua (bind): Count and unpack all arguments before calling inner function. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 +++ lib/std/functional.lua | 25 +++++++++++++++++++++++-- specs/functional_spec.yaml | 12 ++++++++++-- specs/spec_helper.lua | 5 +++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3f87965..70492a6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -197,6 +197,9 @@ correctly, rather than `nil` as in previous releases. It's also considerably faster now that it doesn't use `pcall` any more. + - `std.functional.bind` returns functions that propagate trailing `nil` + arguments correctly. + - `std.functional.memoize` now considers trailing nil arguments when looking up memoized value for those particular arguments. diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 230c021..62da3f5 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -72,12 +72,33 @@ end local function bind (fn, bound) return function (...) - local argt, unbound, i = copy (bound), pack (...), 1 + local argt, unbound = {}, pack (...) + + -- Inline `argt = copy (bound)`... + local n = 0 + for k, v in _pairs (bound) do + -- ...but only copy integer keys. + if type (k) == "number" and math_ceil (k) == k then + argt[k] = v + n = k > n and k or n -- Inline `n = maxn (unbound)` in same pass. + end + end + + -- Respect packed *bound* table. + n = bound.n or n + + -- Bind *unbound* parameters sequentially into *argt* gaps. + local i = 1 for j = 1, unbound.n do while argt[i] ~= nil do i = i + 1 end argt[i], i = unbound[j], i + 1 end - return fn (unpack (argt)) + + -- Even if there are gaps remaining above *i*, pass at least *n* args. + if n >= i then return fn (unpack (argt, 1, n)) end + + -- Otherwise, we filled gaps beyond *n*, and pass that many args. + return fn (unpack (argt, 1, i - 1)) end end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index d777cb2..6cd5a8b 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -79,8 +79,16 @@ specify std.functional: - it supports out of order extra arguments: expect (f (op.pow, {[2] = 3}) (2)).to_be (8) - it propagates nil arguments correctly: - expect ({f (M.id, {[2]="b", [4]="d"}) (nil, 3, 5, 6, nil)}). - to_equal {nil, "b", 3, "d", 5, 6, nil} + r = pack (f (M.id, {[4]="d"}) (1)) + expect (r).to_equal (pack (1, nil, nil, "d")) + r = pack (f (M.id, {[4]="d"}) (nil, 2)) + expect (r).to_equal (pack (nil, 2, nil, "d")) + r = pack (f (M.id, {[2]="b", [4]="d"}) (nil, 3, 5, 6, nil)) + expect (r).to_equal (pack (nil, "b", 3, "d", 5, 6, nil)) + r = pack (f (M.id, {[2]="b", [4]="d"}) (nil, 3, nil, nil, 7)) + expect (r).to_equal (pack (nil, "b", 3, "d", nil, nil, 7)) + r = pack (f (M.id, {[2]="b", [4]="d"}) (nil, 3, nil, nil, 7, nil, nil)) + expect (r).to_equal (pack (nil, "b", 3, "d", nil, nil, 7, nil, nil)) - describe callable: diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 376df44..e50fda4 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -36,6 +36,11 @@ maxn = table.maxn or function (t) 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 From 499cf61feb9aa6c44f42d546990266d38871270e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 12 Oct 2015 23:12:29 +0100 Subject: [PATCH 631/703] refactor: shorten functional.bind by a few lines. * lib/std/functional.lua (bind): Shorten implementation by a few lines. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 62da3f5..39f2f19 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -75,7 +75,7 @@ local function bind (fn, bound) local argt, unbound = {}, pack (...) -- Inline `argt = copy (bound)`... - local n = 0 + local n = bound.n or 0 for k, v in _pairs (bound) do -- ...but only copy integer keys. if type (k) == "number" and math_ceil (k) == k then @@ -84,9 +84,6 @@ local function bind (fn, bound) end end - -- Respect packed *bound* table. - n = bound.n or n - -- Bind *unbound* parameters sequentially into *argt* gaps. local i = 1 for j = 1, unbound.n do From e4d71d91f2bd01ed14aa694c7194c16760955fbb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 22 Nov 2015 19:56:36 +0000 Subject: [PATCH 632/703] doc: fix reference to std.ielems * lib/std/functional.lua (ireverse): Fix reference to std.ielems. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 39f2f19..5284913 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -624,7 +624,7 @@ local M = { -- @tparam table t a table -- @treturn table a new table with integer keyed elements in reverse -- order with respect to *t* - -- @see ielems + -- @see std.ielems -- @see ipairs -- @usage -- local rielems = std.functional.compose (std.ireverse, std.ielems) From 60ac7ec2ab6ba0a762600fd27ac0c68d702fc488 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 22 Nov 2015 19:58:11 +0000 Subject: [PATCH 633/703] maint: remove unused _require local. * lib/std/init.lua (_require): Remove unused local. Signed-off-by: Gary V. Vaughan --- lib/std/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/init.lua b/lib/std/init.lua index 705520d..5bdd64e 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -43,7 +43,6 @@ local _ = { local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local _require = _.std.require local _tostring = _.std.tostring local argscheck = _.typing.argscheck local compare = _.std.list.compare From 7de3ae9114b53fcd4036d12b054480ba433dc165 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 22 Nov 2015 19:59:03 +0000 Subject: [PATCH 634/703] maint: remove unused leaves local. * lib/std/table.lua (leaves): Remove unused local. Signed-off-by: Gary V. Vaughan --- lib/std/table.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index c40880e..667df07 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -34,7 +34,6 @@ local argerror = _.typing.argerror local collect = _.std.functional.collect local copy = _.std.base.copy local invert = _.std.table.invert -local leaves = _.std.tree.leaves local len = _.std.operator.len local maxn = _.std.table.maxn local merge = _.std.base.merge From ddd1fb4a3d6aa63951ed3216fb666effebf82eec Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 22 Nov 2015 19:59:41 +0000 Subject: [PATCH 635/703] doc: remove spurious quote mark. * lib/std/tree.lua (node): Remove spurious quote mark in ldoc example. Signed-off-by: Gary V. Vaughan --- lib/std/tree.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 80106c5..6afd7f6 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -313,7 +313,7 @@ return Module { -- print (node_type, path, node) -- end -- --> "branch" {} {{"leaf1", "leaf2"}, "leaf3"} - -- --> "branch" {1} {"leaf1", "leaf"2") + -- --> "branch" {1} {"leaf1", "leaf2") -- --> "leaf" {1,1} "leaf1" -- --> "leaf" {1,2} "leaf2" -- --> "join" {1} {"leaf1", "leaf2"} From d2209cd44e6a80c5e507a1db60261efbf9390b0f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 29 Nov 2015 15:46:59 +0000 Subject: [PATCH 636/703] base: remove unused base.keysort export. * lib/std/base.lua (M.base.keysort): Remove. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index f186828..204ccc0 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -638,7 +638,6 @@ return { base = { copy = copy, - keysort = keysort, last = last, merge = merge, mnemonic = mnemonic, From 7b5e49f850d9443e7fb20f0a53cb13de477ab7eb Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 29 Nov 2015 16:43:24 +0000 Subject: [PATCH 637/703] std: use simpler semantics for faster npairs and rnpairs. * lib/std/init.lua (npairs, rnpairs): Use the `n` field set by `table.pack`, or the result of the length operation to determine the last ordinal element, instead of calling `table.maxn`. * lib/std/typing.lua: Internally, pack argument and result tables using `table.pack`, and then use the `n` field instead of calling `table.maxn`. * lib/std/base.lua (maxn): Move from here... * lib/std/table.lua (maxn): ...to here. * lib/std/delete-after/a-year.lua (maxn): ...and here. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ++++ lib/std/base.lua | 23 +++-------------------- lib/std/delete-after/a-year.lua | 11 ++++++++++- lib/std/table.lua | 11 ++++++++++- lib/std/typing.lua | 21 ++++++++++++--------- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/NEWS.md b/NEWS.md index 70492a6..71d0a34 100644 --- a/NEWS.md +++ b/NEWS.md @@ -245,6 +245,10 @@ into the module `prototype` field, and add the module functions to the parent table returned when the module is required. + - `std.npairs` and `std.rnpairs` no longer honor the `maxn` table size, + instead using either the `n` field (as set by `table.pack`) or else + the result of the length operator. + - `functional.lambda` no longer returns a bare function, but a functable that can be called and stringified. diff --git a/lib/std/base.lua b/lib/std/base.lua index 204ccc0..acf4212 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -50,7 +50,6 @@ local string_find = string.find local string_format = string.format local table_concat = table.concat local table_insert = table.insert -local table_maxn = table.maxn local table_pack = table.pack local table_sort = table.sort local table_unpack = table.unpack or unpack @@ -93,15 +92,6 @@ local function pairs (t) end -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. ]]-- @@ -316,8 +306,7 @@ end local function npairs (t) - local m = getmetamethod (t, "__len") - local i, n = 0, m and m(t) or maxn (t) + local i, n = 0, t.n or len (t) return function (t) i = i + 1 if i <= n then return i, t[i] end @@ -332,11 +321,7 @@ end local function unpack (t, i, j) - if j == nil then - -- respect __len, and then maxn if nil j was passed - local m = getmetamethod (t, "__len") - j = m and m (t) or maxn (t) - end + j = j or t.n or len (t) local fn = getmetamethod (t, "__unpack") or table_unpack return fn (t, tonumber (i) or 1, tonumber (j)) end @@ -518,8 +503,7 @@ end local function rnpairs (t) - local m = getmetamethod (t, "__len") - local oob = (m and m (t) or maxn (t)) + 1 + local oob = (t.n or len (t)) + 1 return function (t, n) n = n - 1 @@ -686,7 +670,6 @@ return { table = { invert = invert, - maxn = maxn, pack = pack, unpack = unpack, }, diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 8ea1488..0ace2dc 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -42,6 +42,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local string_match = string.match local table_concat = table.concat local table_insert = table.insert + local table_maxn = table.maxn local table_remove = table.remove local table_sort = table.sort local table_unpack = table.unpack or unpack @@ -70,7 +71,6 @@ if not require "std.debug_init"._DEBUG.deprecate then local copy = _.std.base.copy local leaves = _.std.tree.leaves local len = _.std.operator.len - local maxn = _.std.table.maxn local nop = _.std.functional.nop local sortkeys = _.std.base.sortkeys local split = _.std.string.split @@ -80,6 +80,15 @@ if not require "std.debug_init"._DEBUG.deprecate then local _, _ENV = nil, _.strict {} + 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 + + --[[ ========== ]]-- --[[ Death Row! ]]-- --[[ ========== ]]-- diff --git a/lib/std/table.lua b/lib/std/table.lua index 667df07..c2b4876 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -19,6 +19,7 @@ local type = type local math_min = math.min local table_insert = table.insert +local table_maxn = table.maxn local _ = { @@ -35,7 +36,6 @@ local collect = _.std.functional.collect local copy = _.std.base.copy local invert = _.std.table.invert local len = _.std.operator.len -local maxn = _.std.table.maxn local merge = _.std.base.merge local pack = _.std.table.pack local unpack = _.std.table.unpack @@ -123,6 +123,15 @@ local function keys (t) end +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 + + local function new (x, t) return setmetatable (t or {}, {__index = function (t, i) diff --git a/lib/std/typing.lua b/lib/std/typing.lua index b12b8ef..a43bdc4 100644 --- a/lib/std/typing.lua +++ b/lib/std/typing.lua @@ -38,6 +38,7 @@ local string_gsub = string.gsub local string_match = string.match local table_concat = table.concat local table_insert = table.insert +local table_pack = table.pack local table_remove = table.remove local table_sort = table.sort @@ -56,7 +57,6 @@ local _setfenv = _.std.debug.setfenv local _tostring = _.std.tostring local copy = _.std.base.copy local len = _.std.operator.len -local maxn = _.std.table.maxn local nop = _.std.functional.nop local split = _.std.string.split local unpack = _.std.table.unpack @@ -279,7 +279,7 @@ if _DEBUG.argcheck then local ok = pcall (argcheck, "pcall", i, typelist[i], valuelist[i]) if not ok then return i end end - for i = n + 1, maxn (valuelist) do -- additional values against final type + for i = n + 1, valuelist.n do -- additional values against final type local ok = pcall (argcheck, "pcall", i, typelist[n], valuelist[i]) if not ok then return i end end @@ -396,12 +396,12 @@ if _DEBUG.argcheck then end -- Otherwise the argument type itself was mismatched. - if t.dots or #t >= maxn (valuelist) then + if t.dots or #t >= valuelist.n then argt.badtype (i, extramsg_mismatch (expected, valuelist[i]), 3) end end - local n, t = maxn (valuelist), t or permutations[1] + local n, t = valuelist.n, t or permutations[1] if t and t.dots == nil and n > #t then argt.badtype (#t + 1, extramsg_toomany (argt.bad, #t, n), 3) end @@ -488,10 +488,13 @@ if _DEBUG.argcheck then end return function (...) - local argt = {...} + local argt = table_pack (...) -- Don't check type of self if fname has a ':' in it. - if string_find (fname, ":") then table_remove (argt, 1) end + if string_find (fname, ":") then + table_remove (argt, 1) + argt.n = argt.n - 1 + end -- Diagnose bad inputs. diagnose (argt, input) @@ -501,7 +504,7 @@ if _DEBUG.argcheck then _setfenv (inner, _getfenv (1)) -- Execute. - local results = {inner (...)} + local results = table_pack (inner (...)) -- Diagnose bad outputs. if returntypes then @@ -653,8 +656,8 @@ return { -- @see resulterror -- @see extramsg_mismatch -- @usage - -- if maxn (argt) > 7 then - -- argerror ("sevenses", 8, extramsg_toomany ("argument", 7, maxn (argt))) + -- if select ("#", ...) > 7 then + -- argerror ("sevenses", 8, extramsg_toomany ("argument", 7, select ("#", ...))) -- end extramsg_toomany = extramsg_toomany, From 8f7dac173cf356396cce387642f30b942ed8d352 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 29 Nov 2015 17:07:52 +0000 Subject: [PATCH 638/703] table: use imported _pairs symbol consistently. * lib/std/table.lua (maxn): In this module we import `_pairs` not `pairs`, so be sure to use the right one! * lib/std/delete-after/a-year.lua (maxn): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/delete-after/a-year.lua | 2 +- lib/std/table.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 0ace2dc..da473e7 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -82,7 +82,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local maxn = table_maxn or function (t) local n = 0 - for k in pairs (t) do + for k in _pairs (t) do if type (k) == "number" and k > n then n = k end end return n diff --git a/lib/std/table.lua b/lib/std/table.lua index c2b4876..ca6374b 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -125,7 +125,7 @@ end local maxn = table_maxn or function (t) local n = 0 - for k in pairs (t) do + for k in _pairs (t) do if type (k) == "number" and k > n then n = k end end return n From 6a0f81d254434f7f2ea130e748e35fbecc21d250 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 29 Nov 2015 17:17:02 +0000 Subject: [PATCH 639/703] typing: use std.table.pack consistently. * lib/std/typing.lua (argscheck): use std.table.pack to cater for Lua 5.1 which has no pack function. Signed-off-by: Gary V. Vaughan --- lib/std/typing.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/typing.lua b/lib/std/typing.lua index a43bdc4..475c7da 100644 --- a/lib/std/typing.lua +++ b/lib/std/typing.lua @@ -38,7 +38,6 @@ local string_gsub = string.gsub local string_match = string.match local table_concat = table.concat local table_insert = table.insert -local table_pack = table.pack local table_remove = table.remove local table_sort = table.sort @@ -58,6 +57,7 @@ local _tostring = _.std.tostring local copy = _.std.base.copy local len = _.std.operator.len local nop = _.std.functional.nop +local pack = _.std.table.pack local split = _.std.string.split local unpack = _.std.table.unpack @@ -488,7 +488,7 @@ if _DEBUG.argcheck then end return function (...) - local argt = table_pack (...) + local argt = pack (...) -- Don't check type of self if fname has a ':' in it. if string_find (fname, ":") then @@ -504,7 +504,7 @@ if _DEBUG.argcheck then _setfenv (inner, _getfenv (1)) -- Execute. - local results = table_pack (inner (...)) + local results = pack (inner (...)) -- Diagnose bad outputs. if returntypes then From 3ebb65d53dfdae5f21857cb6a24d5fdf6c13e941 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 29 Nov 2015 18:07:09 +0000 Subject: [PATCH 640/703] deprecated: don't break the semantics of debug.argscheck on LuaJIT. * lib/std/delete-after/a-year.lua (argscheck): Use table.packed argument tables to avoid changing behaviour on LuaJIT. Signed-off-by: Gary V. Vaughan --- lib/std/delete-after/a-year.lua | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index da473e7..9b0acfc 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -42,7 +42,6 @@ if not require "std.debug_init"._DEBUG.deprecate then local string_match = string.match local table_concat = table.concat local table_insert = table.insert - local table_maxn = table.maxn local table_remove = table.remove local table_sort = table.sort local table_unpack = table.unpack or unpack @@ -72,6 +71,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local leaves = _.std.tree.leaves local len = _.std.operator.len local nop = _.std.functional.nop + local pack = _.std.table.pack local sortkeys = _.std.base.sortkeys local split = _.std.string.split local unpack = _.std.table.unpack @@ -80,14 +80,6 @@ if not require "std.debug_init"._DEBUG.deprecate then local _, _ENV = nil, _.strict {} - 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 - --[[ ========== ]]-- --[[ Death Row! ]]-- @@ -302,7 +294,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local ok = pcall (argcheck, "pcall", i, typelist[i], valuelist[i]) if not ok then return i end end - for i = n + 1, maxn (valuelist) do -- additional values against final type + for i = n + 1, valuelist.n do -- additional values against final type local ok = pcall (argcheck, "pcall", i, typelist[n], valuelist[i]) if not ok then return i end end @@ -419,12 +411,12 @@ if not require "std.debug_init"._DEBUG.deprecate then end -- Otherwise the argument type itself was mismatched. - if t.dots or #t >= maxn (valuelist) then + if t.dots or #t >= valuelist.n then argt.badtype (i, extramsg_mismatch (expected, valuelist[i]), 3) end end - local n, t = maxn (valuelist), t or permutations[1] + local n, t = valuelist.n, t or permutations[1] if t and t.dots == nil and n > #t then argt.badtype (#t + 1, extramsg_toomany (argt.bad, #t, n), 3) end @@ -511,10 +503,13 @@ if not require "std.debug_init"._DEBUG.deprecate then end return function (...) - local argt = {...} + local argt = pack (...) -- Don't check type of self if fname has a ':' in it. - if string_find (fname, ":") then table_remove (argt, 1) end + if string_find (fname, ":") then + table_remove (argt, 1) + argt.n = argt.n - 1 + end -- Diagnose bad inputs. diagnose (argt, input) @@ -524,7 +519,7 @@ if not require "std.debug_init"._DEBUG.deprecate then _setfenv (inner, _getfenv (1)) -- Execute. - local results = {inner (...)} + local results = pack (inner (...)) -- Diagnose bad outputs. if returntypes then From abe5d3ba21ca9897b20e8c94c0f74204d1b46326 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 4 Dec 2015 23:37:33 +0000 Subject: [PATCH 641/703] functional: improve nil argument propagation. * specs/spec_helper.lua (len): New helper function to match std.base.len. (unpack): Adjust to match std.table.unpack. * specs/functional_spec.lua (any, compose, filter, map, reduce): Add examples of correct nil argument propagation. * lib/std/base.lua (reduce): Pack trailing iterator arguments on entry, and unpack them all again when calling iterator. * lib/std/functional.lua (any, compose): Likewise. (filter, map): Similarly, pass all arguments to iterator. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 9 ++++++-- lib/std/base.lua | 8 ++++--- lib/std/functional.lua | 36 ++++++++++++++--------------- specs/functional_spec.yaml | 47 ++++++++++++++++++++++++++++++-------- specs/spec_helper.lua | 10 +++++++- 5 files changed, 77 insertions(+), 33 deletions(-) diff --git a/NEWS.md b/NEWS.md index 71d0a34..79dd0e5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -197,12 +197,17 @@ correctly, rather than `nil` as in previous releases. It's also considerably faster now that it doesn't use `pcall` any more. - - `std.functional.bind` returns functions that propagate trailing `nil` - arguments correctly. + - `std.functional.any`, `std.functional.bind` and + `std.functional.compose` return functions that propagate trailing + `nil` arguments correctly. - `std.functional.memoize` now considers trailing nil arguments when looking up memoized value for those particular arguments. + - `std.functional.filter`, `std.functional.map` and + `std.functional.reduce` now pass trailing nil arguments to their + iterator function correctly. + - You can now derive other types from `std.set` by passing a `_type` field in the init argument, just like the other table argument objects. diff --git a/lib/std/base.lua b/lib/std/base.lua index acf4212..611087c 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -328,12 +328,14 @@ end local function reduce (fn, d, ifn, ...) - local argt = {...} + local argt if not callable (ifn) then - ifn, argt = pairs, {ifn, ...} + ifn, argt = pairs, pack (ifn, ...) + else + argt = pack (...) end - local nextfn, state, k = ifn (unpack (argt)) + local nextfn, state, k = ifn (unpack (argt, 1, argt.n)) local t = {nextfn (state, k)} -- table of iteration 1 local r = d -- initialise accumulator diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 5284913..595166e 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -58,14 +58,14 @@ local function any (...) local fns = pack (...) return function (...) - local argt = {} + local argt = {n = 0} for i = 1, fns.n do - argt = {fns[i] (...)} + argt = pack (fns[i] (...)) if argt[1] ~= nil then - return unpack (argt) + return unpack (argt, 1, argt.n) end end - return unpack (argt) + return unpack (argt, 1, argt.n) end end @@ -145,11 +145,11 @@ local function compose (...) local fns = pack (...) return function (...) - local argt = {...} + local argt = pack (...) for i = 1, fns.n do - argt = {fns[i] (unpack (argt))} + argt = pack (fns[i] (unpack (argt, 1, argt.n))) end - return unpack (argt) + return unpack (argt, 1, argt.n) end end @@ -180,12 +180,12 @@ end local function filter (pfn, ifn, ...) - local argt, r = {...}, {} + local argt, r = pack (...), {} if not callable (ifn) then - ifn, argt = _pairs, {ifn, ...} + ifn, argt = _pairs, pack (ifn, ...) end - local nextfn, state, k = ifn (unpack (argt)) + local nextfn, state, k = ifn (unpack (argt, 1, argt.n)) local t = {nextfn (state, k)} -- table of iteration 1 local arity = #t -- How many return values from ifn? @@ -326,12 +326,12 @@ end, id) local function map (mapfn, ifn, ...) - local argt, r = {...}, {} - if not callable (ifn) or not next (argt) then - ifn, argt = _pairs, {ifn, ...} + local argt, r = pack (...), {} + if not callable (ifn) or argt.n == 0 then + ifn, argt = _pairs, pack (ifn, ...) end - local nextfn, state, k = ifn (unpack (argt)) + local nextfn, state, k = ifn (unpack (argt, 1, argt.n)) local mapargs = {nextfn (state, k)} local arity = 1 @@ -511,7 +511,7 @@ local M = { -- @usage -- --> {"a", "b", "c"} -- collect {"a", "b", "c", x=1, y=2, z=5} - collect = X ("collect ([func], any...)", collect), + collect = X ("collect ([func], ?any...)", collect), --- Compose functions. -- @function compose @@ -571,7 +571,7 @@ local M = { -- @usage -- --> {2, 4} -- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) - filter = X ("filter (func, [func], any...)", filter), + filter = X ("filter (func, [func], ?any...)", filter), --- Flatten a nested table into a list. -- @function flatten @@ -669,7 +669,7 @@ local M = { -- @usage -- --> {1, 4, 9, 16} -- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) - map = X ("map (func, [func], any...)", map), + map = X ("map (func, [func], ?any...)", map), --- Map a function over a table of argument lists. -- @function map_with @@ -730,7 +730,7 @@ local M = { -- @usage -- --> 2 ^ 3 ^ 4 ==> 4096 -- reduce (std.operator.pow, 2, std.ielems, {3, 4}) - reduce = X ("reduce (func, any, [func], any...)", reduce), + reduce = X ("reduce (func, any, [func], ?any...)", reduce), --- Shape a table according to a list of dimensions. -- diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 6cd5a8b..43ce678 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -15,6 +15,15 @@ before: deprecate_on = bind (deprecation, {"nil", this_module}) deprecate_off = bind (deprecation, {false, this_module}) + function iter (...) + local l = pack (...) + local oob = l.n + 1 + return function (l, n) + n = n - 1 + if n > 0 then return n, l[n] or false end + end, l, oob + end + M = require (this_module) @@ -50,6 +59,10 @@ specify std.functional: - it propagates arguments and returned results: expect (f (M.id) (true)).to_be (true) expect ({f (M.id) (1, 2, 3)}).to_equal {1, 2, 3} + - it propagates nil arguments correctly: + truthy = function (x) return x and true or false end + expect (f (truthy) (true, false, nil, nil, 0, nil)). + to_equal (true, false, false, false, true, false) - it calls all functions until one returns non-nil: expect (f (M.id, M.id, stop, fail) (nil)).to_be (true) - it only looks at first returned value: @@ -117,6 +130,7 @@ specify std.functional: expect (f {}).to_be (nil) expect (f "").to_be (nil) + - describe case: - before: yes = function () return true end @@ -162,17 +176,21 @@ specify std.functional: f = M.collect - context with bad arguments: - badargs.diagnose (f, "std.functional.collect ([func], any*)") + badargs.diagnose (f, "std.functional.collect ([func], ?any*)") - it collects a list of single return value iterator results: expect (f (base.ielems, {"a", "b", "c"})).to_equal {"a", "b", "c"} - it collects a table of key:value iterator results: t = {"first", second="two", last=3} expect (f (pairs, t)).to_equal (t) - - it propagates nil arguments correctly: - expect (f {"a", nil, nil, "d", "e"}).to_equal {"a", [4]="d", [5]="e"} - it defaults to npairs iteration: expect (f {1, 2, [5]=5, a="b", c="d"}).to_equal {1, 2, [5]=5} + - it propagates nil arguments correctly: + expect (f {"a", nil, nil, "d", "e"}).to_equal {"a", [4]="d", [5]="e"} + expect (f (iter, "a", nil, nil, "d", "e")). + to_equal {"a", false, false, "d", "e"} + expect (f (iter, nil, nil, 3, 4, nil, nil)). + to_equal {false, false, 3, 4, false, false} - describe compose: @@ -185,8 +203,10 @@ specify std.functional: - it composes a single function correctly: expect (f (M.id) (1)).to_be (1) - it propagates nil arguments correctly: - expect ({f (M.id) (1, nil, nil, 4)}).to_equal {1, nil, nil, 4} - expect ({f (M.id, M.id) (1, nil, nil, 4)}).to_equal {1, nil, nil, 4} + expect (pack (f (M.id) (nil, 2, nil, nil))). + to_equal (pack (nil, 2, nil, nil)) + expect (pack (f (M.id, M.id) (nil, 2, nil, nil))). + to_equal (pack (nil, 2, nil, nil)) - it composes functions in the correct order: expect (f (math.sin, math.cos) (1)). to_be (math.cos (math.sin (1))) @@ -276,6 +296,8 @@ specify std.functional: - it propagates nil arguments correctly: t = {"a", nil, nil, "d", "e"} expect (f (M.id, base.npairs, t)).to_equal (t) + expect (f (M.id, iter, nil, nil, 3, 4, nil, nil)). + to_equal {false, false, 3, 4, false, false} - it passes all iteration result values to filter predicate: t = {} f (function (k, v) t[k] = v end, pairs, elements) @@ -381,6 +403,11 @@ specify std.functional: expect (f {1, 1, 2, 3}).to_equal {1, 1, 2, 3} - it returns multiple arguments unchanged: expect ({f (1, "two", false)}).to_equal {1, "two", false} + - it propagates nil values correctly: + expect (pack (f ("a", nil, nil, "d", "e"))). + to_equal (pack ("a", nil, nil, "d", "e")) + expect (pack (f (nil, nil, 3, 4, nil, nil))). + to_equal (pack (nil, nil, 3, 4, nil, nil)) - describe ireverse: @@ -473,7 +500,7 @@ specify std.functional: f = M.map - context with bad arguments: - badargs.diagnose (f, "std.functional.map (func, [func], any*)") + badargs.diagnose (f, "std.functional.map (func, [func], ?any*)") - it works with an empty table: expect (f (M.id, ipairs, {})).to_equal {} @@ -483,8 +510,8 @@ specify std.functional: - it propagates nil arguments correctly: t = {"a", nil, nil, "d", "e"} expect (f (M.id, base.npairs, t)).to_equal (t) - t = {nil, nil, 3, 4} - expect (f (M.id, base.npairs, t)).to_equal (t) + expect (f (M.id, iter, nil, nil, 3, 4, nil, nil)). + to_equal {false, false, 3, 4, false, false} - it passes all iteration result values to map function: t = {} f (function (k, v) t[k] = v end, pairs, elements) @@ -805,7 +832,7 @@ specify std.functional: f = M.reduce - context with bad arguments: - badargs.diagnose (f, "std.functional.reduce (func, any, [func], any*)") + badargs.diagnose (f, "std.functional.reduce (func, any, [func], ?any*)") - it works with an empty table: expect (f (op.sum, 2, ipairs, {})).to_be (2) @@ -823,6 +850,8 @@ specify std.functional: to_equal {"1", "nil", "nil", "a", "false"} expect (f (set, {}, base.npairs, {nil, nil, "3"})). to_equal {"nil", "nil", "3"} + expect (f (set, {}, iter, nil, nil, 3, 4, nil, nil)). + to_equal {"false", "false", "3", "4", "false", "false"} - it reduces elements from left to right: expect (f (op.pow, 2, base.ielems, {3, 4})).to_be ((2 ^ 3) ^ 4) - it passes all iterator results to accumulator function: diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index e50fda4..b34b1f8 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -25,6 +25,14 @@ local LUA = os.getenv "LUA" or "lua" setdebug = require "std.debug"._setdebug +-- 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 {} + return type (__len) == "function" and __len (x) or #x +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) @@ -47,7 +55,7 @@ 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, i or 1, j or maxn (t)) + return _unpack (t, tonumber (i) or 1, tonumber (j or t.n or len (t))) end From bf49901e87f2d2c6cac318e17db8e4a5db31f194 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 5 Dec 2015 00:30:24 +0000 Subject: [PATCH 642/703] Revert "std: use simpler semantics for faster npairs and rnpairs." This reverts most of commit 7b5e49f850d9443e7fb20f0a53cb13de477ab7eb, save using packed tables in lib/std/typing.lua which is faster and thus remains unreverted. Signed-off-by: Gary V. Vaughan --- NEWS.md | 4 ---- lib/std/base.lua | 23 ++++++++++++++++++++--- lib/std/delete-after/a-year.lua | 1 + lib/std/table.lua | 11 +---------- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/NEWS.md b/NEWS.md index 79dd0e5..e8d1410 100644 --- a/NEWS.md +++ b/NEWS.md @@ -250,10 +250,6 @@ into the module `prototype` field, and add the module functions to the parent table returned when the module is required. - - `std.npairs` and `std.rnpairs` no longer honor the `maxn` table size, - instead using either the `n` field (as set by `table.pack`) or else - the result of the length operator. - - `functional.lambda` no longer returns a bare function, but a functable that can be called and stringified. diff --git a/lib/std/base.lua b/lib/std/base.lua index 611087c..f28520c 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -50,6 +50,7 @@ local string_find = string.find local string_format = string.format local table_concat = table.concat local table_insert = table.insert +local table_maxn = table.maxn local table_pack = table.pack local table_sort = table.sort local table_unpack = table.unpack or unpack @@ -92,6 +93,15 @@ local function pairs (t) end +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. ]]-- @@ -306,7 +316,8 @@ end local function npairs (t) - local i, n = 0, t.n or len (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 @@ -321,7 +332,11 @@ end local function unpack (t, i, j) - j = j or t.n or len (t) + if j == nil then + -- respect __len, and then maxn if nil j was passed + local m = getmetamethod (t, "__len") + j = m and m (t) or maxn (t) + end local fn = getmetamethod (t, "__unpack") or table_unpack return fn (t, tonumber (i) or 1, tonumber (j)) end @@ -505,7 +520,8 @@ end local function rnpairs (t) - local oob = (t.n or len (t)) + 1 + local m = getmetamethod (t, "__len") + local oob = (m and m (t) or maxn (t)) + 1 return function (t, n) n = n - 1 @@ -672,6 +688,7 @@ return { table = { invert = invert, + maxn = maxn, pack = pack, unpack = unpack, }, diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 9b0acfc..a9a0ee7 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -70,6 +70,7 @@ if not require "std.debug_init"._DEBUG.deprecate then local copy = _.std.base.copy local leaves = _.std.tree.leaves local len = _.std.operator.len + local maxn = _.std.table.maxn local nop = _.std.functional.nop local pack = _.std.table.pack local sortkeys = _.std.base.sortkeys diff --git a/lib/std/table.lua b/lib/std/table.lua index ca6374b..667df07 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -19,7 +19,6 @@ local type = type local math_min = math.min local table_insert = table.insert -local table_maxn = table.maxn local _ = { @@ -36,6 +35,7 @@ local collect = _.std.functional.collect local copy = _.std.base.copy local invert = _.std.table.invert local len = _.std.operator.len +local maxn = _.std.table.maxn local merge = _.std.base.merge local pack = _.std.table.pack local unpack = _.std.table.unpack @@ -123,15 +123,6 @@ local function keys (t) end -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 - - local function new (x, t) return setmetatable (t or {}, {__index = function (t, i) From 9142ae3583a392261bea82d5052bba4a403caf26 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 5 Dec 2015 01:02:51 +0000 Subject: [PATCH 643/703] operator: len is deterministic for tables with holes. * specs/operator_spec.yaml (len): Add example that fails with current PUC-Rio # operator. * lib/std/base.lua (len): Look for the lowest numberic index with a nil element immediately following. * specs/spec_helper.lua (len): Update to match. * NEWS.md (New features): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 13 ++++++++++++- lib/std/base.lua | 16 +++++++++++++--- specs/operator_spec.yaml | 3 ++- specs/spec_helper.lua | 9 ++++++++- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index e8d1410..5f6ab8b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -125,7 +125,18 @@ - New `operator.eqv` is similar to `operator.eq`, except that it succeeds when recursive table contents are equivalent. - - New `operator.len` replaces deprecated `table.len`. + - New `operator.len` replaces deprecated `table.len`. `operator.len` + is always deterministic; counting only numerically indexed elements + immediately up to the first `nil` valued element (PUC-Rio Lua does + not have this feature for its `#` operator): + + ```lua + local t1 = {1, 2, [5]=3} + local t2 = {1, 2, nil, nil, 3} + print (eqv(t1, t2)) --> true + print (len(t1) == len(t2)) --> true + print (#t1 == #t2) --> LuaJIT: true, PUC-Rio: false + ``` - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. diff --git a/lib/std/base.lua b/lib/std/base.lua index f28520c..8742154 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -600,9 +600,19 @@ local tostring_vtable = { -- Lua < 5.2 doesn't call `__len` automatically! -len = function (t) - local m = getmetamethod (t, "__len") - return m and m (t) or #t +-- Also PUC-Rio Lua #operation can return any numerically indexed +-- element with an immediately following nil valued element, which is +-- non-deterministic for non-sequence tables. +len = function (x) + local m = getmetamethod (x, "__len") + if m then return m (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 diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml index f2ac1c1..10aac5c 100644 --- a/specs/operator_spec.yaml +++ b/specs/operator_spec.yaml @@ -43,7 +43,8 @@ specify std.operator: expect (f {1, 2, 5, a=10, 3}).to_be (4) - it works with an empty table: expect (f {}).to_be (0) - - it ignores elements after a hole: + - it always ignores elements after the first hole: + expect (f {1, 2, nil, nil, 3}).to_be (2) expect (f {1, 2, [5]=3}).to_be (2) - it respects __len metamethod: t = setmetatable ({1, 2, [5]=3}, {__len = function () return 42 end}) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index b34b1f8..06d65ad 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -29,7 +29,14 @@ setdebug = require "std.debug"._setdebug -- valued __len metamethod, so don't write examples that need that! function len (x) local __len = getmetatable (x) or {} - return type (__len) == "function" and __len (x) or #x + 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 From f0bf4fbf395526d270587e369d4838214c467fee Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 5 Dec 2015 01:22:54 +0000 Subject: [PATCH 644/703] functional: memoize propagates nil return values correctly. * specs/functional_spec.yaml (memoize): New example to show correct behaviour when returning nil values from mnemonic function. * lib/std/functional.lua (memoize): Pack and unpack return values correctly. * NEWS.md (Bug fixes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 ++- lib/std/functional.lua | 4 ++-- specs/functional_spec.yaml | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5f6ab8b..c050529 100644 --- a/NEWS.md +++ b/NEWS.md @@ -213,7 +213,8 @@ `nil` arguments correctly. - `std.functional.memoize` now considers trailing nil arguments when - looking up memoized value for those particular arguments. + looking up memoized value for those particular arguments, and propagates + `nil` return values from `mnemonic` functions correctly. - `std.functional.filter`, `std.functional.map` and `std.functional.reduce` now pass trailing nil arguments to their diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 595166e..2b85d50 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -275,10 +275,10 @@ local function memoize (fn, mnemonic) local k = mnemonic (...) local t = self[k] if t == nil then - t = {fn (...)} + t = pack (fn (...)) self[k] = t end - return unpack (t) + return unpack (t, 1, t.n) end }) end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 43ce678..65c2080 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -581,6 +581,9 @@ specify std.functional: - it propagates multiple return values: expect ((memfn ())).to_be (nil) expect (select (2, memfn ())).to_be "bzzt" + - it propogates nil return values: + expect (pack (f (function (...) return ... end) (nil, nil, 3, 4, nil, nil))). + to_equal (pack (nil, nil, 3, 4, nil, nil)) - it propagates multiple arguments: expect ({memfn ("a", 42, false)}).to_equal {3, {"a", 42, false}} - it propagates nil arguments: From ec696d1e7a6f9fed8e49fb734d2ab8b9b2d267a1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 5 Jan 2016 23:10:11 +0000 Subject: [PATCH 645/703] slingshot: update to latest revision for Travis GCE compatibility. * slingshot: Pull latest upstream. * .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 33 ++++++++++++++++++++++----------- slingshot | 2 +- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0f8d610..3f331b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,17 @@ language: c +addons: + apt: + packages: + - help2man + env: global: + - LUAJIT_DIR=luajit-2.0 + - LUAJIT_VER=2.0.4 + - LUA53_VER=5.3.2 + - LUAROCKS_VER=2.2.2 + - _COMPILE="libtool --mode=compile --tag=CC gcc" - _CFLAGS="-O2 -Wall -DLUA_COMPAT_ALL -DLUA_COMPAT_5_2 -DLUA_USE_LINUX" - _INSTALL="libtool --mode=install install -p" @@ -22,18 +32,19 @@ env: - LUA=lua5.1 - LUA=luajit +sudo: required + before_install: # Put back the links for libyaml, which are missing on recent Travis VMs - test -f /usr/lib/libyaml.so || sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; - - sudo apt-get install help2man # Fetch Lua sources. - cd $TRAVIS_BUILD_DIR - 'if test lua5.3 = "$LUA"; then - curl http://www.lua.org/ftp/lua-5.3.1.tar.gz | tar xz; - cd lua-5.3.1; + curl http://www.lua.org/ftp/lua-$LUA53_VER.tar.gz | tar xz; + cd lua-$LUA53_VER; fi' - 'if test lua5.2 = "$LUA"; then curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz; @@ -46,12 +57,12 @@ before_install: # Unpack, compile and install Lua. - 'if test luajit = "$LUA"; then - curl http://luajit.org/download/LuaJIT-2.0.3.tar.gz | tar xz; - cd LuaJIT-2.0.3; + curl http://luajit.org/download/LuaJIT-$LUAJIT_VER.tar.gz | tar xz; + cd LuaJIT-$LUAJIT_VER; make && sudo make install; for header in lua.h luaconf.h lualib.h lauxlib.h luajit.h lua.hpp; do - if test -f /usr/local/include/luajit-2.0/$header; then - sudo ln -s /usr/local/include/luajit-2.0/$header /usr/local/include/$header; + if test -f /usr/local/include/$LUAJIT_DIR/$header; then + sudo ln -s /usr/local/include/$LUAJIT_DIR/$header /usr/local/include/$header; fi; done; else @@ -77,9 +88,9 @@ before_install: # Fetch LuaRocks. - cd $TRAVIS_BUILD_DIR - - 'git clone https://github.com/keplerproject/luarocks.git luarocks-2.2.0' - - cd luarocks-2.2.0 - - git checkout v2.2.0 + - 'git clone https://github.com/keplerproject/luarocks.git luarocks-$LUAROCKS_VER' + - cd luarocks-$LUAROCKS_VER + - git checkout v$LUAROCKS_VER # Compile and install luarocks. - if test luajit = "$LUA"; then @@ -91,7 +102,7 @@ before_install: # Tidy up file droppings. - cd $TRAVIS_BUILD_DIR - - rm -rf lua-5.3.1 lua-5.2.3 lua-5.1.5 LuaJIT-2.0.3 luarocks-2.2.0 + - rm -rf lua-$LUA53_VER lua-5.2.4 lua-5.1.5 LuaJIT-$LUAJIT_VER luarocks-$LUAROCKS_VER install: diff --git a/slingshot b/slingshot index 43a723d..84d850a 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 43a723dd7f5585aaefa0b209d927eb0e77aa674a +Subproject commit 84d850ad801617c81d2a69a6dc9c74204f512327 From 20f3a1a39c0ed59709800966ae9bc08cbedb900a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 6 Jan 2016 00:26:27 +0000 Subject: [PATCH 646/703] maint: bump copyright years. * COPYING, README.md, bootstrap.conf, configure.ac, local.mk, slingshot: Add 2016 to copyright years. Signed-off-by: Gary V. Vaughan --- COPYING | 2 +- README.md | 2 +- bootstrap.conf | 2 +- configure.ac | 2 +- local.mk | 2 +- slingshot | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/COPYING b/COPYING index ec03e04..43c6b1e 100644 --- a/COPYING +++ b/COPYING @@ -4,7 +4,7 @@ the terms of the MIT license (the same license as Lua itself), unless noted otherwise in the body of that file. ==================================================================== -Copyright (C) 2002-2015 stdlib authors +Copyright (C) 2002-2016 stdlib authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index c01bd9c..eef5ae7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ by the [stdlib project][github] This is a collection of Lua libraries for LuaJIT, Lua 5.1, 5.2 and 5.3. -The libraries are copyright by their authors 2000-2015 (see the +The libraries are copyright by their authors 2000-2016 (see the [AUTHORS][] file for details), and released under the [MIT license][mit] (the same license as Lua itself). There is no warranty. diff --git a/bootstrap.conf b/bootstrap.conf index ae8a642..63c9684 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -1,6 +1,6 @@ # bootstrap.conf (Stdlib) version 2015-01-03 # -# Copyright (C) 2013-2015 Gary V. Vaughan +# Copyright (C) 2013-2016 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 # This is free software; see the source for copying conditions. There is NO diff --git a/configure.ac b/configure.ac index fd116ac..d642536 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl configure.ac dnl -dnl Copyright (C) 2012-2015 Gary V. Vaughan +dnl Copyright (C) 2012-2016 Gary V. Vaughan dnl Written by Gary V. Vaughan, 2012 dnl dnl This program is free software; you can redistribute it and/or modify diff --git a/local.mk b/local.mk index 04dbccd..317428c 100644 --- a/local.mk +++ b/local.mk @@ -1,6 +1,6 @@ # Local Make rules. # -# Copyright (C) 2013-2015 Gary V. Vaughan +# Copyright (C) 2013-2016 Gary V. Vaughan # Written by Gary V. Vaughan, 2013 # # This program is free software; you can redistribute it and/or modify it diff --git a/slingshot b/slingshot index 84d850a..3dee4de 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 84d850ad801617c81d2a69a6dc9c74204f512327 +Subproject commit 3dee4defd7d3d80a62b4638012f5dee973cc7114 From 87182d6f127165ed9b55b7b2dd1d182f74860721 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 6 Jan 2016 00:46:24 +0000 Subject: [PATCH 647/703] maint: remove APIs deprecated on 2016-01-03. * lib/std/delete-after/2016-01-03.lua: Remove. * local.mk (dist_luastddelete_DATA): Adjust. * lib/std/functional.lua, lib/std/list.lua, lib/std/string.lua (deprecated): Replace reference to std.delete-after.2016-01-03 with nil. * specs/functional_spec.yaml, specs/list_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Remove examples of deleted previously deprecated APIs. * NEWS.md (Incompatible Changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 23 +- lib/std/delete-after/2016-01-03.lua | 518 ------------------- lib/std/functional.lua | 2 +- lib/std/list.lua | 2 +- lib/std/string.lua | 2 +- local.mk | 1 - specs/functional_spec.yaml | 210 +------- specs/list_spec.yaml | 744 ---------------------------- specs/string_spec.yaml | 127 +---- specs/table_spec.yaml | 78 +-- 10 files changed, 31 insertions(+), 1676 deletions(-) delete mode 100644 lib/std/delete-after/2016-01-03.lua diff --git a/NEWS.md b/NEWS.md index c050529..94f6018 100644 --- a/NEWS.md +++ b/NEWS.md @@ -237,10 +237,25 @@ - Deprecated multi-argument `functional.bind` has been removed. - - Deprecated methods `list:depair`, `list:map_with`, `list:transpose` and - `list:zip_with` have been removed. - - - Deprecated function `table.clone_rename` has been removed. + - Deprecated methods `list:depair`, `list:elems`, `list:enpair`, + `list:filter`, `list:flatten`, `list:foldl`, `list:foldr`, + `list:index_key`, `list:index_value`, `list:map`, `list:map_with`, + `list:project`, `list:relems`, `list:reverse`, `list:shape`, + `list:transpose` and `list:zip_with` have been removed. + + - Deprecated functions `functional.eval`, `functional.fold`, + `functional.op["[]"]`, `functional.op["+"]`, `functional.op["-"]`, + `functional.op["*"]`, `functional.op["/"], `functional.op["and"]', + `functional.op["or"]`, `functional.op["not"]`, `functional.op["=="]`, + `functional.op["~="]`, `list.depair`, `list.elems`, `list.enpair`, + `list.filter`, `list.flatten`, `list.foldl`, `list.foldr`, + `list.index_key`, `list.index_value`, `list.map`, `list.map_with`, + `list.project`, `list.relems`, `list.reverse`, `list.shape`, + `list.transpose`, `list.zip_with`, `string.assert`, + `string.require_version`, `string.tostring`, `table.clone_rename`, + `table.metamethod`, `table.ripairs` and `table.totable` have been + removed. See previous entries below for what they were replaced + by. - Now that the `prototype` field is used to reference a module's object prototype, `std.object.prototype` no longer return the object diff --git a/lib/std/delete-after/2016-01-03.lua b/lib/std/delete-after/2016-01-03.lua deleted file mode 100644 index 7570fcf..0000000 --- a/lib/std/delete-after/2016-01-03.lua +++ /dev/null @@ -1,518 +0,0 @@ ---[[-- - Provide at least one year of support for deprecated APIs, or at - least one release cycle if that is longer. - - When `_DEBUG.deprecate` is `true` we don`t even load this support, in - which case `require`ing this module returns `false`. - - Otherwise, return a table of all functions deprecated in the given - `RELEASE` and earlier, going back at least one year. The table is - keyed on the original module to enable merging deprecated APIs back - into their previous namespaces - this is handled automatically by the - documented modules according to the contents of `_DEBUG`. - - In some release after the date of this module, it will be removed and - these APIs will not be available any longer. -]] - - -local RELEASE = "41.0.0" - -local M = false - -if not require "std.debug_init"._DEBUG.deprecate then - - local assert = assert - local error = error - local getmetatable = getmetatable - local pairs = pairs - local require = require - local tonumber = tonumber - local tostring = tostring - local type = type - - local coroutine_yield = coroutine.yield - local coroutine_wrap = coroutine.wrap - local math_ceil = math.ceil - local math_min = math.min - local math_max = math.max - local string_find = string.find - local string_format = string.format - local table_insert = table.insert - local table_unpack = table.unpack or unpack - - local _, deprecated = { - -- Adding anything else here will probably cause a require loop. - maturity = require "std.maturity", - std = require "std.base", - strict = require "std.strict", - } - - -- Merge in deprecated APIs from previous release if still available. - _.ok, deprecated = pcall (require, "std.delete-after.2015-05.01") - if not _.ok then deprecated = {} end - - - -- Dangerous :-o Hope we don't need to deprecate anything in - -- std.operator any time in the next year or so... - local operator = require "std.operator" - - local _ipairs = _.std.ipairs - local _pairs = _.std.pairs - local _tostring = _.std.tostring - local DEPRECATED = _.maturity.DEPRECATED - local eval = _.std.eval - local ielems = _.std.ielems - local ripairs = _.std.ripairs - - -- Only the above symbols are used below this line. - local _, _ENV = nil, _.strict {} - - - --[[ ========== ]]-- - --[[ Death Row! ]]-- - --[[ ========== ]]-- - - - local function _assert (expect, fmt, arg1, ...) - local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or "" - return expect or error (msg, 2) - end - - - local function callable (x) - if type (x) == "function" then return x end - return (getmetatable (x) or {}).__call - end - - - local function getmetamethod (x, n) - local m = (getmetatable (x) or {})[n] - if callable (m) then return m end - end - - - local function len (t) - local m = getmetamethod (t, "__len") - return m and m (t) or #t - end - - - local function compare (l, m) - local lenl, lenm = len (l), len (m) - for i = 1, math_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 depair (proto, ls) - local t = {} - for _, v in _ipairs (ls) do - t[v[1]] = v[2] - end - return t - end - - - local function elems (proto, ...) - return ielems (...) - end - - - local function enpair (proto, t) - local ls = proto {} - for i, v in _pairs (t) do - ls[#ls + 1] = proto {i, v} - end - return ls - end - - - local function filter (proto, pfn, l) - local r = proto {} - for _, e in _ipairs (l) do - if pfn (e) then - r[#r + 1] = e - end - end - return r - end - - - local function fold (fn, d, ifn, ...) - local nextfn, state, k = ifn (...) - local t = {nextfn (state, k)} - - local r = d - while t[1] ~= nil do - r = fn (r, t[#t]) - t = {nextfn (state, t[1])} - end - return r - end - - - local function ireverse (t) - local oob = 1 - while t[oob] ~= nil do - oob = oob + 1 - end - - local r = {} - for i = 1, oob - 1 do r[oob - i] = t[i] end - return r - end - - - local function reduce (fn, d, ifn, ...) - local argt = {...} - if not callable (ifn) then - ifn, argt = _pairs, {ifn, ...} - end - - local nextfn, state, k = ifn (table_unpack (argt)) - local t = {nextfn (state, k)} -- table of iteration 1 - - local r = d -- initialise accumulator - while t[1] ~= nil do -- until iterator returns nil - k = t[1] - r = fn (r, table_unpack (t)) -- pass all iterator results to fn - t = {nextfn (state, k)} -- maintain loop invariant - end - return r - end - - - local function foldl (proto, fn, d, t) - if t == nil then - local tail = {} - for i = 2, len (d) do tail[#tail + 1] = d[i] end - d, t = d[1], tail - end - return reduce (fn, d, ielems, t) - end - - - local function foldr (proto, fn, d, t) - if t == nil then - local u, last = {}, len (d) - for i = 1, last - 1 do u[#u + 1] = d[i] end - d, t = d[last], u - end - return reduce (function (x, y) return fn (y, x) end, d, ielems, ireverse (t)) - end - - - local function index_key (proto, f, l) - local r = {} - for i, v in _ipairs (l) do - local k = v[f] - if k then - r[k] = i - end - end - return r - end - - - local function index_value (proto, f, l) - local r = {} - for i, v in _ipairs (l) do - local k = v[f] - if k then - r[k] = v - end - end - return r - 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 - coroutine_yield (n) - end - end - return coroutine_wrap (visit), tr - end - - - local function flatten (proto, l) - local r = proto {} - for v in leaves (_ipairs, l) do - r[#r + 1] = v - end - return r - end - - - local function map (proto, fn, l) - local r = proto {} - for _, e in _ipairs (l) do - local v = fn (e) - if v ~= nil then - r[#r + 1] = v - end - end - return r - end - - - local function map_with (proto, fn, ls) - return map (proto, function (...) return fn (table_unpack (...)) end, ls) - end - - - local function project (proto, x, l) - return map (proto, function (t) return t[x] end, l) - end - - - local function relems (proto, l) return ielems (ireverse (l)) end - - - local function reverse (proto, l) return proto (ireverse (l)) end - - - local function shape (proto, s, l) - l = flatten (proto, l) - -- Check the shape and calculate the size of the zero, if any - local size = 1 - local zero - for i, v in _ipairs (s) do - if v == 0 then - if zero then -- bad shape: two zeros - return nil - else - zero = i - end - else - size = size * v - end - end - if zero then - s[zero] = math_ceil (len (l) / size) - end - local function fill (i, d) - if d > len (s) then - return l[i], i + 1 - else - local r = proto {} - for j = 1, s[d] do - local e - e, i = fill (i, d + 1) - r[#r + 1] = e - end - return r, i - end - end - return (fill (1, 1)) - end - - - local function split (s, sep) - local r, patt = {} - if sep == "" then - patt = "(.)" - table_insert (r, "") - else - patt = "(.-)" .. (sep or "%s+") - end - local b, slen = 0, len (s) - while b <= slen do - local e, n, m = string_find (s, patt, b + 1) - table_insert (r, m or s:sub (b + 1, slen)) - b = n or slen + 1 - end - return r - end - - - local function totable (x) - local m = getmetamethod (x, "__totable") - if m then - return m (x) - elseif type (x) == "table" then - return x - elseif type (x) == "string" then - local t = {} - x:gsub (".", function (c) t[#t + 1] = c end) - return t - else - return nil - end - end - - - local function transpose (proto, ls) - local rs, lenls, dims = proto {}, len (ls), map (proto, len, ls) - if len (dims) > 0 then - for i = 1, math_max (table_unpack (dims)) do - rs[i] = proto {} - for j = 1, lenls do - rs[i][j] = ls[j][i] - end - end - end - return rs - end - - - local function vcompare (a, b) - return compare (split (a, "%."), split (b, "%.")) - end - - - local function _require (module, min, too_big, pattern) - local m = require (module) - local v = tostring (type (m) == "table" and (m.version or m._VERSION) or ""):match (pattern or "([%.%d]+)%D*$") - 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 - - - local function zip_with (proto, ls, fn) - return map_with (proto, fn, transpose (proto, ls)) - end - - - -- Ensure deprecated APIs observe _DEBUG warning standards. - local function X (old, new, fn) - if fn ~= nil then new = "use '" .. new .. "' instead" end - return DEPRECATED (RELEASE, "'std." .. old .. "'", new, fn) - end - - local function DEPRECATEOP (old, new) - return X ("functional.op[" .. old .. "]", "std.operator." .. new, operator[new]) - end - - local function acyclic_merge (dest, src) - for k, v in pairs (src) do - if type (v) == "table" then - dest[k] = dest[k] or {} - if type (dest[k]) == "table" then acyclic_merge (dest[k], v) end - else - dest[k] = dest[k] or v - end - end - return dest - end - - M = acyclic_merge ({ - functional = { - eval = X ("functional.eval", "std.eval", eval), - fold = X ("functional.fold", "std.functional.reduce", fold), - op = { - ["[]"] = DEPRECATEOP ("[]", "get"), - ["+"] = DEPRECATEOP ("+", "sum"), - ["-"] = DEPRECATEOP ("-", "diff"), - ["*"] = DEPRECATEOP ("*", "prod"), - ["/"] = DEPRECATEOP ("/", "quot"), - ["and"] = DEPRECATEOP ("and", "conj"), - ["or"] = DEPRECATEOP ("or", "disj"), - ["not"] = DEPRECATEOP ("not", "neg"), - ["=="] = DEPRECATEOP ("==", "eq"), - ["~="] = DEPRECATEOP ("~=", "neq"), - }, - }, - - list = { - depair = X ("list.depair", depair), - elems = X ("list.elems", "std.ielems", elems), - enpair = X ("list.enpair", enpair), - filter = X ("list.filter", "std.functional.filter", filter), - flatten = X ("list.flatten", "std.functional.flatten", flatten), - foldl = X ("list.foldl", "std.functional.foldl", foldl), - foldr = X ("list.foldr", "std.functional.foldr", foldr), - index_key = DEPRECATED (RELEASE, "'std.list.index_key'", - "compose 'std.functional.filter' and 'std.table.invert' instead", - index_key), - index_value = DEPRECATED (RELEASE, "'std.list.index_value'", - "compose 'std.functional.filter' and 'std.table.invert' instead", - index_value), - map = X ("list.map", "std.functional.map", map), - map_with = X ("list.map_with'", "std.functional.map_with", map_with), - project = X ("list.project", "std.table.project", project), - relems = DEPRECATED (RELEASE, "'std.list.relems'", - "compose 'std.ielems' and 'std.functional.ireverse' instead", relems), - reverse = DEPRECATED (RELEASE, "'std.list.reverse'", - "compose 'std.list' and 'std.functional.ireverse' instead", reverse), - shape = X ("list.shape", "std.table.shape", shape), - transpose = X ("list.transpose", "std.functional.zip", transpose), - zip_with = X ("list.zip_with", "std.functional.zip_with", zip_with), - }, - - string = { - assert = X ("string.assert", "std.assert", _assert), - require_version = X ("string.require_version", "std.require", _require), - tostring = X ("string.tostring", "std.tostring", _tostring), - }, - - table = { - metamethod = X ("table.metamethod", "std.getmetamethod", getmetamethod), - ripairs = X ("table.ripairs", "std.ripairs", ripairs), - totable = X ("table.totable", "std.pairs", totable), - }, - - methods = { - list = { - elems = X ("list:elems", "std.ielems", elems), - enpair = X ("list:enpair", enpair), - filter = X ("std.list:filter", "std.functional.filter", - function (proto, self, p) return filter (proto, p, self) end), - flatten = X ("list:flatten", "std.functional.flatten", flatten), - foldl = X ("list:foldl", "std.functional.foldl", function (proto, self, fn, e) - if e ~= nil then return foldl (proto, fn, e, self) end - return foldl (proto, fn, self) - end), - foldr = X ("list:foldr", "std.functional.foldr", function (proto, self, fn, e) - if e ~= nil then return foldr (proto, fn, e, self) end - return foldr (proto, fn, self) - end), - index_key = X ("list:index_key", - function (proto, self, fn) return index_key (proto, fn, self) end), - index_value = X ("list:index_value", - function (proto, self, fn) return index_value (proto, fn, self) end), - map = X ("list:map", "std.functional.map", - function (proto, self, fn) return map (proto, fn, self) end), - project = X ("list:project", "std.table.project", - function (proto, self, x) return project (proto, x, self) end), - relems = X ("list:relems", relems), - reverse = DEPRECATED (RELEASE, "'std.list:reverse'", - "compose 'std.list' and 'std.functional.ireverse' instead", reverse), - shape = X ("list:shape", "std.table.shape", - function (proto, t, l) return shape (proto, l, t) end), - }, - }, - }, - deprecated) - -end - -return M diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 2b85d50..8042610 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -43,7 +43,7 @@ local leaves = _.std.tree.leaves local unpack = _.std.table.unpack -local deprecated = require "std.delete-after.2016-01-03" +local deprecated = nil local _, _ENV = nil, _.strict {} diff --git a/lib/std/list.lua b/lib/std/list.lua index 1a4d860..1da1333 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -40,7 +40,7 @@ local merge = _.std.base.merge local unpack = _.std.table.unpack -local deprecated = require "std.delete-after.2016-01-03" +local deprecated = nil local _, _ENV = nil, _.strict {} diff --git a/lib/std/string.lua b/lib/std/string.lua index 687435a..77b7f89 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -48,7 +48,7 @@ local split = _.std.string.split local toqstring = _.std.base.toqstring -local deprecated = require "std.delete-after.2016-01-03" +local deprecated = nil local _, _ENV = nil, _.strict {} diff --git a/local.mk b/local.mk index 317428c..3afdf9d 100644 --- a/local.mk +++ b/local.mk @@ -91,7 +91,6 @@ dist_luastd_DATA = \ luastddeletedir = $(luastddir)/delete-after dist_luastddelete_DATA = \ - lib/std/delete-after/2016-01-03.lua \ lib/std/delete-after/2016-01-31.lua \ lib/std/delete-after/2016-03-08.lua \ lib/std/delete-after/a-year.lua \ diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 65c2080..7886107 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -5,10 +5,9 @@ before: global_table = "_G" exported_apis = { "any", "bind", "callable", "case", "collect", "compose", - "cond", "curry", "eval", "filter", "flatten", "fold", - "foldl", "foldr", "id", "ireverse", "lambda", "map", - "map_with", "memoize", "nop", "op", "product", "reduce", - "shape", "zip", "zip_with" } + "cond", "curry", "filter", "flatten", "foldl", "foldr", + "id", "ireverse", "lambda", "map", "map_with", "memoize", + "nop", "product", "reduce", "shape", "zip", "zip_with" } setdebug { deprecate = false } @@ -263,21 +262,6 @@ specify std.functional: expect (bin (10)).to_be (op.pow (2, 10)) -- describe eval: - - before: - f = M.eval - - - it writes a deprecation warning: - expect (deprecate_on ("eval", 42)).to_contain_error "was deprecated" - expect (deprecate_off ("eval", 42)).not_to_contain_error "was deprecated" - - - 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 filter: - before: elements = {"a", "b", "c", "d", "e"} @@ -334,31 +318,6 @@ specify std.functional: expect (f (t)).to_equal {"one", "two", "three", "four"} -- describe fold: - - before: - op = require "std.operator" - f = M.fold - - - it writes a deprecation warning: - expect (deprecate_on ("fold", "M.id, 1, ipairs, {}")). - to_contain_error "was deprecated" - expect (deprecate_off ("fold", "M.id, 1, ipairs, {}")). - not_to_contain_error "was deprecated" - - - it works with an empty table: - expect (f (op.sum, 2, ipairs, {})).to_be (2) - - it calls a binary function over single return value iterator results: - expect (f (op.sum, 2, base.ielems, {3})). - to_be (2 + 3) - expect (f (op.prod, 2, base.ielems, {3, 4})). - to_be (2 * 3 * 4) - - it calls a binary function over key:value iterator results: - expect (f (op.sum, 2, ipairs, {3})).to_be (2 + 3) - expect (f (op.prod, 2, ipairs, {3, 4})).to_be (2 * 3 * 4) - - it folds elements from left to right: - expect (f (op.pow, 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4) - - - describe foldl: - before: op = require "std.operator" @@ -640,169 +599,6 @@ specify std.functional: expect (f (1, "two", false)).to_be (nil) -- describe op: - - context with []: - - before: - f = M.op["[]"] - - - it writes a deprecation warning: - expect (deprecate_on ("op['[]']", "{2}, 1")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['[]']", "{2}, 1")). - not_to_contain_error "was deprecated" - - - it dereferences a table: - expect (f ({}, 1)).to_be (nil) - expect (f ({"foo", "bar"}, 1)).to_be "foo" - expect (f ({foo = "bar"}, "foo")).to_be "bar" - - - context with +: - - before: - f = M.op["+"] - - - it writes a deprecation warning: - expect (deprecate_on ("op['+']", "2, 1")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['+']", "2, 1")). - not_to_contain_error "was deprecated" - - - it returns the sum of its arguments: - expect (f (99, 2)).to_be (99 + 2) - - - context with -: - - before: - f = M.op["-"] - - - it writes a deprecation warning: - expect (deprecate_on ("op['-']", "2, 1")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['-']", "2, 1")). - not_to_contain_error "was deprecated" - - - it returns the difference of its arguments: - expect (f (99, 2)).to_be (99 - 2) - - - context with *: - - before: - f = M.op["*"] - - - it writes a deprecation warning: - expect (deprecate_on ("op['*']", "2, 1")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['*']", "2, 1")). - not_to_contain_error "was deprecated" - - - it returns the product of its arguments: - expect (f (99, 2)).to_be (99 * 2) - - - context with /: - - before: - f = M.op["/"] - - - it writes a deprecation warning on: - expect (deprecate_on ("op['/']", "2, 1")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['/']", "2, 1")). - not_to_contain_error "was deprecated" - - - it returns the quotient of its arguments: - expect (f (99, 2)).to_be (99 / 2) - - - context with and: - - before: - f = M.op["and"] - - - it writes a deprecation warning: - expect (deprecate_on ("op['and']", "true, false")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['and']", "true, false")). - not_to_contain_error "was deprecated" - - - it returns the logical and of its arguments: - expect (f (false, false)).to_be (false) - expect (f (false, true)).to_be (false) - expect (f (true, false)).to_be (false) - expect (f (true, true)).to_be (true) - - it supports truthy and falsey arguments: - expect (f ()).to_be (nil) - expect (f (0)).to_be (nil) - expect (f (nil, 0)).to_be (nil) - expect (f (0, "false")).to_be ("false") - - - context with or: - - before: - f = M.op["or"] - - - it writes a deprecation warning: - expect (deprecate_on ("op['or']", "true, false")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['or']", "true, false")). - not_to_contain_error "was deprecated" - - - it returns the logical or of its arguments: - expect (f (false, false)).to_be (false) - expect (f (false, true)).to_be (true) - expect (f (true, false)).to_be (true) - expect (f (true, true)).to_be (true) - - it supports truthy and falsey arguments: - expect (f ()).to_be (nil) - expect (f (0)).to_be (0) - expect (f (nil, 0)).to_be (0) - expect (f (0, "false")).to_be (0) - - - context with not: - - before: - f = M.op["not"] - - - it writes a deprecation warning: - expect (deprecate_on ("op['not']", true)). - to_contain_error "was deprecated" - expect (deprecate_off ("op['not']", true)). - not_to_contain_error "was deprecated" - - - it returns the logical not of its argument: - expect (f (false)).to_be (true) - expect (f (true)).to_be (false) - - it supports truthy and falsey arguments: - expect (f ()).to_be (true) - expect (f (0)).to_be (false) - - - context with ==: - - before: - f = M.op["=="] - - - it writes a deprecation warning: - expect (deprecate_on ("op['==']", "2, 1")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['==']", "2, 1")). - not_to_contain_error "was deprecated" - - - it returns true if the arguments are equal: - expect (f ()).to_be (true) - expect (f ("foo", "foo")).to_be (true) - - it returns false if the arguments are unequal: - expect (f (1)).to_be (false) - expect (f ("foo", "bar")).to_be (false) - - - context with ~=: - - before: - f = M.op["~="] - - - it writes a deprecation warning: - expect (deprecate_on ("op['~=']", "2, 1")). - to_contain_error "was deprecated" - expect (deprecate_off ("op['~=']", "2, 1")). - not_to_contain_error "was deprecated" - - - it returns false if the arguments are equal: - expect (f (1, 1)).to_be (false) - expect (f ("foo", "foo")).to_be (false) - - it returns true if the arguments are unequal: - expect (f (1, 2)).to_be (true) - expect (f ("foo", "bar")).to_be (true) - expect (f ({}, {})).to_be (true) - - - describe product: - before: f = M.product diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index f166cac..34a9d85 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -284,581 +284,6 @@ specify std.list: expect (f (List {}, "x")).to_equal (List {"x"}) -- describe depair: - - before: - l = List {List {1, "first"}, List {2, "second"}, List {"third", 4}} - t = {"first", "second", third = 4} - - - context as a module function: - - before: - f = M.depair - - - it writes a deprecation warning: - expect (deprecate_on ("depair", "P {P {1, 'first'}}")). - to_contain_error "was deprecated" - expect (deprecate_off ("depair", "P {P {1, 'first'}}")). - not_to_contain_error "was deprecated" - - - it returns a primitive table: - expect (objtype (f (l))).to_be "table" - - it works with an empty List: - l = List {} - expect (f (l)).to_equal {} - - it is the inverse of enpair: - expect (f (l)).to_equal (t) - - -- describe elems: - - context as a module function: - - before: - f = M.elems - - - it writes a deprecation warning: - expect (deprecate_on ("elems", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("elems", "{}")). - not_to_contain_error "was deprecated" - - - it is an iterator over List members: - t = {} - for e in f (l) do table.insert (t, e) end - expect (t).to_equal {"foo", "bar", "baz"} - - it works for an empty List: - t = {} - for e in f (List {}) do table.insert (t, e) end - expect (t).to_equal {} - - - context as an object method: - - before: - f = l.elems - - - it writes a deprecation warning: - expect (deprecate_on ("elems", "", "{1, 2}")). - to_contain_error "was deprecated" - expect (deprecate_off ("elems", "", "{1, 2}")). - not_to_contain_error "was deprecated" - - - it is an iterator over List members: - t = {} - for e in l:elems () do table.insert (t, e) end - expect (t).to_equal {"foo", "bar", "baz"} - - it works for an empty List: - t, l = {}, List {} - for e in l:elems () do table.insert (t, e) end - expect (t).to_equal {} - - -- describe enpair: - - before: - t = {"first", "second", third = 4} - f = M.enpair - - - it writes a deprecation warning: - expect (deprecate_on ("enpair", "{1,2,three=4}")). - to_contain_error "was deprecated" - expect (deprecate_off ("enpair", "{1,2,three=4}")). - not_to_contain_error "was deprecated" - - - context as a module function: - - it returns a List object: - expect (objtype (f (t))).to_be "List" - - it works for an empty table: - expect (f {}).to_equal (List {}) - - it turns a table into a List of pairs: - expect (f (t)). - to_equal (List {List {1, "first"}, List {2, "second"}, List {"third", 4}}) - - -- describe filter: - - before: - l = List {"foo", "bar", "baz", "quux"} - p = function (e) return (e:match "a" ~= nil) end - - - context as a module function: - - before: - f = M.filter - - - it writes a deprecation warning: - argstr="function (e) return true end, P {1, 2, 5}" - expect (deprecate_on ("filter", argstr)). - to_contain_error "was deprecated" - expect (deprecate_off ("filter", argstr)). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (p, l))).to_be "List" - - it works for an empty List: - expect (f (p, List {})).to_equal (List {}) - - it filters a List according to a predicate: - expect (f (p, l)).to_equal (List {"bar", "baz"}) - - - context as an object method: - - before: - f = l.filter - - - it writes a deprecation warning: - argstr="function (e) return true end" - expect (deprecate_on ("filter", argstr, "{1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("filter", argstr, "{1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l, p))).to_be "List" - - it works for an empty List: - expect (f (List {}, p)).to_equal (List {}) - - it filters a List according to a predicate: - expect (f (l, p)).to_equal (List {"bar", "baz"}) - - -- describe flatten: - - before: - l = List {List {List {"one"}}, "two", List {List {"three"}, "four"}} - - - context as a module function: - - before: - f = M.flatten - - - it writes a deprecation warning: - expect (deprecate_on ("flatten", "P {1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("flatten", "P {1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l))).to_be "List" - - it works for an empty List: - l = List {} - expect (f (l)).to_equal (List {}) - - it flattens a List: - expect (f (l)). - to_equal (List {"one", "two", "three", "four"}) - - - context as an object method: - - before: - f = l.flatten - - - it writes a deprecation warning: - expect (deprecate_on ("flatten", "", "{1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("flatten", "", "{1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l))).to_be "List" - - it works for an empty List: - l = List {} - expect (f (l)).to_equal (List {}) - - it flattens a List: - expect (f (l)). - to_equal (List {"one", "two", "three", "four"}) - - -- describe foldl: - - before: - op = require "std.operator" - l = List {3, 4} - - - context as a module function: - - before: - f = M.foldl - - - it writes a deprecation warning: - argstr = "require 'std.operator'.sum, 1, P {1, 2, 5}" - expect (deprecate_on ("foldl", argstr)). - to_contain_error "was deprecated" - expect (deprecate_off ("foldl", argstr)). - not_to_contain_error "was deprecated" - - - context with a table: - - it works with an empty table: - expect (f (op.sum, 10000, {})).to_be (10000) - - it folds a binary function through a table: - expect (f (op.sum, 10000, {1, 10, 100})).to_be (10111) - - it folds from left to right: - expect (f (op.pow, 2, {3, 4})).to_be ((2 ^ 3) ^ 4) - - - context with a List: - - it works with an empty List: - expect (f (op.sum, 10000, List {})).to_be (10000) - - it folds a binary function through a List: - expect (f (op.sum, 10000, List {1, 10, 100})). - to_be (10111) - - it folds from left to right: - expect (f (op.pow, 2, List {3, 4})).to_be ((2 ^ 3) ^ 4) - - - context as an object method: - - before: - f = l.foldl - - - it writes a deprecation warning: - argstr = "require 'std.operator'.sum, 1" - expect (deprecate_on ("foldl", argstr, "{1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("foldl", argstr, "{1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it works with an empty List: - l = List {} - expect (f (l, op.sum, 2)).to_be (2) - - it folds a binary function through a List: - expect (f (l, op.sum, 2)).to_be (9) - - it folds from left to right: - expect (f (l, op.pow, 2)).to_be ((2 ^ 3) ^ 4) - - -- describe foldr: - - before: - op = require "std.operator" - l = List {10000, 100} - - - context as a module function: - - before: - f = M.foldr - - - it writes a deprecation warning: - argstr = "require 'std.operator'.sum, 1, P {1, 2, 5}" - expect (deprecate_on ("foldl", argstr)). - to_contain_error "was deprecated" - expect (deprecate_off ("foldl", argstr)). - not_to_contain_error "was deprecated" - - - context with a table: - - it works with an empty table: - expect (f (op.sum, 10000, {})).to_be (10000) - - it folds a binary function through a table: - expect (f (op.sum, 10000, {1, 10, 100})).to_be (10111) - - it folds from right to left: - expect (f (op.quot, 10, {10000, 100})).to_be (10000 / (100 / 10)) - - - context with a List: - - it works with an empty List: - expect (f (op.sum, 10000, List {})).to_be (10000) - - it folds a binary function through a List: - expect (f (op.sum, 10000, List {1, 10, 100})). - to_be (10111) - - it folds from right to left: - expect (f (op.quot, 10, List {10000, 100})). - to_be (10000 / (100 / 10)) - - - context as an object method: - - before: - f = l.foldr - - - it writes a deprecation warning: - argstr = "require 'std.operator'.sum, 1" - expect (deprecate_on ("foldr", argstr, "{1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("foldr", argstr, "{1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it works with an empty List: - l = List {} - expect (f (l, op.sum, 10)).to_be (10) - - it folds a binary function through a List: - expect (f (l, op.sum, 10)).to_be (10110) - - it folds from right to left: - expect (f (l, op.quot, 10)).to_be (10000 / (100 / 10)) - - -- describe index_key: - - context as a module function: - - before: - f = M.index_key - - - it writes a deprecation warning: - expect (deprecate_on ("index_key", "{1, P {{1}}}")). - to_contain_error "was deprecated" - expect (deprecate_off ("index_key", "{1, P {{1}}}")). - not_to_contain_error "was deprecated" - - - it makes a map of matched table field values to table List offsets: - l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} - t = f ("a", l) - expect (t).to_equal {b = 1, x = 3} - for k, v in pairs (t) do - expect (k).to_equal (l[v]["a"]) - end - - it captures only the last matching List offset: - l = List {{a = "b"}, {a = "x"}, {a = "b"}} - t = f ("a", l) - expect (t.b).not_to_be (1) - expect (t.x).to_be (2) - expect (t.b).to_be (3) - - it produces incomplete indices when faced with repeated matching table values: - l = List {{1, 2, 3}, {2}, {2, 1, 3, 2, 1}} - expect (f (1, l)).to_equal {1, 3} - expect (f (2, l)).to_equal {3, 1} - expect (f (3, l)).to_equal {nil, nil, 3} - - - context as an object method: - - before: - f = l.index_key - - - it writes a deprecation warning: - expect (deprecate_on ("index_key", 1, "P {1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("index_key", 1, "P {1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it makes a map of matched table field values to table List offsets: - l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} - t = l:index_key "a" - expect (t).to_equal {b = 1, x = 3} - for k, v in pairs (t) do - expect (k).to_equal (l[v]["a"]) - end - - it captures only the last matching List offset: - l = List {{a = "b"}, {a = "x"}, {a = "b"}} - t = l:index_key "a" - expect (t.b).not_to_be (1) - expect (t.x).to_be (2) - expect (t.b).to_be (3) - - it produces incomplete indices when faced with repeated matching table values: - l = List {{1, 2, 3}, {2}, {2, 1, 3, 2, 1}} - expect (l:index_key (1)).to_equal {1, 3} - expect (l:index_key (2)).to_equal {3, 1} - expect (l:index_key (3)).to_equal {nil, nil, 3} - - -- describe index_value: - - context as a module function: - - before: - f = M.index_value - - - it writes a deprecation warning: - expect (deprecate_on ("index_value", "{1, P {{1}}}")). - to_contain_error "was deprecated" - expect (deprecate_off ("index_value", "{1, P {{1}}}")). - not_to_contain_error "was deprecated" - - - it makes a table of matched table field values to table List references: - l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} - t = f ("a", l) - expect (t).to_equal {b = l[1], x = l[3]} - for k, v in pairs (t) do - expect (k).to_equal (v["a"]) - end - - it captures only the last matching List offset: - l = List {{a = "b"}, {a = "x"}, {a = "b"}} - t = f ("a", l) - expect (t.b).not_to_be (l[1]) - expect (t.x).to_be (l[2]) - expect (t.b).to_be (l[3]) - - it produces incomplete indices when faced with repeated matching table values: - l = List {{1, 2, 3}, {2}, {2, 1, 3, 2, 1}} - expect (f (1, l)).to_equal {l[1], l[3]} - expect (f (2, l)).to_equal {l[3], l[1]} - expect (f (3, l)).to_equal {nil, nil, l[3]} - - - context as an object method: - - before: - l = List {{1}} - - f = l.index_value - - - it writes a deprecation warning: - expect (deprecate_on ("index_value", 1, "P {1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("index_value", 1, "P {1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it makes a table of matched table field values to table List references: - l = List {{a = "b", c = "d"}, {e = "x", f = "g"}, {a = "x"}} - t = l:index_value "a" - expect (t).to_equal {b = l[1], x = l[3]} - for k, v in pairs (t) do - expect (k).to_equal (v["a"]) - end - - it captures only the last matching List offset: - l = List {{a = "b"}, {a = "x"}, {a = "b"}} - t = l:index_value "a" - expect (t.b).not_to_be (l[1]) - expect (t.x).to_be (l[2]) - expect (t.b).to_be (l[3]) - - it produces incomplete indices when faced with repeated matching table values: - l = List {{1, 2, 3}, {2}, {2, 1, 3, 2, 1}} - expect (l:index_value (1)).to_equal {l[1], l[3]} - expect (l:index_value (2)).to_equal {l[3], l[1]} - expect (l:index_value (3)).to_equal {nil, nil, l[3]} - - -- describe map: - - before: - l = List {1, 2, 3, 4, 5} - sq = function (n) return n * n end - - - context as a module function: - - before: - f, badarg = init (M, this_module, "map") - - - it writes a deprecation warning: - argstr = "function () return 1 end, P {1, 2, 5}" - expect (deprecate_on ("map", argstr)). - to_contain_error "was deprecated" - expect (deprecate_off ("map", argstr)). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (sq, l))).to_be "List" - - it works for an empty List: - expect (f (sq, List {})).to_equal (List {}) - - it creates a new List: - o = l - m = f (sq, l) - expect (l).to_equal (o) - expect (m).not_to_equal (o) - expect (l).to_equal (List {1, 2, 3, 4, 5}) - - it maps a function over a List: - expect (f (sq, l)).to_equal (List {1, 4, 9, 16, 25}) - - - context as an object method: - - before: - f = l.map - - - it writes a deprecation warning: - argstr = "function () return 1 end" - expect (deprecate_on ("map", argstr, "{1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("map", argstr, "{1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - m = f (l, sq) - expect (objtype (m)).to_be "List" - - it works for an empty List: - expect (f (List {}, sq)).to_equal (List {}) - - it creates a new List: - o = l - m = f (l, sq) - expect (l).to_equal (o) - expect (m).not_to_equal (o) - expect (l).to_equal (List {1, 2, 3, 4, 5}) - - it maps a function over a List: - expect (f (l, sq)).to_equal (List {1, 4, 9, 16, 25}) - - -- describe map_with: - - before: - l = List {List {1, 2, 3}, List {4, 5}} - fn = function (...) return select ("#", ...) end - - - context as a module function: - - before: - f = M.map_with - - - it writes a deprecation warning: - argstr = "function () return 1 end, P {P {1, 2}, P {5}}" - expect (deprecate_on ("map_with", argstr)). - to_contain_error "was deprecated" - expect (deprecate_off ("map_with", argstr)). - not_to_contain_error "was deprecated" - - - it returns a List object: - m = f (fn, l) - expect (objtype (m)).to_be "List" - - it creates a new List: - o = l - m = f (fn, l) - expect (l).to_equal (o) - expect (m).not_to_equal (o) - expect (l).to_equal (List {List {1, 2, 3}, List {4, 5}}) - - it maps a function over a List: - expect (f (fn, l)).to_equal (List {3, 2}) - - it works for an empty List: - l = List {} - expect (f (fn, l)).to_equal (List {}) - - -- describe project: - - before: - l = List { - {first = false, second = true, third = true}, - {first = 1, second = 2, third = 3}, - {first = "1st", second = "2nd", third = "3rd"}, - } - - - context as a module function: - - before: - f = M.project - - - it writes a deprecation warning: - argstr = "1, P {P {1, 2}, P {5}}" - expect (deprecate_on ("project", argstr)). - to_contain_error "was deprecated" - expect (deprecate_off ("project", argstr)). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f ("third", l))).to_be "List" - - it works with an empty List: - expect (f ("third", List {})).to_equal (List {}) - - it projects a List of fields from a List of tables: - expect (f ("third", l)).to_equal (List {true, 3, "3rd"}) - - it projects fields with a falsey value correctly: - expect (f ("first", l)).to_equal (List {false, 1, "1st"}) - - - context as an object method: - - before: - f = l.project - - - it writes a deprecation warning: - init = "{{1, 2}, {5}}" - expect (deprecate_on ("project", 1, init)). - to_contain_error "was deprecated" - expect (deprecate_off ("project", 1, init)). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l, "third"))).to_be "List" - - it works with an empty List: - expect (f (List {}, "third")).to_equal (List {}) - - it projects a List of fields from a List of tables: - expect (f (l, "third")).to_equal (List {true, 3, "3rd"}) - - it projects fields with a falsey value correctly: - expect (f (l, "first")).to_equal (List {false, 1, "1st"}) - - -- describe relems: - - context as a module function: - - before: - f = M.relems - - - it writes a deprecation warning: - expect (deprecate_on ("relems", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("relems", "{}")). - not_to_contain_error "was deprecated" - - - it is a reverse iterator over List members: - t = {} - for e in f (l) do table.insert (t, e) end - expect (t).to_equal {"baz", "bar", "foo"} - - it works for an empty List: - t = {} - for e in f (List {}) do table.insert (t, e) end - expect (t).to_equal {} - - - context as an object method: - - before: - f = l.relems - - - it writes a deprecation warning: - expect (deprecate_on ("relems", "", "{1, 2}")). - to_contain_error "was deprecated" - expect (deprecate_off ("relems", "", "{1, 2}")). - not_to_contain_error "was deprecated" - - - it is a reverse iterator over List members: - t = {} - for e in l:relems () do table.insert (t, e) end - expect (t).to_equal {"baz", "bar", "foo"} - - it works for an empty List: - t, l = {}, List {} - for e in l:relems () do table.insert (t, e) end - expect (t).to_equal {} - - - describe rep: - before: l = List {"foo", "bar"} @@ -890,119 +315,6 @@ specify std.list: to_equal (List {"foo", "bar", "foo", "bar", "foo", "bar"}) -- describe reverse: - - before: - l = List {"foo", "bar", "baz", "quux"} - - - context as a module function: - - before: - f = M.reverse - - - it writes a deprecation warning: - expect (deprecate_on ("reverse", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("reverse", "{}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l))).to_be "List" - - it works for an empty List: - l = List {} - expect (f (l)).to_equal (List {}) - - it makes a new reversed List: - m = l - expect (f (l)). - to_equal (List {"quux", "baz", "bar", "foo"}) - expect (l).to_equal (List {"foo", "bar", "baz", "quux"}) - expect (l).to_be (m) - - - context as an object method: - - before: - f = l.reverse - - - it writes a deprecation warning: - expect (deprecate_on ("reverse", "", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("reverse", "", "{}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l))).to_be "List" - - it works for an empty List: - expect (f (List {})).to_equal (List {}) - - it makes a new reversed List: - m = l - expect (f (l)). - to_equal (List {"quux", "baz", "bar", "foo"}) - expect (l).to_equal (List {"foo", "bar", "baz", "quux"}) - expect (l).to_be (m) - - -- describe shape: - - before: - l = List {1, 2, 3, 4, 5, 6} - - - context as a module function: - - before: - f = M.shape - - - it writes a deprecation warning: - expect (deprecate_on ("shape", "{0}, P {1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("shape", "{0}, P {1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f ({2, 3}, l))).to_be "List" - - it works for an empty List: - expect (f ({0}, List {})).to_equal (List {}) - - it returns the result in a new List object: - expect (f ({2, 3}, l)).not_to_be (l) - - it does not perturb the argument List: - f ({2, 3}, l) - expect (l).to_equal (List {1, 2, 3, 4, 5, 6}) - - it reshapes a List according to given dimensions: - expect (f ({2, 3}, l)). - to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) - expect (f ({3, 2}, l)). - to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - - it treats 0-valued dimensions as an indefinite number: - expect (f ({2, 0}, l)). - to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) - expect (f ({0, 2}, l)). - to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - - - context as an object method: - - before: - f = l.shape - - - it writes a deprecation warning: - expect (deprecate_on ("shape", "{0}", "{1, 2, 5}")). - to_contain_error "was deprecated" - expect (deprecate_off ("shape", "{0}", "{1, 2, 5}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l, {2, 3}))).to_be "List" - - it works for an empty List: - expect (f (List {}, {0})).to_equal (List {}) - - it returns the result in a new List object: - expect (f (l, {2, 3})):not_to_be (l) - - it does not perturb the argument List: - f (l, {2, 3}) - expect (l).to_equal (List {1, 2, 3, 4, 5, 6}) - - it reshapes a List according to given dimensions: - expect (f (l, {2, 3})). - to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) - expect (f (l, {3, 2})). - to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - - it treats 0-valued dimensions as an indefinite number: - expect (f (l, {2, 0})). - to_equal (List {List {1, 2, 3}, List {4, 5, 6}}) - expect (f (l, {0, 2})). - to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - - - describe sub: - before: l = List {1, 2, 3, 4, 5, 6, 7} @@ -1085,62 +397,6 @@ specify std.list: expect (f (List {1})).to_equal (List {}) -- describe transpose: - - before: - l = List {List {1, 2}, List {3, 4}, List {5, 6}} - - - context as a module function: - - before: - f = M.transpose - - - it writes a deprecation warning: - expect (deprecate_on ("transpose", "P {P {1, 2}, P {5, 9}}")). - to_contain_error "was deprecated" - expect (deprecate_off ("transpose", "P {P {1, 2}, P {5, 9}}")). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l))).to_be "List" - - it works for an empty List: - expect (f (List {})).to_equal (List {}) - - it returns the result in a new List object: - expect (f (l)).not_to_be (l) - - it does not perturb the argument List: - m = f (l) - expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5, 6}}) - - it transposes rows and columns: - expect (f (l)).to_equal (List {List {1, 3, 5}, List {2, 4, 6}}) - - -- describe zip_with: - - before: - l = List {List {1, 2}, List {3, 4}, List {5}} - fn = function (...) return tonumber (table.concat {...}) end - - - context as a module function: - - before: - f = M.zip_with - - - it writes a deprecation warning: - argstr = "function () return 0 end, P {P {1, 2}, P {5}}" - expect (deprecate_on ("transpose", argstr)). - to_contain_error "was deprecated" - expect (deprecate_off ("transpose", argstr)). - not_to_contain_error "was deprecated" - - - it returns a List object: - expect (objtype (f (l, fn))).to_be "List" - - it works for an empty List: - expect (f (List {}, fn)).to_equal (List {}) - - it returns the result in a new List object: - expect (f (l, fn)):not_to_be (l) - - it does not perturb the argument List: - m = f (l, fn) - expect (l).to_equal (List {List {1, 2}, List {3, 4}, List {5}}) - - it combines column entries with a function: - expect (f (l, fn)).to_equal (List {135, 24}) - - - describe __pickle: - before: loadstring = loadstring or load diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 022fa58..c7f01c4 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -9,7 +9,7 @@ before: "numbertosi", "ordinal_suffix", "pad", "pickle", "prettytostring", "render", "rtrim", "split", "tfind", "trim", "wrap" } - deprecations = { "assert", "require_version", "tostring" } + deprecations = {} setdebug { deprecate = false } @@ -66,39 +66,6 @@ specify std.string: expect (subject).to_be (original) -- describe assert: - - before: - f = M.assert - - - it writes a deprecation warning: - expect (deprecate_on ("assert", "true")). - to_contain_error "was deprecated" - expect (deprecate_off ("assert", "true")). - not_to_contain_error "was deprecated" - - - 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 caps: - before: f = M.caps @@ -501,9 +468,9 @@ specify std.string: between = "function () return ',' end" argstr = table.concat ({around, around, inside, inside, between}, ",") - expect (deprecate_on ("tostring", argstr)). + expect (deprecate_on ("render", argstr)). to_contain_error "was deprecated" - expect (deprecate_off ("tostring", argstr)). + expect (deprecate_off ("render", argstr)). not_to_contain_error "was deprecated" - context with bad arguments: @@ -545,62 +512,6 @@ specify std.string: to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") -- describe require_version: - - before: - f = M.require_version - - - it writes a deprecation warning: - expect (deprecate_on ("require_version", '"std.string"')). - to_contain_error "was deprecated" - expect (deprecate_off ("require_version", '"std.string"')). - not_to_contain_error "was deprecated" - - - 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 () - - it diagnoses module too new: - expect (f ("std", "0", "0")).to_raise () - - 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", "41")).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: - std = require "std" - std._VERSION, std.version = std.version, nil - expect (f ("std", "41", "9999")).to_be (require "std") - std._VERSION, std.version = nil, std._VERSION - - 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 () - expect (f ("std", "1.3")).to_raise () - expect (f ("std", "2.1.2")).to_raise () - expect (f ("std", "2")).to_raise () - expect (f ("std", "1.2.10")).to_raise () - - it diagnoses module too new: - expect (f ("std", nil, "1.2.2")).to_raise () - expect (f ("std", nil, "1.1")).to_raise () - expect (f ("std", nil, "1.1.2")).to_raise () - expect (f ("std", nil, "1")).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", "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) - - - describe rtrim: - before: subject = " \t\r\n a short string \t\r\n " @@ -699,38 +610,6 @@ specify std.string: expect (subject).to_be (original) -- describe tostring: - - before: - f = M.tostring - - - it writes a deprecation warning: - expect (deprecate_on ("tostring", 42)). - to_contain_error "was deprecated" - expect (deprecate_off ("tostring", 42)). - not_to_contain_error "was deprecated" - - - it renders primitives exactly like system tostring: - expect (f (nil)).to_be (tostring (nil)) - expect (f (false)).to_be (tostring (false)) - expect (f (42)).to_be (tostring (42)) - expect (f (f)).to_be (tostring (f)) - expect (f "a string").to_be "a string" - - it renders empty tables as a pair of braces: - expect (f {}).to_be ("{}") - - it renders table array part compactly: - expect (f {"one", "two", "five"}). - to_be '{one,two,five}' - - it renders a table dictionary part compactly: - expect (f { one = true, two = 2, three = {3}}). - to_be '{one=true,three={3},two=2}' - - it renders table keys in table.sort order: - expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }). - to_be '{five=1,four=2,one=3,three=4,two=5}' - - it renders keys with invalid symbol names compactly: - expect (f { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }). - to_be '{?=1,[]=1,_=0,a-key=1,word=0}' - - - describe trim: - before: subject = " \t\r\n a short string \t\r\n " diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 606ae2e..246d375 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -8,8 +8,7 @@ before: | "merge", "merge_select", "monkey_patch", "new", "pack", "project", "remove", "size", "sort", "unpack", "values" } - deprecations = { "flatten", "len", "okeys", "metamethod", "ripairs", - "shape", "totable" } + deprecations = { "flatten", "len", "okeys", "shape" } setdebug { deprecate = false } @@ -184,9 +183,9 @@ specify std.table: f = M.flatten - it writes a deprecation warning: - expect (deprecate_on ("ripairs", "{}")). + expect (deprecate_on ("flatten", "{}")). to_contain_error "was deprecated" - expect (deprecate_off ("ripairs", "{}")). + expect (deprecate_off ("flatten", "{}")). not_to_contain_error "was deprecated" - it returns a table: @@ -434,31 +433,6 @@ specify std.table: expect (getmetatable (f ({}, t1mt, {"k1"}, ":nometa"))).to_be (nil) -- describe metamethod: - - before: - Object = require "std.object" - objmethod = function () end - obj = Object { - _type = "DerivedObject", - _method = objmethod, - } - - f = M.metamethod - - - it writes a deprecation warning: - expect (deprecate_on ("metamethod", "{}, 'subject'")). - to_contain_error "was deprecated" - expect (deprecate_off ("metamethod", "{}, 'subject'")). - not_to_contain_error "was deprecated" - - - it returns nil for missing metamethods: - expect (f (obj, "not a method on obj")).to_be (nil) - - it returns nil for non-function metatable entries: - expect (f (obj, "_type")).to_be (nil) - - it returns a method from the metatable: - expect (f (obj, "_method")).to_be (objmethod) - - - describe monkey_patch: - before: f = M.monkey_patch @@ -615,29 +589,6 @@ specify std.table: expect (t).to_equal {1, 2, 5, 42} -- describe ripairs: - - before: - f = M.ripairs - - - it writes a deprecation warning: - expect (deprecate_on ("ripairs", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("ripairs", "{}")). - not_to_contain_error "was deprecated" - - - 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 a 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"} - - - describe shape: - before: l = {1, 2, 3, 4, 5, 6} @@ -705,29 +656,6 @@ specify std.table: expect (f (subject, cmp)).to_equal (target) -- describe totable: - - before: - t = {"one", "two", "five"} - mt = { _type = "MockObject", - __totable = function (self) return self.content end } - - f = M.totable - - - it writes a deprecation warning: | - expect (deprecate_on ("totable", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("totable", "{}")). - not_to_contain_error "was deprecated" - - - it calls object's __totable metamethod: - object = setmetatable ({content = t}, mt) - expect (f (object)).to_be (t) - - it returns a table with no __totable metamethod unchanged: - t = {content = t} - object = setmetatable (t, { _type = "Thing" }) - expect (f (object)).to_be (t) - - - describe unpack: - before: t = {"one", "two", "five"} From 11e6bbdef193138cf9d6a38e54e763f4e811553f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 6 Jan 2016 23:31:21 +0000 Subject: [PATCH 648/703] refactor: move npairs and rnpairs from base to std itself. * lib/std/base.lua (npairs, rnpairs): Move from here... * lib/std/init.lua (npairs, rnpairs): ...to here. And import maxn from base.table so that they work. * lib/std/functional.lua (npairs): Remove unused import. * specs/functional_spec.yaml (before): Roll our own npairs, rather than being coupled to the newly arrived std.npairs. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 26 -------------------------- lib/std/functional.lua | 1 - lib/std/init.lua | 27 +++++++++++++++++++++++++-- specs/functional_spec.yaml | 18 ++++++++++++++---- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 8742154..bd6c6df 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -315,17 +315,6 @@ local function Module (t) 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 pack = table_pack or function (...) return {n = select ("#", ...), ...} end @@ -519,19 +508,6 @@ local function ripairs (t) 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 function _setfenv (fn, env) -- Unwrap functable: if type (fn) == "table" then @@ -641,10 +617,8 @@ return { getmetamethod = getmetamethod, ielems = ielems, ipairs = ipairs, - npairs = npairs, pairs = pairs, ripairs = ripairs, - rnpairs = rnpairs, tostring = function (x) return render (x, tostring_vtable) end, diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 8042610..ef6c19d 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -35,7 +35,6 @@ local len = _.std.operator.len local merge = _.std.base.merge local mnemonic = _.std.base.mnemonic local nop = _.std.functional.nop -local npairs = _.std.npairs local pack = _.std.table.pack local reduce = _.std.functional.reduce local render = _.std.string.render diff --git a/lib/std/init.lua b/lib/std/init.lua index 5bdd64e..231d56b 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -51,10 +51,9 @@ local elems = _.std.elems local eval = _.std.eval local getmetamethod = _.std.getmetamethod local ielems = _.std.ielems +local maxn = _.std.table.maxn local merge = _.std.base.merge -local npairs = _.std.npairs local ripairs = _.std.ripairs -local rnpairs = _.std.rnpairs local split = _.std.string.split local deprecated = require "std.delete-after.a-year" @@ -119,6 +118,30 @@ local function barrel (namespace) 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 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 function vcompare (a, b) return compare (split (a, "%."), split (b, "%.")) end diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 7886107..ae5ff49 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -14,6 +14,16 @@ before: deprecate_on = bind (deprecation, {"nil", this_module}) deprecate_off = bind (deprecation, {false, this_module}) + function npairs (t) + local m = base.getmetamethod (t, "__len") + local i, n = 0, m and m(t) or base.table.maxn (t) + return function (t) + i = i + 1 + if i <= n then return i, t[i] end + end, + t, i + end + function iter (...) local l = pack (...) local oob = l.n + 1 @@ -279,7 +289,7 @@ specify std.functional: expect (f (M.id, base.elems, inverse)).to_contain.a_permutation_of {1, 2, 3, 4, 5} - it propagates nil arguments correctly: t = {"a", nil, nil, "d", "e"} - expect (f (M.id, base.npairs, t)).to_equal (t) + expect (f (M.id, npairs, t)).to_equal (t) expect (f (M.id, iter, nil, nil, 3, 4, nil, nil)). to_equal {false, false, 3, 4, false, false} - it passes all iteration result values to filter predicate: @@ -468,7 +478,7 @@ specify std.functional: expect (f (M.id, pairs, inverse)).to_contain.a_permutation_of (elements) - it propagates nil arguments correctly: t = {"a", nil, nil, "d", "e"} - expect (f (M.id, base.npairs, t)).to_equal (t) + expect (f (M.id, npairs, t)).to_equal (t) expect (f (M.id, iter, nil, nil, 3, 4, nil, nil)). to_equal {false, false, 3, 4, false, false} - it passes all iteration result values to map function: @@ -645,9 +655,9 @@ specify std.functional: expect (f (op.prod, 2, base.ielems, {3, 4})).to_be (2 * 3 * 4) - it propagates nil arguments correctly: function set (t, k, v) t[k] = tostring (v) return t end - expect (f (set, {}, base.npairs, {1, nil, nil, "a", false})). + expect (f (set, {}, npairs, {1, nil, nil, "a", false})). to_equal {"1", "nil", "nil", "a", "false"} - expect (f (set, {}, base.npairs, {nil, nil, "3"})). + expect (f (set, {}, npairs, {nil, nil, "3"})). to_equal {"nil", "nil", "3"} expect (f (set, {}, iter, nil, nil, 3, 4, nil, nil)). to_equal {"false", "false", "3", "4", "false", "false"} From 491b97f33b06e94cd75938df45582071a2a6f280 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 7 Jan 2016 00:13:13 +0000 Subject: [PATCH 649/703] refactor: move elems from base to std itself. * lib/std/base.lua (wrapiterator): Remove by manually inlining... (ielems): ...into here... (elems): ...and here. Move from here... * lib/std/init.lua (elems): ...to here. * specs/functional_spec.yaml (before): Roll our own elems, rather than being coupled to the newly arrived std.elems. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 37 ++++++++----------------------------- lib/std/init.lua | 12 +++++++++++- specs/functional_spec.yaml | 14 ++++++++++++-- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index bd6c6df..30f69a9 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -160,32 +160,6 @@ local function copy (dest, src) end ---- Iterator adaptor for discarding first value from core iterator function. --- @func factory iterator to be wrapped --- @param ... *factory* arguments --- @treturn function iterator that discards first returned value of --- factory iterator --- @return invariant state from *factory* --- @return `true` --- @usage --- for v in wrapiterator (ipairs {"a", "b", "c"}) do process (v) end -local function wrapiterator (factory, ...) - -- Capture wrapped ctrl variable into an upvalue... - local fn, istate, ctrl = factory (...) - -- Wrap the returned iterator fn to maintain wrapped ctrl. - return function (state, _) - local v - ctrl, v = fn (state, ctrl) - if ctrl then return v end - end, istate, true -- wrapped initial state, and wrapper ctrl -end - - -local function elems (t) - return wrapiterator (pairs, t) -end - - local function escape_pattern (s) return (s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) end @@ -227,8 +201,14 @@ local function _getfenv (fn) end -local function ielems (l) - return wrapiterator (ipairs, l) +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 @@ -612,7 +592,6 @@ end -- public API here too, which means everything looks relatively normal -- when importing the functions into stdlib implementation modules. return { - elems = elems, eval = eval, getmetamethod = getmetamethod, ielems = ielems, diff --git a/lib/std/init.lua b/lib/std/init.lua index 231d56b..d309780 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -47,7 +47,6 @@ local _tostring = _.std.tostring local argscheck = _.typing.argscheck local compare = _.std.list.compare local copy = _.std.base.copy -local elems = _.std.elems local eval = _.std.eval local getmetamethod = _.std.getmetamethod local ielems = _.std.ielems @@ -118,6 +117,17 @@ local function barrel (namespace) 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 npairs (t) local m = getmetamethod (t, "__len") local i, n = 0, m and m(t) or maxn (t) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index ae5ff49..1598aaa 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -14,6 +14,16 @@ before: deprecate_on = bind (deprecation, {"nil", this_module}) deprecate_off = bind (deprecation, {false, this_module}) + + function elems (t) + local fn, istate, ctrl = base.pairs (t) + return function (state, _) + local v + ctrl, v = fn (state, ctrl) + if ctrl then return v end + end, istate, true + end + function npairs (t) local m = base.getmetamethod (t, "__len") local i, n = 0, m and m(t) or base.table.maxn (t) @@ -286,7 +296,7 @@ specify std.functional: expect (f (M.id, pairs, {})).to_equal {} - it iterates through element keys: expect (f (M.id, base.ielems, elements)).to_equal {"a", "b", "c", "d", "e"} - expect (f (M.id, base.elems, inverse)).to_contain.a_permutation_of {1, 2, 3, 4, 5} + expect (f (M.id, elems, inverse)).to_contain.a_permutation_of {1, 2, 3, 4, 5} - it propagates nil arguments correctly: t = {"a", nil, nil, "d", "e"} expect (f (M.id, npairs, t)).to_equal (t) @@ -488,7 +498,7 @@ specify std.functional: - it returns a list of mapped single return value iterator results: expect (f (function (e) return e:match "[aeiou]" end, base.ielems, elements)). to_equal {"a", "e"} - expect (f (function (e) return e .. "x" end, base.elems, elements)). + expect (f (function (e) return e .. "x" end, elems, elements)). to_contain.a_permutation_of {"ax", "bx", "cx", "dx", "ex"} - it returns a table of mapped key:value iterator results: t = {"first", second=2, last="three"} From a612d5e25e4d3f3a003d2f306497bfd01c8f1948 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 8 Jan 2016 10:16:39 +0000 Subject: [PATCH 650/703] maint: remove unused code from std.functional. * lib/std/functional.lua (copy, merge, render, deprecated): Remove unused declarations. Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index ef6c19d..20ad1cf 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -29,21 +29,16 @@ local _ipairs = _.std.ipairs local _pairs = _.std.pairs local argscheck = _.typing.argscheck local callable = _.std.functional.callable -local copy = _.std.base.copy local ielems = _.std.ielems local len = _.std.operator.len -local merge = _.std.base.merge local mnemonic = _.std.base.mnemonic local nop = _.std.functional.nop local pack = _.std.table.pack local reduce = _.std.functional.reduce -local render = _.std.string.render local leaves = _.std.tree.leaves local unpack = _.std.table.unpack -local deprecated = nil - local _, _ENV = nil, _.strict {} @@ -788,11 +783,6 @@ local M = { } -if deprecated then - M = merge (M, deprecated.functional) -end - - return M From 7409b1eb85270a1096ed4a04af7e18eb4670a329 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 8 Jan 2016 22:19:48 +0000 Subject: [PATCH 651/703] tuple: use tuple.unpack for tuples, table.unpack for tables. * specs/tuple_spec.yaml (unpack): Remove examples of using table.unpack. Add examples of behaviours with tuple.unpack module function and with unpack object method. * lib/std/tuple.lua (unpack): New tuple specific unpack function. (tuple:__index): Return tuple unpack when lookup key is "unpack". (tuple:__unpack): Remove. Signed-off-by: Gary V. Vaughan --- lib/std/tuple.lua | 32 ++++++++++++--------- specs/tuple_spec.yaml | 67 +++++++++++++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 19fa5f5..1c47a29 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -4,7 +4,7 @@ An interned, immutable, nil-preserving tuple object. Like Lua strings, tuples with the same elements can be quickly compared - with a straight forward `==` comparison. The `prototype` field in the + with a straight-forward `==` comparison. The `prototype` field in the returned module table is the empty tuple, which can be cloned to create tuples with other contents. @@ -31,6 +31,7 @@ local getmetatable = getmetatable local next = next local select = select local setmetatable = setmetatable +local tonumber = tonumber local type = type local string_format = string.format @@ -77,6 +78,20 @@ local intern = setmetatable ({}, { }) +--- Unpack tuple values between index *i* and *j*, inclusive. +-- @function unpack +-- @int[opt=1] i first index to unpack +-- @int[opt=t.n] j last index to unpack +-- @return ... values at indices *i* through *j*, inclusive +-- @usage +-- t = Tuple (1, 3, 2, 5) +-- --> 3, 2, 5 +-- tuple.unpack (t, 2) +local function unpack (t, i, j) + return table_unpack (next (t), tonumber (i) or 1, tonumber (j) or t.n) +end + + --[[ ================== ]]-- --[[ Type Declarations. ]]-- @@ -112,6 +127,7 @@ local prototype = Container { -- @section metamethods __index = function (self, k) + if k == "unpack" then return unpack end return next (self) [k] end, @@ -147,19 +163,6 @@ local prototype = Container { return string_format ("%s (%s)", getmetatable (self)._type, argstr) end, - --- Unpack tuple values between index *i* and *j*, inclusive. - -- @function prototype:__unpack - -- @int[opt=1] i first index to unpack - -- @int[opt=len(t)] j last index to unpack - -- @return ... values at indices *i* through *j*, inclusive - -- @usage - -- t = Tuple (1, 3, 2, 5) - -- --> 3, 2, 5 - -- table.unpack (t, 2) - __unpack = function (self, i, j) - return table_unpack (next (self), i, j) - end, - --- Return a loadable serialization of this object, where possible. -- @function prototype:__pickle -- @treturn string pickled object representataion @@ -183,4 +186,5 @@ local prototype = Container { return Module { prototype = prototype, + unpack = unpack, } diff --git a/specs/tuple_spec.yaml b/specs/tuple_spec.yaml index ac1d8a8..629c255 100644 --- a/specs/tuple_spec.yaml +++ b/specs/tuple_spec.yaml @@ -79,9 +79,15 @@ specify std.tuple: - describe immutability: - - it diagnoses mutation attempts: - function fn () t2[1] = 1 end - expect (fn ()).to_raise "cannot change immutable tuple object" + - before: + fn = function (t, k, v) t[k] = v end + + - it diagnoses attempts to mutate contents: + expect (fn (t2, 1, 1)).to_raise "cannot change immutable tuple object" + - it diagnoses attempts to mutate methods: + expect (fn (t2, "unpack", nil)).to_raise "cannot change immutable tuple object" + - it diagnoses attempts to add new values: + expect (fn (t2, 0, "oob")).to_raise "cannot change immutable tuple object" - describe traversing: @@ -106,24 +112,43 @@ specify std.tuple: - describe unpacking: - - before: - unpack = require "std.table".unpack - - collect = function (t) - local r = {unpack (t)} - for i = 1, t.n do r[i] = tostring (r[i]) end - return table.concat (r, ",") - end - - - it returns all elements: - expect (collect (Tuple ("a", "b", "c"))).to_be "a,b,c" - - it works with 0-tuple: - expect (collect (Tuple ())).to_be "" - - it understands nil elements: - expect (collect (Tuple (nil))).to_be "nil" - expect (collect (Tuple (false, nil))).to_be "false,nil" - expect (collect (Tuple (nil, false))).to_be "nil,false" - expect (collect (Tuple (nil, nil))).to_be "nil,nil" + - context with module function: + - before: + unpack = require "std.tuple".unpack + + collect = function (t) + local r = {unpack (t)} + for i = 1, t.n do r[i] = tostring (r[i]) end + return table.concat (r, ",") + end + + - it returns all elements: + expect (collect (Tuple ("a", "b", "unpack"))).to_be "a,b,unpack" + - it works with 0-tuple: + expect (collect (Tuple ())).to_be "" + - it understands nil elements: + expect (collect (Tuple (nil))).to_be "nil" + expect (collect (Tuple (false, nil))).to_be "false,nil" + expect (collect (Tuple (nil, false))).to_be "nil,false" + expect (collect (Tuple (nil, nil))).to_be "nil,nil" + + - context with object method: + - before: + collect = function (t) + local r = {t:unpack ()} + for i = 1, t.n do r[i] = tostring (r[i]) end + return table.concat (r, ",") + end + + - it returns all elements: + expect (collect (Tuple ("a", "b", "unpack"))).to_be "a,b,unpack" + - it works with 0-tuple: + expect (collect (Tuple ())).to_be "" + - it understands nil elements: + expect (collect (Tuple (nil))).to_be "nil" + expect (collect (Tuple (false, nil))).to_be "false,nil" + expect (collect (Tuple (nil, false))).to_be "nil,false" + expect (collect (Tuple (nil, nil))).to_be "nil,nil" - describe __tostring: From 18ec33f2d3ccdb38d674561e1eddc0b6b96be8be Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 9 Jan 2016 12:39:15 +0000 Subject: [PATCH 652/703] list: remove unused code from std.list. * lib/std/list.lua (math_ceil, math_max, merge, deprecated) (unpack): Remove. (table_unpack): Import Lua's own table.unpack C function. (cons): Pass all arguments to table_unpack to avoid differences between various implementation semantics. (prototype): Rename this... (List): ...to this. Signed-off-by: Gary V. Vaughan --- lib/std/list.lua | 45 ++++++++++----------------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/lib/std/list.lua b/lib/std/list.lua index 1da1333..4336450 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -17,8 +17,7 @@ ]] -local math_ceil = math.ceil -local math_max = math.max +local table_unpack = table.unpack or unpack local _ = { @@ -36,12 +35,8 @@ local _pairs = _.std.pairs local argscheck = _.typing.argscheck local compare = _.std.list.compare local len = _.std.operator.len -local merge = _.std.base.merge -local unpack = _.std.table.unpack -local deprecated = nil - local _, _ENV = nil, _.strict {} @@ -51,7 +46,7 @@ local _, _ENV = nil, _.strict {} --[[ ================= ]]-- -local prototype +local List local function append (l, x) @@ -62,7 +57,7 @@ end local function concat (l, ...) - local r = prototype {} + local r = List {} for _, e in _ipairs {l, ...} do for _, v in _ipairs (e) do r[#r + 1] = v @@ -73,7 +68,7 @@ end local function rep (l, n) - local r = prototype {} + local r = List {} for i = 1, n do r = concat (r, l) end @@ -82,7 +77,7 @@ end local function sub (l, from, to) - local r = prototype {} + local r = List {} local lenl = len (l) from = from or 1 to = to or lenl @@ -148,7 +143,7 @@ local methods = { -- @usage -- --> List {"x", 1, 2, 3} -- consed = (List {1, 2, 3}):cons "x" - cons = X ("cons (List, any)", function (l, x) return prototype {x, unpack (l)} end), + cons = X ("cons (List, any)", function (l, x) return List {x, table_unpack (l, 1, len (l))} end), --- Repeat a list. -- @function prototype:rep @@ -190,7 +185,7 @@ local methods = { -- @usage -- local List = require "std.list".prototype -- assert (std.type (List) == "List") -local List = { +List = Object { _type = "std.list.List", --- Metamethods @@ -232,28 +227,8 @@ local List = { } --- Lots of scope to tidy and simplify once we don't need to merge in the --- deprecated functions below. -local M = {} - -if deprecated then - local function bindfns (dest, src) - for k, v in _pairs (src) do - dest[k] = dest[k] or function (...) return v (prototype, ...) end - end - return dest - end - - methods = bindfns (methods, deprecated.methods.list) - M = bindfns (M, deprecated.list) -end - - -prototype = Object (List) - - -return Module (merge ({ - prototype = prototype, +return Module { + prototype = List, append = methods.append, compare = methods.compare, @@ -262,4 +237,4 @@ return Module (merge ({ rep = methods.rep, sub = methods.sub, tail = methods.tail, -}, M)) +} From 7179061165c7d42ff118995287827c4eeee87c79 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 9 Jan 2016 14:14:35 +0000 Subject: [PATCH 653/703] refactor: move unpack from std.base to std.table. More simplification for marginal speed increases, and better separation of concerns between modules. * specs/table_spec.yaml (unpack): New examples of prioritizing the result of __len metamethod over calling maxn when the third argument to unpack is not given. * lib/std/base.lua (unpack): Move from here... * lib/std/table.lua (unpack): ...to here. Remove support for __unpack metamethod, no longer used by std.tuple. * lib/std/base (reduce): Use table.unpack C function with all arguments. * lib/std/delete-after/a-year.lua (table_pack): Bring our own implementation. (collect): Use table.unpack C function with all arguments. (result_pack, result_unpack): Remove. (argcheck, argerror, argscheck, resulterror): Simplify accordingly. * lib/std/functional.lua (any, bind, compose, filter, memoize) (map, map_witth, _product): Use table.unpack C function with all arguments. * lib/std/maturity.lua (result_unpack): Remove. (DEPRECATED): Use table.unpack C function with all arguments. * lib/std/package.lua (insert): Likewise. * lib/std/typing.lua (argcheck): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 24 +++++------------ lib/std/delete-after/a-year.lua | 36 +++++++++++-------------- lib/std/functional.lua | 48 ++++++++++++++++----------------- lib/std/maturity.lua | 7 +---- lib/std/package.lua | 5 ++-- lib/std/table.lua | 14 +++++++++- lib/std/typing.lua | 4 +-- specs/table_spec.yaml | 6 +++++ 8 files changed, 70 insertions(+), 74 deletions(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index 30f69a9..d790c9f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -300,17 +300,6 @@ local pack = table_pack or function (...) end -local function unpack (t, i, j) - if j == nil then - -- respect __len, and then maxn if nil j was passed - local m = getmetamethod (t, "__len") - j = m and m (t) or maxn (t) - end - local fn = getmetamethod (t, "__unpack") or table_unpack - return fn (t, tonumber (i) or 1, tonumber (j)) -end - - local function reduce (fn, d, ifn, ...) local argt if not callable (ifn) then @@ -319,14 +308,14 @@ local function reduce (fn, d, ifn, ...) argt = pack (...) end - local nextfn, state, k = ifn (unpack (argt, 1, argt.n)) - local t = {nextfn (state, k)} -- table of iteration 1 + local nextfn, state, k = ifn (table_unpack (argt, 1, argt.n)) + local t = pack (nextfn (state, k)) -- table of iteration 1 - local r = d -- initialise accumulator - while t[1] ~= nil do -- until iterator returns nil + local r = d -- initialise accumulator + while t[1] ~= nil do -- until iterator returns nil k = t[1] - r = fn (r, unpack (t)) -- pass all iterator results to fn - t = {nextfn (state, k)} -- maintain loop invariant + r = fn (r, table_unpack (t, 1, t.n)) -- pass all iterator results to fn + t = pack (nextfn (state, k)) -- maintain loop invariant end return r end @@ -653,7 +642,6 @@ return { invert = invert, maxn = maxn, pack = pack, - unpack = unpack, }, tree = { diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index a9a0ee7..72c8817 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -42,6 +42,9 @@ if not require "std.debug_init"._DEBUG.deprecate then local string_match = string.match local table_concat = table.concat local table_insert = table.insert + local table_pack = table.pack or function (...) + return {n = select ("#", ...), ...} + end local table_remove = table.remove local table_sort = table.sort local table_unpack = table.unpack or unpack @@ -75,7 +78,6 @@ if not require "std.debug_init"._DEBUG.deprecate then local pack = _.std.table.pack local sortkeys = _.std.base.sortkeys local split = _.std.string.split - local unpack = _.std.table.unpack -- Only the above symbols are used below this line. local _, _ENV = nil, _.strict {} @@ -527,7 +529,7 @@ if not require "std.debug_init"._DEBUG.deprecate then diagnose (results, output) end - return unpack (results) + return table_unpack (results, 1, results.n) end end @@ -543,11 +545,11 @@ if not require "std.debug_init"._DEBUG.deprecate then local function collect (ifn, ...) - local argt, r = {...}, {} + local argt, r = pack (...), {} -- How many return values from ifn? local arity = 1 - for e, v in ifn (table_unpack (argt)) do + for e, v in ifn (table_unpack (argt, 1, argt.n)) do if v then arity, r = 2, {} break end -- Build an arity-1 result table on first pass... r[#r + 1] = e @@ -555,7 +557,7 @@ if not require "std.debug_init"._DEBUG.deprecate then if arity == 2 then -- ...oops, it was arity-2 all along, start again! - for k, v in ifn (table_unpack (argt)) do + for k, v in ifn (table_unpack (argt, 1, argt.n)) do r[k] = v end end @@ -638,14 +640,6 @@ if not require "std.debug_init"._DEBUG.deprecate then return DEPRECATED (RELEASE, "'std.debug." .. base .. "'", "use 'std.argcheck." .. base .. "' instead", fn) or nil end - local function result_pack (...) - return {n = select ("#", ...), ...} - end - - local function result_unpack (v) - return table_unpack (v, 1, v.n) - end - local function acyclic_merge (dest, src) for k, v in pairs (src) do if type (v) == "table" then @@ -664,23 +658,23 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Add 2 to the level, this anonymous function and XX, being -- careful not to let tail call elimination remove a stack -- frame: - local r = result_pack (argcheck (name, i, expected, actual, (level or 1) + 2)) - return result_unpack (r) + local r = table_pack (argcheck (name, i, expected, actual, (level or 1) + 2)) + return table_unpack (r, 1, r.n) end), argerror = XX ("argerror", function (name, i, extramsg, level) - local r = result_pack (argerror (name, i, extramsg, (level or 1) + 2)) - return result_unpack (r) + local r = table_pack (argerror (name, i, extramsg, (level or 1) + 2)) + return table_unpack (r, 1, r.n) end), argscheck = XX ("argscheck", argscheck), extramsg_mismatch = XX ("extramsg_mismatch", function (expected, actual, index) - local r = result_pack (extramsg_mismatch (typesplit (expected), actual, index)) - return result_unpack (r) + local r = table_pack (extramsg_mismatch (typesplit (expected), actual, index)) + return table_unpack (r, 1, r.n) end), extramsg_toomany = XX ("extramsg_toomany", extramsg_toomany), parsetypes = XX ("parsetypes", parsetypes), resulterror = XX ("resulterror", function (name, i, extramsg, level) - local r = result_pack (resulterror (name, i, extramsg, (level or 1) + 2)) - return result_unpack (r) + local r = table_pack (resulterror (name, i, extramsg, (level or 1) + 2)) + return table_unpack (r, 1, r.n) end), typesplit = XX ("typesplit", typesplit), }, diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 20ad1cf..87b9017 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -17,6 +17,7 @@ local type = type local math_ceil = math.ceil local table_remove = table.remove +local table_unpack = table.unpack or unpack local _ = { @@ -36,7 +37,6 @@ local nop = _.std.functional.nop local pack = _.std.table.pack local reduce = _.std.functional.reduce local leaves = _.std.tree.leaves -local unpack = _.std.table.unpack local _, _ENV = nil, _.strict {} @@ -56,10 +56,10 @@ local function any (...) for i = 1, fns.n do argt = pack (fns[i] (...)) if argt[1] ~= nil then - return unpack (argt, 1, argt.n) + return table_unpack (argt, 1, argt.n) end end - return unpack (argt, 1, argt.n) + return table_unpack (argt, 1, argt.n) end end @@ -86,10 +86,10 @@ local function bind (fn, bound) end -- Even if there are gaps remaining above *i*, pass at least *n* args. - if n >= i then return fn (unpack (argt, 1, n)) end + if n >= i then return fn (table_unpack (argt, 1, n)) end -- Otherwise, we filled gaps beyond *n*, and pass that many args. - return fn (unpack (argt, 1, i - 1)) + return fn (table_unpack (argt, 1, i - 1)) end end @@ -141,9 +141,9 @@ local function compose (...) return function (...) local argt = pack (...) for i = 1, fns.n do - argt = pack (fns[i] (unpack (argt, 1, argt.n))) + argt = pack (fns[i] (table_unpack (argt, 1, argt.n))) end - return unpack (argt, 1, argt.n) + return table_unpack (argt, 1, argt.n) end end @@ -179,22 +179,22 @@ local function filter (pfn, ifn, ...) ifn, argt = _pairs, pack (ifn, ...) end - local nextfn, state, k = ifn (unpack (argt, 1, argt.n)) + local nextfn, state, k = ifn (table_unpack (argt, 1, argt.n)) - local t = {nextfn (state, k)} -- table of iteration 1 - local arity = #t -- How many return values from ifn? + local t = pack (nextfn (state, k)) -- table of iteration 1 + local arity = #t -- How many return values from ifn? if arity == 1 then local v = t[1] - while v ~= nil do -- until iterator returns nil - if pfn (unpack (t)) then -- pass all iterator results to p + while v ~= nil do -- until iterator returns nil + if pfn (table_unpack (t, 1, t.n)) then -- pass all iterator results to p r[#r + 1] = v end - t = {nextfn (state, v)} -- maintain loop invariant + t = pack (nextfn (state, v)) -- maintain loop invariant v = t[1] - if #t > 1 then -- unless we discover arity is not 1 after all + if #t > 1 then -- unless we discover arity is not 1 after all arity, r = #t, {} break end end @@ -207,8 +207,8 @@ local function filter (pfn, ifn, ...) -- current t with arity > 1 is the correct next value to use while t[1] ~= nil do local k = t[1] - if pfn (unpack (t)) then r[k] = t[2] end - t = {nextfn (state, k)} + if pfn (table_unpack (t, 1, t.n)) then r[k] = t[2] end + t = pack (nextfn (state, k)) end end @@ -272,7 +272,7 @@ local function memoize (fn, mnemonic) t = pack (fn (...)) self[k] = t end - return unpack (t, 1, t.n) + return table_unpack (t, 1, t.n) end }) end @@ -325,12 +325,12 @@ local function map (mapfn, ifn, ...) ifn, argt = _pairs, pack (ifn, ...) end - local nextfn, state, k = ifn (unpack (argt, 1, argt.n)) - local mapargs = {nextfn (state, k)} + local nextfn, state, k = ifn (table_unpack (argt, 1, argt.n)) + local mapargs = pack (nextfn (state, k)) local arity = 1 while mapargs[1] ~= nil do - local d, v = mapfn (unpack (mapargs)) + local d, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) if v ~= nil then arity, r = 2, {} break end @@ -344,9 +344,9 @@ local function map (mapfn, ifn, ...) -- (ii) arity used to be 1, but we only consumed nil values, so the -- current mapargs with arity > 1 is the correct next value to use while mapargs[1] ~= nil do - local k, v = mapfn (unpack (mapargs)) + local k, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) r[k] = v - mapargs = {nextfn (state, mapargs[1])} + mapargs = pack (nextfn (state, mapargs[1])) end end return r @@ -356,7 +356,7 @@ end local function map_with (mapfn, tt) local r = {} for k, v in _pairs (tt) do - r[k] = mapfn (unpack (v)) + r[k] = mapfn (table_unpack (v, 1, len (v))) end return r end @@ -366,7 +366,7 @@ local function _product (x, l) local r = {} for v1 in ielems (x) do for v2 in ielems (l) do - r[#r + 1] = {v1, unpack (v2)} + r[#r + 1] = {v1, table_unpack (v2, 1, len (v2))} end end return r diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua index fc1551d..11eaa51 100644 --- a/lib/std/maturity.lua +++ b/lib/std/maturity.lua @@ -71,11 +71,6 @@ local function result_pack (...) end -local function result_unpack (v) - return table_unpack (v, 1, v.n) -end - - local function DEPRECATED (version, name, extramsg, fn) if fn == nil then fn, extramsg = extramsg, nil end @@ -87,7 +82,7 @@ local function DEPRECATED (version, name, extramsg, fn) -- would lose a stack frame and change the `level` argument -- required for frame counting functions, so we do this instead: local r = result_pack (fn (...)) - return result_unpack (r) + return table_unpack (r, 1, r.n) end end end diff --git a/lib/std/package.lua b/lib/std/package.lua index dce30b0..4e57892 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -44,6 +44,7 @@ local string_match = string.match local table_concat = table.concat local table_insert = table.insert local table_remove = table.remove +local table_unpack = table.unpack local _ = { @@ -56,9 +57,9 @@ local argscheck = _.typing.argscheck local catfile = _.std.io.catfile local escape_pattern = _.std.string.escape_pattern local invert = _.std.table.invert +local len = _.std.operator.len local merge = _.std.base.merge local split = _.std.string.split -local unpack = _.std.table.unpack local _, _ENV = nil, _.strict {} @@ -152,7 +153,7 @@ end local function insert (pathstrings, ...) local paths = split (pathstrings, pathsep) table_insert (paths, ...) - return normalize (unpack (paths)) + return normalize (table_unpack (paths, 1, len (paths))) end diff --git a/lib/std/table.lua b/lib/std/table.lua index 667df07..5d79e3e 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -15,10 +15,12 @@ local getmetatable = getmetatable local next = next local setmetatable = setmetatable local table = table +local tonumber = tonumber local type = type local math_min = math.min local table_insert = table.insert +local table_unpack = table.unpack local _ = { @@ -33,12 +35,12 @@ local argscheck = _.typing.argscheck local argerror = _.typing.argerror local collect = _.std.functional.collect local copy = _.std.base.copy +local getmetamethod = _.std.getmetamethod local invert = _.std.table.invert local len = _.std.operator.len local maxn = _.std.table.maxn local merge = _.std.base.merge local pack = _.std.table.pack -local unpack = _.std.table.unpack local deprecated = require "std.delete-after.a-year" @@ -177,6 +179,16 @@ local function remove (t, pos) end +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 table_unpack (t, tonumber (i) or 1, tonumber (j)) +end + + local function values (t) local l = {} for _, v in _pairs (t) do diff --git a/lib/std/typing.lua b/lib/std/typing.lua index 475c7da..300f095 100644 --- a/lib/std/typing.lua +++ b/lib/std/typing.lua @@ -40,6 +40,7 @@ local table_concat = table.concat local table_insert = table.insert local table_remove = table.remove local table_sort = table.sort +local table_unpack = table.unpack local _ = { @@ -59,7 +60,6 @@ local len = _.std.operator.len local nop = _.std.functional.nop local pack = _.std.table.pack local split = _.std.string.split -local unpack = _.std.table.unpack local _, _ENV = nil, _.strict {} @@ -511,7 +511,7 @@ if _DEBUG.argcheck then diagnose (results, output) end - return unpack (results) + return table_unpack (results, 1, results.n) end end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 246d375..aa2b865 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -664,6 +664,12 @@ specify std.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} From 5008deddb719fc5f0e35e4aff928e4b0cc67a149 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 9 Jan 2016 14:30:22 +0000 Subject: [PATCH 654/703] maint: fallback to unpack for Lua 5.1, which has no table.unpack. * lib/std/package.lua (table_unpack): Fallback to unpack for Lua 5.1. * lib/std/table.lua (table_unpack): Likewise. * lib/std/typing.lua (table_unpack): Likewise. Signed-off-by: Gary V. Vaughan --- lib/std/package.lua | 2 +- lib/std/table.lua | 2 +- lib/std/typing.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/package.lua b/lib/std/package.lua index 4e57892..157cf67 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -44,7 +44,7 @@ local string_match = string.match local table_concat = table.concat local table_insert = table.insert local table_remove = table.remove -local table_unpack = table.unpack +local table_unpack = table.unpack or unpack local _ = { diff --git a/lib/std/table.lua b/lib/std/table.lua index 5d79e3e..af4da61 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -20,7 +20,7 @@ local type = type local math_min = math.min local table_insert = table.insert -local table_unpack = table.unpack +local table_unpack = table.unpack or unpack local _ = { diff --git a/lib/std/typing.lua b/lib/std/typing.lua index 300f095..a3c5907 100644 --- a/lib/std/typing.lua +++ b/lib/std/typing.lua @@ -40,7 +40,7 @@ local table_concat = table.concat local table_insert = table.insert local table_remove = table.remove local table_sort = table.sort -local table_unpack = table.unpack +local table_unpack = table.unpack or unpack local _ = { From 47bc831da485f09459c4bc6be1b86c87477719bf Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 10 Jan 2016 23:35:45 +0000 Subject: [PATCH 655/703] functional: simplify implementation of lambda strings. * lib/std/functional.lua (lambda): No need to pack and unpack again immediately afterwards! Signed-off-by: Gary V. Vaughan --- lib/std/functional.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 87b9017..52b6cf5 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -293,8 +293,7 @@ local lambda = memoize (function (s) if body then expr = [[ return function (...) - local unpack = table.unpack or unpack - local _1,_2,_3,_4,_5,_6,_7,_8,_9 = unpack {...} + local _1,_2,_3,_4,_5,_6,_7,_8,_9 = ... local _ = _1 return ]] .. body .. [[ end From cbd4925394252004631251f0900cefb69de9ea6a Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 12 Jan 2016 09:34:07 +0000 Subject: [PATCH 656/703] optparse: split into its own package. * lib/std/optparses.lua: Remove file. * local.mk (dist_luastd_DATA): Remove lib/std/optparse.lua. (dist_docmodules_DATA): Remove doc/modules/optparse.html. * specs/optparse_spec.yaml: Remove file. * specs/specs.mk (specl_SPECS): Remove specs/optparse_spec.yaml. * lib/std/io.lua (warn): Remove optparse references from ldoc comments. * build-aux/config.ld.in: Remove optparse. * NEWS.md (Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 15 + build-aux/config.ld.in | 11 +- lib/std/io.lua | 5 +- lib/std/optparse.lua | 762 --------------------------------------- local.mk | 2 - specs/optparse_spec.yaml | 461 ----------------------- specs/specs.mk | 1 - 7 files changed, 19 insertions(+), 1238 deletions(-) delete mode 100644 lib/std/optparse.lua delete mode 100644 specs/optparse_spec.yaml diff --git a/NEWS.md b/NEWS.md index 94f6018..a905cdf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,15 @@ us organization-wise, but improvements and corrections to the content are always welcome! + - With this release, stdlib is much more focused, and non-core module + `std.optparse` has been moved into its own package and release + cycle. You can install it separately from its [own project][optparse] + or using Luarocks: + + ```bash + luarocks install optparse + ``` + - `require "std.strict"` now returns a function that can be applied to any environment that should detect references to undeclared variables. For example, to check within the implementation of a @@ -229,6 +238,9 @@ ### Incompatible changes + - `std.optparse` has been moved to its own package, and is no longer + shipped as part of stdlib. + - `std.debug.DEPRECATED` and `std.debug.DEPRECATIONMSG` have moved to a new module `std.maturity`. Deprecation DEPRECATED with multi-level deprecation warnings was more confusing than simply moving the @@ -1608,3 +1620,6 @@ - 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. + + +[own project]: https://github.com/gvvaughan/optparse diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index fdad474..59f1f9a 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -17,19 +17,15 @@ LuaJIT), 5.2 and 5.3 written in pure Lua, comprising: useful objects built on it: @{std.container}, @{std.object}, @{std.list}, @{std.set}, @{std.strbuf}, @{std.tree} and @{std.tuple}; -4. A specialized prototype object that reads the help text for a - command-line script, and produces a custom option parser for handling - the options described by that help text: @{std.optparse}; - -5. A foundation for programming in a functional style: @{std.functional} +4. A foundation for programming in a functional style: @{std.functional} and @{std.operator}; -6. A runtime gradual typing system, for typechecking argument and return +5. A runtime gradual typing system, for typechecking argument and return types at function boundaries with simple annotations that can be enabled or disabled for production code, with a Lua API modelled on the core Lua C language API: also in @{std.typing}; -7. And an implementation of @{std.strict} to enforce declaration of all +6. And an implementation of @{std.strict} to enforce declaration of all globals prior to use. ## LICENSE @@ -67,7 +63,6 @@ file = { -- Other Modules "../lib/std/maturity.lua", - "../lib/std/optparse.lua", "../lib/std/strict.lua", "../lib/std/typing.lua", } diff --git a/lib/std/io.lua b/lib/std/io.lua index 6253043..8d552d2 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -202,13 +202,10 @@ M = { -- 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. @{std.optparse:parse} - -- returns an `opts` table that provides the required `program` - -- field, as long as you assign it back to `_G.opts`. + -- `opts.program` and `opts.line` if any. -- @function warn -- @string msg format string -- @param ... additional arguments to plug format string specifiers - -- @see std.optparse:parse -- @see die -- @usage -- local OptionParser = require "std.optparse" diff --git a/lib/std/optparse.lua b/lib/std/optparse.lua deleted file mode 100644 index a77ce3d..0000000 --- a/lib/std/optparse.lua +++ /dev/null @@ -1,762 +0,0 @@ ---[=[-- - Parse and process command line options. - - In the common case, you can write the long-form help output typical of - a modern-command line program, and let this module generate a custom - parser that collects and diagnoses the options it describes. - - The parser is a @{std.object} instance which can then be tweaked for - the uncommon case, by hand, or by using the @{on} function to tie your - custom handlers to options that are not handled quite the way you'd - like. - - Prototype Chain - --------------- - - table - `-> Container - `-> Object - `-> OptionParser - - @module std.optparse -]=] - - -local assert = assert -local error = error -local print = print -local require = require -local setmetatable = setmetatable -local tostring = tostring -local type = type - -local io_open = io.open -local io_stderr = io.stderr -local os_exit = os.exit -local string_len = string.len -local table_insert = table.insert - - -local _ = { - object = require "std.object", - std = require "std.base", - strict = require "std.strict", -} - -local Object = _.object.prototype - -local _ipairs = _.std.ipairs -local _pairs = _.std.pairs -local last = _.std.base.last -local len = _.std.operator.len - - -local _, _ENV = nil, _.strict {} - - - ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - -local optional, required - - ---- Normalise an argument list. --- Separate short options, remove `=` separators from --- `--long-option=optarg` etc. --- @local --- @function normalise --- @tparam table arglist list of arguments to normalise --- @treturn table normalised argument list -local function normalise (self, arglist) - local normal = {} - local i = 0 - while i < len (arglist) do - i = i + 1 - local opt = arglist[i] - - -- Split '--long-option=option-argument'. - if opt:sub (1, 2) == "--" then - local x = opt:find ("=", 3, true) - if x then - local optname = opt:sub (1, x -1) - - -- Only split recognised long options. - if self[optname] then - table_insert (normal, optname) - table_insert (normal, opt:sub (x + 1)) - else - x = nil - end - end - - if x == nil then - -- No '=', or substring before '=' is not a known option name. - table_insert (normal, opt) - end - - elseif opt:sub (1, 1) == "-" and string_len (opt) > 2 then - local orig, split, rest = opt, {} - repeat - opt, rest = opt:sub (1, 2), opt:sub (3) - - split[#split + 1] = opt - - -- If there's no handler, the option was a typo, or not supposed - -- to be an option at all. - if self[opt] == nil then - opt, split = nil, { orig } - - -- Split '-xyz' into '-x -yz', and reiterate for '-yz' - elseif self[opt].handler ~= optional and - self[opt].handler ~= required then - if string_len (rest) > 0 then - opt = "-" .. rest - else - opt = nil - end - - -- Split '-xshortargument' into '-x shortargument'. - else - split[#split + 1] = rest - opt = nil - end - until opt == nil - - -- Append split options to normalised list - for _, v in _ipairs (split) do table_insert (normal, v) end - else - table_insert (normal, opt) - end - end - - normal[-1], normal[0] = arglist[-1], arglist[0] - return normal -end - - ---- Store `value` with `opt`. --- @local --- @function set --- @string opt option name --- @param value option argument value -local function set (self, opt, value) - local key = self[opt].key - local opts = self.opts[key] - - if type (opts) == "table" then - table_insert (opts, value) - elseif opts ~= nil then - self.opts[key] = { opts, value } - else - self.opts[key] = value - end -end - - - ---[[ ============= ]]-- ---[[ Option Types. ]]-- ---[[ ============= ]]-- - - ---- Option at `arglist[i]` can take an argument. --- Argument is accepted only if there is a following entry that does not --- begin with a '-'. --- --- This is the handler automatically assigned to options that have --- `--opt=[ARG]` style specifications in the @{OptionParser} spec --- argument. You can also pass it as the `handler` argument to @{on} for --- options you want to add manually without putting them in the --- @{OptionParser} spec. --- --- Like @{required}, this handler will store multiple occurrences of a --- command-line option. --- @static --- @tparam table arglist list of arguments --- @int i index of last processed element of *arglist* --- @param[opt=true] value either a function to process the option --- argument, or a default value if encountered without an optarg --- @treturn int index of next element of *arglist* to process --- @usage --- parser:on ("--enable-nls", parser.option, parser.boolean) -function optional (self, arglist, i, value) - if i + 1 <= len (arglist) and arglist[i + 1]:sub (1, 1) ~= "-" then - return self:required (arglist, i, value) - end - - if type (value) == "function" then - value = value (self, arglist[i], nil) - elseif value == nil then - value = true - end - - set (self, arglist[i], value) - return i + 1 -end - - ---- Option at `arglist[i}` requires an argument. --- --- This is the handler automatically assigned to options that have --- `--opt=ARG` style specifications in the @{OptionParser} spec argument. --- You can also pass it as the `handler` argument to @{on} for options --- you want to add manually without putting them in the @{OptionParser} --- spec. --- --- Normally the value stored in the `opt` table by this handler will be --- the string given as the argument to that option on the command line. --- However, if the option is given on the command-line multiple times, --- `opt["name"]` will end up with all those arguments stored in the --- array part of a table: --- --- $ cat ./prog --- ... --- parser:on ({"-e", "-exec"}, required) --- _G.arg, _G.opt = parser:parse (_G.arg) --- print std.string.tostring (_G.opt.exec) --- ... --- $ ./prog -e '(foo bar)' -e '(foo baz)' -- qux --- {1=(foo bar),2=(foo baz)} --- @static --- @tparam table arglist list of arguments --- @int i index of last processed element of *arglist* --- @param[opt] value either a function to process the option argument, --- or a forced value to replace the user's option argument. --- @treturn int index of next element of *arglist* to process --- @usage --- parser:on ({"-o", "--output"}, parser.required) -function required (self, arglist, i, value) - local opt = arglist[i] - if i + 1 > len (arglist) then - self:opterr ("option '" .. opt .. "' requires an argument") - return i + 1 - end - - if type (value) == "function" then - value = value (self, opt, arglist[i + 1]) - elseif value == nil then - value = arglist[i + 1] - end - - set (self, opt, value) - return i + 2 -end - - ---- Finish option processing --- --- This is the handler automatically assigned to the option written as --- `--` in the @{OptionParser} spec argument. You can also pass it as --- the `handler` argument to @{on} if you want to manually add an end --- of options marker without writing it in the @{OptionParser} spec. --- --- This handler tells the parser to stop processing arguments, so that --- anything after it will be an argument even if it otherwise looks --- like an option. --- @static --- @tparam table arglist list of arguments --- @int i index of last processed element of `arglist` --- @treturn int index of next element of `arglist` to process --- @usage --- parser:on ("--", parser.finished) -local function finished (self, arglist, i) - for opt = i + 1, len (arglist) do - table_insert (self.unrecognised, arglist[opt]) - end - return 1 + len (arglist) -end - - ---- Option at `arglist[i]` is a boolean switch. --- --- This is the handler automatically assigned to options that have --- `--long-opt` or `-x` style specifications in the @{OptionParser} spec --- argument. You can also pass it as the `handler` argument to @{on} for --- options you want to add manually without putting them in the --- @{OptionParser} spec. --- --- Beware that, _unlike_ @{required}, this handler will store multiple --- occurrences of a command-line option as a table **only** when given a --- `value` function. Automatically assigned handlers do not do this, so --- the option will simply be `true` if the option was given one or more --- times on the command-line. --- @static --- @tparam table arglist list of arguments --- @int i index of last processed element of *arglist* --- @param[opt] value either a function to process the option argument, --- or a value to store when this flag is encountered --- @treturn int index of next element of *arglist* to process --- @usage --- parser:on ({"--long-opt", "-x"}, parser.flag) -local function flag (self, arglist, i, value) - local opt = arglist[i] - if type (value) == "function" then - set (self, opt, value (self, opt, true)) - elseif value == nil then - local key = self[opt].key - self.opts[key] = true - end - - return i + 1 -end - - ---- Option should display help text, then exit. --- --- This is the handler automatically assigned tooptions that have --- `--help` in the specification, e.g. `-h, -?, --help`. --- @static --- @function help --- @usage --- parser:on ("-?", parser.version) -local function help (self) - print (self.helptext) - os_exit (0) -end - - ---- Option should display version text, then exit. --- --- This is the handler automatically assigned tooptions that have --- `--version` in the specification, e.g. `-V, --version`. --- @static --- @function version --- @usage --- parser:on ("-V", parser.version) -local function version (self) - print (self.versiontext) - os_exit (0) -end - - - ---[[ =============== ]]-- ---[[ Argument Types. ]]-- ---[[ =============== ]]-- - - ---- Map various option strings to equivalent Lua boolean values. --- @table boolvals --- @field false false --- @field 0 false --- @field no false --- @field n false --- @field true true --- @field 1 true --- @field yes true --- @field y true -local boolvals = { - ["false"] = false, ["true"] = true, - ["0"] = false, ["1"] = true, - no = false, yes = true, - n = false, y = true, -} - - ---- Return a Lua boolean equivalent of various *optarg* strings. --- Report an option parse error if *optarg* is not recognised. --- --- Pass this as the `value` function to @{on} when you want various --- "truthy" or "falsey" option arguments to be coerced to a Lua `true` --- or `false` respectively in the options table. --- @static --- @string opt option name --- @string[opt="1"] optarg option argument, must be a key in @{boolvals} --- @treturn bool `true` or `false` --- @usage --- parser:on ("--enable-nls", parser.optional, parser.boolean) -local function boolean (self, opt, optarg) - if optarg == nil then optarg = "1" end -- default to truthy - local b = boolvals[tostring (optarg):lower ()] - if b == nil then - return self:opterr (optarg .. ": Not a valid argument to " ..opt[1] .. ".") - end - return b -end - - ---- Report an option parse error unless *optarg* names an --- existing file. --- --- Pass this as the `value` function to @{on} when you want to accept --- only option arguments that name an existing file. --- @fixme this only checks whether the file has read permissions --- @static --- @string opt option name --- @string optarg option argument, must be an existing file --- @treturn string *optarg* --- @usage --- parser:on ("--config-file", parser.required, parser.file) -local function file (self, opt, optarg) - local h, errmsg = io_open (optarg, "r") - if h == nil then - return self:opterr (optarg .. ": " .. errmsg) - end - h:close () - return optarg -end - - - ---[[ =============== ]]-- ---[[ Option Parsing. ]]-- ---[[ =============== ]]-- - - ---- Report an option parse error, then exit with status 2. --- --- Use this in your custom option handlers for consistency with the --- error output from built-in @{std.optparse} error messages. --- @static --- @string msg error message -local function opterr (self, msg) - local prog = self.program - -- Ensure final period. - if msg:match ("%.$") == nil then msg = msg .. "." end - io_stderr:write (prog .. ": error: " .. msg .. "\n") - io_stderr:write (prog .. ": Try '" .. prog .. " --help' for help.\n") - os_exit (2) -end - - ------- --- Function signature of an option handler for @{on}. --- @function on_handler --- @tparam table arglist list of arguments --- @int i index of last processed element of *arglist* --- @param[opt=nil] value additional `value` registered with @{on} --- @treturn int index of next element of *arglist* to process - - ---- Add an option handler. --- --- When the automatically assigned option handlers don't do everything --- you require, or when you don't want to put an option into the --- @{OptionParser} `spec` argument, use this function to specify custom --- behaviour. If you write the option into the `spec` argument anyway, --- calling this function will replace the automatically assigned handler --- with your own. --- --- When writing your own handlers for @{std.optparse:on}, you only need --- to deal with normalised arguments, because combined short arguments --- (`-xyz`), equals separators to long options (`--long=ARG`) are fully --- expanded before any handler is called. --- @function on --- @tparam[string|table] opts name of the option, or list of option names --- @tparam on_handler handler function to call when any of *opts* is --- encountered --- @param value additional value passed to @{on_handler} --- @usage --- -- Don't process any arguments after `--` --- parser:on ('--', parser.finished) -local function on (self, opts, handler, value) - if type (opts) == "string" then opts = { opts } end - handler = handler or flag -- unspecified options behave as flags - - local normal = {} - for _, optspec in _ipairs (opts) do - optspec:gsub ("(%S+)", - function (opt) - -- 'x' => '-x' - if string_len (opt) == 1 then - opt = "-" .. opt - - -- 'option-name' => '--option-name' - elseif opt:match ("^[^%-]") ~= nil then - opt = "--" .. opt - end - - if opt:match ("^%-[^%-]+") ~= nil then - -- '-xyz' => '-x -y -z' - for i = 2, string_len (opt) do - table_insert (normal, "-" .. opt:sub (i, i)) - end - else - table_insert (normal, opt) - end - end) - end - - -- strip leading '-', and convert non-alphanums to '_' - local key = last (normal):match ("^%-*(.*)$"):gsub ("%W", "_") - - for _, opt in _ipairs (normal) do - self[opt] = { key = key, handler = handler, value = value } - end -end - - ------- --- Parsed options table, with a key for each encountered option, each --- with value set by that option's @{on_handler}. Where an option --- has one or more long-options specified, the key will be the first --- one of those with leading hyphens stripped and non-alphanumeric --- characters replaced with underscores. For options that can only be --- specified by a short option, the key will be the letter of the first --- of the specified short options: --- --- {"-e", "--eval-file"} => opts.eval_file --- {"-n", "--dryrun", "--dry-run"} => opts.dryrun --- {"-t", "-T"} => opts.t --- --- Generally there will be one key for each previously specified --- option (either automatically assigned by @{OptionParser} or --- added manually with @{on}) containing the value(s) assigned by the --- associated @{on_handler}. For automatically assigned handlers, --- that means `true` for straight-forward flags and --- optional-argument options for which no argument was given; or else --- the string value of the argument passed with an option given only --- once; or a table of string values of the same for arguments given --- multiple times. --- --- ./prog -x -n -x => opts = { x = true, dryrun = true } --- ./prog -e '(foo bar)' -e '(foo baz)' --- => opts = {eval_file = {"(foo bar)", "(foo baz)"} } --- --- If you write your own handlers, or otherwise specify custom --- handling of options with @{on}, then whatever value those handlers --- return will be assigned to the respective keys in `opts`. --- @table opts - - ---- Parse an argument list. --- @tparam table arglist list of arguments --- @tparam[opt] table defaults table of default option values --- @treturn table a list of unrecognised *arglist* elements --- @treturn opts parsing results -local function parse (self, arglist, defaults) - self.unrecognised, self.opts = {}, {} - - arglist = normalise (self, arglist) - - local i = 1 - while i > 0 and i <= len (arglist) do - local opt = arglist[i] - - if self[opt] == nil then - table_insert (self.unrecognised, opt) - i = i + 1 - - -- Following non-'-' prefixed argument is an optarg. - if i <= len (arglist) and arglist[i]:match "^[^%-]" then - table_insert (self.unrecognised, arglist[i]) - i = i + 1 - end - - -- Run option handler functions. - else - assert (type (self[opt].handler) == "function") - - i = self[opt].handler (self, arglist, i, self[opt].value) - end - end - - -- Merge defaults into user options. - for k, v in _pairs (defaults or {}) do - if self.opts[k] == nil then self.opts[k] = v end - end - - -- metatable allows `io.warn` to find `parser.program` when assigned - -- back to _G.opts. - return self.unrecognised, setmetatable (self.opts, {__index = self}) -end - - ---- Take care not to register duplicate handlers. --- @param current current handler value --- @param new new handler value --- @return `new` if `current` is nil -local function set_handler (current, new) - assert (current == nil, "only one handler per option") - return new -end - - -local function _init (_, spec) - local parser = {} - - parser.versiontext, parser.version, parser.helptext, parser.program = - spec:match ("^([^\n]-(%S+)\n.-)%s*([Uu]sage: (%S+).-)%s*$") - - if parser.versiontext == nil then - error ("OptionParser spec argument must match '\\n" .. - "...Usage: ...'") - end - - -- Collect helptext lines that begin with two or more spaces followed - -- by a '-'. - local specs = {} - parser.helptext:gsub ("\n %s*(%-[^\n]+)", - function (spec) table_insert (specs, spec) end) - - -- Register option handlers according to the help text. - for _, spec in _ipairs (specs) do - local options, handler = {} - - -- Loop around each '-' prefixed option on this line. - while spec:sub (1, 1) == "-" do - - -- Capture end of options processing marker. - if spec:match "^%-%-,?%s" then - handler = set_handler (handler, finished) - - -- Capture optional argument in the option string. - elseif spec:match "^%-[%-%w]+=%[.+%],?%s" then - handler = set_handler (handler, optional) - - -- Capture required argument in the option string. - elseif spec:match "^%-[%-%w]+=%S+,?%s" then - handler = set_handler (handler, required) - - -- Capture any specially handled arguments. - elseif spec:match "^%-%-help,?%s" then - handler = set_handler (handler, help) - - elseif spec:match "^%-%-version,?%s" then - handler = set_handler (handler, version) - end - - -- Consume argument spec, now that it was processed above. - spec = spec:gsub ("^(%-[%-%w]+)=%S+%s", "%1 ") - - -- Consume short option. - local _, c = spec:gsub ("^%-([-%w]),?%s+(.*)$", - function (opt, rest) - if opt == "-" then opt = "--" end - table_insert (options, opt) - spec = rest - end) - - -- Be careful not to consume more than one option per iteration, - -- otherwise we might miss a handler test at the next loop. - if c == 0 then - -- Consume long option. - spec:gsub ("^%-%-([%-%w]+),?%s+(.*)$", - function (opt, rest) - table_insert (options, opt) - spec = rest - end) - end - end - - -- Unless specified otherwise, treat each option as a flag. - on (parser, options, handler or flag) - end - - return parser -end - - ---- Signature for initialising a custom OptionParser. --- --- Read the documented options from *spec* and return custom parser that --- can be used for parsing the options described in *spec* from a run-time --- argument list. Options in *spec* are recognised as lines that begin --- with at least two spaces, followed by a hyphen. --- @static --- @function OptionParser_Init --- @string spec option parsing specification --- @treturn OptionParser a parser for options described by *spec* --- @usage --- customparser = std.optparse (optparse_spec) - - ---- OptionParser prototype object. --- --- Most often, after instantiating an @{OptionParser}, everything else --- is handled automatically. --- --- Then, calling `parser:parse` as shown below saves unparsed arguments --- into `_G.arg` (usually filenames or similar), and `_G.opts` will be a --- table of successfully parsed option values. The keys into this table --- are the long-options with leading hyphens stripped, and non-word --- characters turned to `_`. For example if `--another-long` had been --- found in the initial `_G.arg`, then `_G.opts` will have a key named --- `another_long`, with an appropriate value. If there is no long --- option name, then the short option is used, i.e. `_G.opts.b` will be --- set. --- --- The values saved against those keys are controlled by the option --- handler, usually just `true` or the option argument string as --- appropriate. --- @object OptionParser --- @tparam OptionParser_Init _init initialisation function --- @string program the first word following "Usage:" from *spec* --- @string version the last white-space delimited word on the first line --- of text from *spec* --- @string versiontext everything preceding "Usage:" from *spec*, and --- which will be displayed by the @{version} @{on_handler} --- @string helptext everything including and following "Usage:" from --- *spec* string and which will be displayed by the @{help} --- @{on_handler} --- @usage --- local std = require "std" --- --- local optparser = std.optparse [[ --- any text VERSION --- Additional lines of text to show when the --version --- option is passed. --- --- Several lines or paragraphs are permitted. --- --- Usage: PROGNAME --- --- Banner text. --- --- Optional long description text to show when the --help --- option is passed. --- --- Several lines or paragraphs of long description are permitted. --- --- Options: --- --- -b a short option with no long option --- --long a long option with no short option --- --another-long a long option with internal hypen --- -v, --verbose a combined short and long option --- -n, --dryrun, --dry-run several spellings of the same option --- -u, --name=USER require an argument --- -o, --output=[FILE] accept an optional argument --- --version display version information, then exit --- --help display this help, then exit --- --- Footer text. Several lines or paragraphs are permitted. --- --- Please report bugs at bug-list@yourhost.com --- ]] --- --- -- Note that @{std.io.die} and @{std.io.warn} will only prefix messages --- -- with `parser.program` if the parser options are assigned back to --- -- `_G.opts`: --- _G.arg, _G.opts = optparser:parse (_G.arg) -return Object { - _type = "OptionParser", - - _init = _init, - - -- Prototype initial values. - opts = {}, - helptext = "", - program = "", - versiontext = "", - version = 0, - - --- @export - __index = { - boolean = boolean, - file = file, - finished = finished, - flag = flag, - help = help, - optional = optional, - required = required, - version = version, - - on = on, - opterr = opterr, - parse = parse, - }, -} diff --git a/local.mk b/local.mk index 3afdf9d..be73cf9 100644 --- a/local.mk +++ b/local.mk @@ -75,7 +75,6 @@ dist_luastd_DATA = \ lib/std/maturity.lua \ lib/std/object.lua \ lib/std/operator.lua \ - lib/std/optparse.lua \ lib/std/package.lua \ lib/std/set.lua \ lib/std/strbuf.lua \ @@ -163,7 +162,6 @@ dist_docfunctional_DATA += \ dist_docmodules_DATA += \ $(docmodules).maturity.html \ - $(docmodules).optparse.html \ $(docmodules).strict.html \ $(docmodules).typing.html \ $(NOTHING_ELSE) diff --git a/specs/optparse_spec.yaml b/specs/optparse_spec.yaml deleted file mode 100644 index 70f874e..0000000 --- a/specs/optparse_spec.yaml +++ /dev/null @@ -1,461 +0,0 @@ -before: - hell = require "specl.shell" - -specify std.optparse: -- before: | - OptionParser = require "std.optparse" - - help = [[ - parseme (stdlib spec) 0α1 - - Copyright © 2015 Gary V. Vaughan - This test program comes with ABSOLUTELY NO WARRANTY. - - Usage: parseme [] ... - - Banner text. - - Long description. - - Options: - - -h, --help display this help, then exit - --version display version information, then exit - -b a short option with no long option - --long a long option with no short option - --another-long a long option with internal hypen - --true a Lua keyword as an option name - -v, --verbose a combined short and long option - -n, --dryrun, --dry-run several spellings of the same option - -u, --name=USER require an argument - -o, --output=[FILE] accept an optional argument - -- end of options - - Footer text. - - Please report bugs at . - ]] - - -- strip off the leading whitespace required for YAML - parser = OptionParser (help:gsub ("^ ", "")) - -- context when required: - - context by name: - - it does not touch the global table: - expect (show_apis {added_to="_G", by="std.optparse"}). - to_equal {} - -- describe OptionParser: - - it recognises the program name: - expect (parser.program).to_be "parseme" - - it recognises the version number: - expect (parser.version).to_be "0α1" - - it recognises the version text: - expect (parser.versiontext). - to_match "^parseme .*Copyright .*NO WARRANTY%." - - it recognises the help text: | - expect (parser.helptext). - to_match ("^Usage: parseme .*Banner .*Long .*Options:.*" .. - "Footer .*/issues>%.") - - it diagnoses incorrect input text: - expect (OptionParser "garbage in").to_raise "argument must match" - -- describe parser: - - before: | - code = [[ - package.path = "]] .. package.path .. [[" - local OptionParser = require 'std.optparse' - local help = [=[]] .. help .. [[]=] - help = help:match ("^[%s\n]*(.-)[%s\n]*$") - - local parser = OptionParser (help) - local arg, opts = parser:parse (_G.arg) - - o = {} - for k, v in pairs (opts) do - table.insert (o, k .. " = " .. tostring (v)) - end - if #o > 0 then - table.sort (o) - print ("opts = { " .. table.concat (o, ", ") .. " }") - end - if #arg > 0 then - print ("args = { " .. table.concat (arg, ", ") .. " }") - end - ]] - parse = bind (luaproc, {code}) - - - it responds to --version with version text: - expect (parse {"--version"}). - to_match_output "^%s*parseme .*Copyright .*NO WARRANTY%.\n$" - - it responds to --help with help text: | - expect (parse {"--help"}). - to_match_output ("^%s*Usage: parseme .*Banner.*Long.*" .. - "Options:.*Footer.*/issues>%.\n$") - - it leaves behind unrecognised short options: - expect (parse {"-x"}).to_output "args = { -x }\n" - - it recognises short options: - expect (parse {"-b"}).to_output "opts = { b = true }\n" - - it leaves behind unrecognised options: - expect (parse {"--not-an-option"}). - to_output "args = { --not-an-option }\n" - - it recognises long options: - expect (parse {"--long"}).to_output "opts = { long = true }\n" - - it recognises long options with hyphens: - expect (parse {"--another-long"}). - to_output "opts = { another_long = true }\n" - - it recognises long options named after Lua keywords: - expect (parse {"--true"}).to_output "opts = { true = true }\n" - - it recognises combined short and long option specs: - expect (parse {"-v"}).to_output "opts = { verbose = true }\n" - expect (parse {"--verbose"}).to_output "opts = { verbose = true }\n" - - it recognises options with several spellings: - expect (parse {"-n"}).to_output "opts = { dry_run = true }\n" - expect (parse {"--dry-run"}).to_output "opts = { dry_run = true }\n" - expect (parse {"--dryrun"}).to_output "opts = { dry_run = true }\n" - - it recognises end of options marker: - expect (parse {"-- -n"}).to_output "args = { -n }\n" - - context given an unhandled long option: - - it leaves behind unmangled argument: - expect (parse {"--not-an-option=with-an-argument"}). - to_output "args = { --not-an-option=with-an-argument }\n" - - context given an option with a required argument: - - it records an argument to a long option following an '=' delimiter: - expect (parse {"--name=Gary"}). - to_output "opts = { name = Gary }\n" - - it records an argument to a short option without a space: - expect (parse {"-uGary"}). - to_output "opts = { name = Gary }\n" - - it records an argument to a long option following a space: - expect (parse {"--name Gary"}). - to_output "opts = { name = Gary }\n" - - it records an argument to a short option following a space: - expect (parse {"-u Gary"}). - to_output "opts = { name = Gary }\n" - - it diagnoses a missing argument: - expect (parse {"--name"}). - to_contain_error "'--name' requires an argument" - expect (parse {"-u"}). - to_contain_error "'-u' requires an argument" - - context given an option with an optional argument: - - it records an argument to a long option following an '=' delimiter: - expect (parse {"--output=filename"}). - to_output "opts = { output = filename }\n" - - it records an argument to a short option without a space: - expect (parse {"-ofilename"}). - to_output "opts = { output = filename }\n" - - it records an argument to a long option following a space: - expect (parse {"--output filename"}). - to_output "opts = { output = filename }\n" - - it records an argument to a short option following a space: - expect (parse {"-o filename"}). - to_output "opts = { output = filename }\n" - - it doesn't consume the following option: - expect (parse {"--output -v"}). - to_output "opts = { output = true, verbose = true }\n" - expect (parse {"-o -v"}). - to_output "opts = { output = true, verbose = true }\n" - - context when splitting combined short options: - - it separates non-argument options: - expect (parse {"-bn"}). - to_output "opts = { b = true, dry_run = true }\n" - expect (parse {"-vbn"}). - to_output "opts = { b = true, dry_run = true, verbose = true }\n" - - it stops separating at a required argument option: - expect (parse {"-vuname"}). - to_output "opts = { name = name, verbose = true }\n" - expect (parse {"-vuob"}). - to_output "opts = { name = ob, verbose = true }\n" - - it stops separating at an optional argument option: - expect (parse {"-vofilename"}). - to_output "opts = { output = filename, verbose = true }\n" - expect (parse {"-vobn"}). - to_output "opts = { output = bn, verbose = true }\n" - - it leaves behind unsplittable short options: - expect (parse {"-xvb"}).to_output "args = { -xvb }\n" - expect (parse {"-vxb"}).to_output "args = { -vxb }\n" - expect (parse {"-vbx"}).to_output "args = { -vbx }\n" - - it separates short options before unsplittable options: - expect (parse {"-vb -xvb"}). - to_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" - expect (parse {"-vb -vxb"}). - to_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" - expect (parse {"-vb -vbx"}). - to_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" - - it separates short options after unsplittable options: - expect (parse {"-xvb -vb"}). - to_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" - expect (parse {"-vxb -vb"}). - to_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" - expect (parse {"-vbx -vb"}). - to_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" - - - context with option defaults: - - before: | - function main (arg) - local OptionParser = require "std.optparse" - local parser = OptionParser ("program 0\nUsage: program\n" .. - " -x set x\n" .. - " -y set y\n" .. - " -z set z\n") - local state = { arg = {}, opts = { x={"t"}, y=false }} - state.arg, state.opts = parser:parse (arg, state.opts) - return state - end - - it prefers supplied argument: - expect (main {"-x", "-y"}). - to_equal { arg = {}, opts = { x=true, y=true }} - expect (main {"-x", "-y", "-z"}). - to_equal { arg = {}, opts = { x=true, y=true, z=true }} - expect (main {"-w", "-x", "-y"}). - to_equal { arg = {"-w"}, opts = { x=true, y=true }} - - it defers to default value: - expect (main {}). - to_equal { arg = {}, opts = { x={"t"}, y=false }} - expect (main {"-z"}). - to_equal { arg = {}, opts = { x={"t"}, y=false, z=true }} - expect (main {"-w"}). - to_equal { arg = {"-w"}, opts = { x={"t"}, y=false }} - - - context with io.die: - - before: | - runscript = function (code) - return luaproc ([[ - local OptionParser = require "std.optparse" - local parser = OptionParser ("program 0\nUsage: program\n") - _G.arg, _G.opts = parser:parse (_G.arg) - ]] .. code .. [[ - require "std.io".die "By 'eck!" - ]]) - end - - it prefers `prog.name` to `opts.program`: | - code = [[prog = { file = "file", name = "name" }]] - expect (runscript (code)).to_fail_while_matching ": name: By 'eck!\n" - - it prefers `prog.file` to `opts.program`: | - code = [[prog = { file = "file" }]] - expect (runscript (code)).to_fail_while_matching ": file: By 'eck!\n" - - it appends `prog.line` if any to `prog.file` over using `opts`: | - code = [[ - prog = { file = "file", line = 125 }; opts.line = 99]] - expect (runscript (code)). - to_fail_while_matching ": file:125: By 'eck!\n" - - it prefixes `opts.program` if any: | - expect (runscript ("")).to_fail_while_matching ": program: By 'eck!\n" - - it appends `opts.line` if any, to `opts.program`: | - code = [[opts.line = 99]] - expect (runscript (code)). - to_fail_while_matching ": program:99: By 'eck!\n" - - - context with io.warn: - - before: | - runscript = function (code) - return luaproc ([[ - local OptionParser = require "std.optparse" - local parser = OptionParser ("program 0\nUsage: program\n") - _G.arg, _G.opts = parser:parse (_G.arg) - ]] .. code .. [[ - require "std.io".warn "Ayup!" - ]]) - end - - it prefers `prog.name` to `opts.program`: | - code = [[prog = { file = "file", name = "name" }]] - expect (runscript (code)).to_output_error "name: Ayup!\n" - - it prefers `prog.file` to `opts.program`: | - code = [[prog = { file = "file" }]] - expect (runscript (code)).to_output_error "file: Ayup!\n" - - it appends `prog.line` if any to `prog.file` over using `opts`: | - code = [[ - prog = { file = "file", line = 125 }; opts.line = 99]] - expect (runscript (code)). - to_output_error "file:125: Ayup!\n" - - it prefixes `opts.program` if any: | - expect (runscript ("")).to_output_error "program: Ayup!\n" - - it appends `opts.line` if any, to `opts.program`: | - code = [[opts.line = 99]] - expect (runscript (code)). - to_output_error "program:99: Ayup!\n" - -- describe parser:on: - - before: | - function parseargs (onargstr, arglist) - code = [[ - package.path = "]] .. package.path .. [[" - local OptionParser = require 'std.optparse' - local help = [=[]] .. help .. [[]=] - help = help:match ("^[%s\n]*(.-)[%s\n]*$") - - local parser = OptionParser (help) - - parser:on (]] .. onargstr .. [[) - - _G.arg, _G.opts = parser:parse (_G.arg) - - o = {} - for k, v in pairs (opts) do - table.insert (o, k .. " = " .. tostring (v)) - end - if #o > 0 then - table.sort (o) - print ("opts = { " .. table.concat (o, ", ") .. " }") - end - if #arg > 0 then - print ("args = { " .. table.concat (arg, ", ") .. " }") - end - ]] - - return luaproc (code, arglist) - end - - - it recognises short options: - expect (parseargs ([["x"]], {"-x"})). - to_output "opts = { x = true }\n" - - it recognises long options: - expect (parseargs([["something"]], {"--something"})). - to_output "opts = { something = true }\n" - - it recognises long options with hyphens: - expect (parseargs([["some-thing"]], {"--some-thing"})). - to_output "opts = { some_thing = true }\n" - - it recognises long options named after Lua keywords: - expect (parseargs ([["if"]], {"--if"})). - to_output "opts = { if = true }\n" - - it recognises combined short and long option specs: - expect (parseargs ([[{"x", "if"}]], {"-x"})). - to_output "opts = { if = true }\n" - expect (parseargs ([[{"x", "if"}]], {"--if"})). - to_output "opts = { if = true }\n" - - it recognises options with several spellings: - expect (parseargs ([[{"x", "blah", "if"}]], {"-x"})). - to_output "opts = { if = true }\n" - expect (parseargs ([[{"x", "blah", "if"}]], {"--blah"})). - to_output "opts = { if = true }\n" - expect (parseargs ([[{"x", "blah", "if"}]], {"--if"})). - to_output "opts = { if = true }\n" - - it recognises end of options marker: - expect (parseargs ([["x"]], {"--", "-x"})). - to_output "args = { -x }\n" - - context given an option with a required argument: - - it records an argument to a short option without a space: - expect (parseargs ([["x", parser.required]], {"-y", "-xarg", "-b"})). - to_contain_output "opts = { b = true, x = arg }" - - it records an argument to a short option following a space: - expect (parseargs ([["x", parser.required]], {"-y", "-x", "arg", "-b"})). - to_contain_output "opts = { b = true, x = arg }\n" - - it records an argument to a long option following a space: - expect (parseargs ([["this", parser.required]], {"--this", "arg"})). - to_output "opts = { this = arg }\n" - - it records an argument to a long option following an '=' delimiter: - expect (parseargs ([["this", parser.required]], {"--this=arg"})). - to_output "opts = { this = arg }\n" - - it diagnoses a missing argument: - expect (parseargs ([[{"x", "this"}, parser.required]], {"-x"})). - to_contain_error "'-x' requires an argument" - expect (parseargs ([[{"x", "this"}, parser.required]], {"--this"})). - to_contain_error "'--this' requires an argument" - - context with a boolean handler function: - - it records a truthy argument: - for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} - do - expect (parseargs ([["x", parser.required, parser.boolean]], - {"-x", optarg})). - to_output "opts = { x = true }\n" - end - - it records a falsey argument: - for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} - do - expect (parseargs ([["x", parser.required, parser.boolean]], - {"-x", optarg})). - to_output "opts = { x = false }\n" - end - - context with a file handler function: - - it records an existing file: - expect (parseargs ([["x", parser.required, parser.file]], - {"-x", "/dev/null"})). - to_output "opts = { x = /dev/null }\n" - - it diagnoses a missing file: | - expect (parseargs ([["x", parser.required, parser.file]], - {"-x", "/this/file/does/not/exist"})). - to_contain_error "error: /this/file/does/not/exist: " - - context with a custom handler function: - - it calls the handler: - expect (parseargs ([["x", parser.required, function (p,o,a) - return "custom" - end - ]], {"-x", "ignored"})). - to_output "opts = { x = custom }\n" - - it diagnoses a missing argument: - expect (parseargs ([["x", parser.required, function (p,o,a) - return "custom" - end - ]], {"-x"})). - to_contain_error "option '-x' requires an argument" - - context given an option with an optional argument: - - it records an argument to a short option without a space: - expect (parseargs ([["x", parser.optional]], {"-y", "-xarg", "-b"})). - to_contain_output "opts = { b = true, x = arg }" - - it records an argument to a short option following a space: - expect (parseargs ([["x", parser.optional]], {"-y", "-x", "arg", "-b"})). - to_contain_output "opts = { b = true, x = arg }\n" - - it records an argument to a long option following a space: - expect (parseargs ([["this", parser.optional]], {"--this", "arg"})). - to_output "opts = { this = arg }\n" - - it records an argument to a long option following an '=' delimiter: - expect (parseargs ([["this", parser.optional]], {"--this=arg"})). - to_output "opts = { this = arg }\n" - - it does't consume the following option: - expect (parseargs ([[{"x", "this"}, parser.optional]], {"-x", "-b"})). - to_output "opts = { b = true, this = true }\n" - expect (parseargs ([[{"x", "this"}, parser.optional]], {"--this", "-b"})). - to_output "opts = { b = true, this = true }\n" - - context with a boolean handler function: - - it records a truthy argument: - for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} - do - expect (parseargs ([["x", parser.optional, parser.boolean]], - {"-x", optarg})). - to_output "opts = { x = true }\n" - end - - it records a falsey argument: - for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} - do - expect (parseargs ([["x", parser.optional, parser.boolean]], - {"-x", optarg})). - to_output "opts = { x = false }\n" - end - - it defaults to a truthy value: - expect (parseargs ([["x", parser.optional, parser.boolean]], - {"-x", "-b"})). - to_output "opts = { b = true, x = true }\n" - - context with a file handler function: - - it records an existing file: - expect (parseargs ([["x", parser.optional, parser.file]], - {"-x", "/dev/null"})). - to_output "opts = { x = /dev/null }\n" - - it diagnoses a missing file: | - expect (parseargs ([["x", parser.optional, parser.file]], - {"-x", "/this/file/does/not/exist"})). - to_contain_error "error: /this/file/does/not/exist: " - - context with a custom handler function: - - it calls the handler: - expect (parseargs ([["x", parser.optional, function (p,o,a) - return "custom" - end - ]], {"-x", "ignored"})). - to_output "opts = { x = custom }\n" - - it does not consume a following option: - expect (parseargs ([["x", parser.optional, function (p,o,a) - return a or "default" - end - ]], {"-x", "-b"})). - to_output "opts = { b = true, x = default }\n" - - context when splitting combined short options: - - it separates non-argument options: - expect (parseargs ([["x"]], {"-xb"})). - to_output "opts = { b = true, x = true }\n" - expect (parseargs ([["x"]], {"-vxb"})). - to_output "opts = { b = true, verbose = true, x = true }\n" - - it stops separating at a required argument option: - expect (parseargs ([[{"x", "this"}, parser.required]], {"-bxbit"})). - to_output "opts = { b = true, this = bit }\n" - - it stops separating at an optional argument option: - expect (parseargs ([[{"x", "this"}, parser.optional]], {"-bxbit"})). - to_output "opts = { b = true, this = bit }\n" diff --git a/specs/specs.mk b/specs/specs.mk index e57b601..cee83b2 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -22,7 +22,6 @@ specl_SPECS = \ $(srcdir)/specs/maturity_spec.yaml \ $(srcdir)/specs/object_spec.yaml \ $(srcdir)/specs/operator_spec.yaml \ - $(srcdir)/specs/optparse_spec.yaml \ $(srcdir)/specs/package_spec.yaml \ $(srcdir)/specs/set_spec.yaml \ $(srcdir)/specs/strbuf_spec.yaml \ From a8fa0f9d392c44d519737ac3576192cdfebf9834 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 17 Jan 2016 16:39:37 +0000 Subject: [PATCH 657/703] maint: require recent optparse compatible specl 14.1.4. * bootstrap.conf (buildreq): Bump specl requirement to 14.1.4. Signed-off-by: Gary V. Vaughan --- bootstrap.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap.conf b/bootstrap.conf index 63c9684..3f5dd4b 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -31,7 +31,7 @@ buildreq=' git - http://git-scm.com ldoc 1.4.2 http://rocks.moonscript.org/manifests/steved/ldoc-1.4.2-1.rockspec - specl 14.1.0 http://rocks.moonscript.org/manifests/gvvaughan/specl-14.1.0-1.rockspec + specl 14.1.4 http://rocks.moonscript.org/manifests/gvvaughan/specl-14.1.0-1.rockspec ' # List of slingshot files to link into stdlib tree before autotooling. From 191c7501800a2d0c764ad8d640a6144b7e9bf57f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 12 Jan 2016 09:34:07 +0000 Subject: [PATCH 658/703] maint: remove optparse from sanity-cfg.mk. * bulid-aux/sanity-cfg.mk (exclude_file_name_regexp--sc_error_message_uppercase): Remove optparse exclusion. Signed-off-by: Gary V. Vaughan --- build-aux/sanity-cfg.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-aux/sanity-cfg.mk b/build-aux/sanity-cfg.mk index 6bbf697..59fa6c7 100644 --- a/build-aux/sanity-cfg.mk +++ b/build-aux/sanity-cfg.mk @@ -1,3 +1,3 @@ -exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/(list|optparse).lua$$ +exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/list.lua$$ EXTRA_DIST += build-aux/sanity-cfg.mk From 680d288ee694501ff8e4b131f0d4eaab4d274e2d Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 17 Jan 2016 15:01:01 +0000 Subject: [PATCH 659/703] slingshot: sync with upstream. * slingshot: Sync with upstream. Signed-off-by: Gary V. Vaughan --- slingshot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slingshot b/slingshot index 3dee4de..9476807 160000 --- a/slingshot +++ b/slingshot @@ -1 +1 @@ -Subproject commit 3dee4defd7d3d80a62b4638012f5dee973cc7114 +Subproject commit 94768075ff472181d475fcd6a5a0896c14abb6d8 From 44d411861d12122de24b1ce714ca05eed310f5cf Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 21 Jan 2016 00:03:32 +0000 Subject: [PATCH 660/703] strict: split out into its own package. * lib/std/strict.lua: Remove file. * local.mk (dist_luastd_DATA): Remove lib/std/strict.lua. (dist_docmodules_DATA): Remove doc/modules/strict.html. * specs/strict_spec.yaml: Remove file. * specs/specs.mk (specl_SPECS): Roemove specs/strict_spec.yaml. * build-aux/config.ld.in: Remove strict. * lib/std/delete-after/2016-01-31.lua, lib/std/delete-after/2016-03-08.lua, lib/std/delete-after/a-year.lua (strict): Remove use of strict entirely. * lib/std/base.lua, lib/std/container.lua, lib/std/debug.lua, lib/std/functional.lua, lib/std/init.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/maturity.lua, lib/std/object.lua, lib/std/operator.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua, lib/std/tuple.lua, lib/std/typing.lua: Adjust to only try to load external strict module if debug_init._DEBUG.strict is set, otherwise if not or if it is not available, gracefully fallback to non-strict environments. * NEWS.md (New features, Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 32 +++------ bootstrap | 2 +- build-aux/config.ld.in | 4 -- lib/std/base.lua | 13 +++- lib/std/container.lua | 12 +++- lib/std/debug.lua | 16 +++-- lib/std/delete-after/2016-01-31.lua | 3 - lib/std/delete-after/2016-03-08.lua | 4 -- lib/std/delete-after/a-year.lua | 4 -- lib/std/functional.lua | 13 +++- lib/std/init.lua | 17 ++++- lib/std/io.lua | 13 +++- lib/std/list.lua | 14 +++- lib/std/math.lua | 14 +++- lib/std/maturity.lua | 12 +++- lib/std/object.lua | 15 +++- lib/std/operator.lua | 13 +++- lib/std/package.lua | 13 +++- lib/std/set.lua | 15 +++- lib/std/strbuf.lua | 15 +++- lib/std/strict.lua | 102 ---------------------------- lib/std/string.lua | 15 +++- lib/std/table.lua | 16 +++-- lib/std/tree.lua | 13 +++- lib/std/tuple.lua | 13 +++- lib/std/typing.lua | 11 ++- local.mk | 2 - specs/specs.mk | 1 - specs/strict_spec.yaml | 67 ------------------ 29 files changed, 224 insertions(+), 260 deletions(-) delete mode 100644 lib/std/strict.lua delete mode 100644 specs/strict_spec.yaml diff --git a/NEWS.md b/NEWS.md index a905cdf..890536d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,22 +10,15 @@ us organization-wise, but improvements and corrections to the content are always welcome! - - With this release, stdlib is much more focused, and non-core module - `std.optparse` has been moved into its own package and release - cycle. You can install it separately from its [own project][optparse] + - With this release, stdlib is much more focused, and non-core modules + `std.optparse` and `std.strict` have been moved into their own packages + and release cycle. You can still install them separately from their + [own][optparse] [projects][strict] or using Luarocks: ```bash luarocks install optparse - ``` - - - `require "std.strict"` now returns a function that can be applied - to any environment that should detect references to undeclared - variables. For example, to check within the implementation of a - module, but without changing the behaviour of the client code: - - ```lua - local _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) + luarocks install strict ``` - All support for deprecated APIs has been moved out of the module @@ -238,8 +231,8 @@ ### Incompatible changes - - `std.optparse` has been moved to its own package, and is no longer - shipped as part of stdlib. + - `std.optparse` and `std.strict` have been moved to their own packages, + and are no longer shipped as part of stdlib. - `std.debug.DEPRECATED` and `std.debug.DEPRECATIONMSG` have moved to a new module `std.maturity`. Deprecation DEPRECATED with multi-level @@ -302,14 +295,6 @@ the new compact format, including stringification of Objects and Containers using their `__tostring` metamethods. - - `std.strict` now returns a callable for detecting accidental global - variable leakage and reference, but does not apply it to `_G` by - default. You can emulate the old behaviour with: - - ```lua - _G = require "std.strict" (_G) - ``` - ## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] @@ -1622,4 +1607,5 @@ formal releases will be in permanent beta, and tracking CVS is recommended. -[own project]: https://github.com/gvvaughan/optparse +[optparse]: https://github.com/gvvaughan/optparse +[strict]: https://github.com/lua-stdlib/strict diff --git a/bootstrap b/bootstrap index 0b70e37..97d3fe9 100755 --- a/bootstrap +++ b/bootstrap @@ -5,7 +5,7 @@ # Bootstrap an Autotooled package from checked-out sources. # Written by Gary V. Vaughan, 2010 -# Copyright (C) 2010-2015 Free Software Foundation, Inc. +# Copyright (C) 2010-2016 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 59f1f9a..86bb8f8 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -25,9 +25,6 @@ LuaJIT), 5.2 and 5.3 written in pure Lua, comprising: enabled or disabled for production code, with a Lua API modelled on the core Lua C language API: also in @{std.typing}; -6. And an implementation of @{std.strict} to enforce declaration of all - globals prior to use. - ## LICENSE The code is copyright by its respective authors, and released under the @@ -63,7 +60,6 @@ file = { -- Other Modules "../lib/std/maturity.lua", - "../lib/std/strict.lua", "../lib/std/typing.lua", } diff --git a/lib/std/base.lua b/lib/std/base.lua index d790c9f..473f55f 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -23,9 +23,10 @@ ]] +local _ENV = _ENV local dirsep = string.match (package.config, "^(%S+)\n") local error = error -local getfenv = getfenv +local getfenv = getfenv or false local getmetatable = getmetatable local loadstring = loadstring or load local next = next @@ -56,7 +57,15 @@ local table_sort = table.sort local table_unpack = table.unpack or unpack -local _ENV = require "std.strict" {} +local _DEBUG = require "std.debug_init"._DEBUG + + +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end diff --git a/lib/std/container.lua b/lib/std/container.lua index 48056c3..5b6bdbf 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -28,6 +28,7 @@ @prototype std.container ]] +local _ENV = _ENV local getmetatable = getmetatable local next = next local select = select @@ -42,7 +43,6 @@ local table_concat = table.concat local _ = { debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } @@ -59,7 +59,15 @@ local pickle = _.std.string.pickle local render = _.std.string.render local sortkeys = _.std.base.sortkeys -local _, _ENV = nil, _.strict {} + +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 8da0ede..639d2cc 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -11,6 +11,7 @@ ]] +local _ENV = _ENV local debug = debug local setmetatable = setmetatable local type = type @@ -21,10 +22,11 @@ local math_max = math.max local table_concat = table.concat +local deprecated = require "std.delete-after.a-year" + local _ = { debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", } local _DEBUG = _.debug_init._DEBUG @@ -35,9 +37,14 @@ local _tostring = _.std.tostring local merge = _.std.base.merge -local deprecated = require "std.delete-after.a-year" +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end -local _, _ENV = nil, _.strict {} +_ = nil @@ -60,8 +67,7 @@ local _, _ENV = nil, _.strict {} -- value causes deprecated APIs not to be defined at all -- @tfield[opt=1] int level debugging level -- @tfield[opt=true] boolean strict enforce strict variable declaration --- before use **in stdlib internals** --- @see std.strict +-- before use **in stdlib internals** (if `require "strict"` works) -- @usage _DEBUG = { argcheck = false, level = 9, strict = false } diff --git a/lib/std/delete-after/2016-01-31.lua b/lib/std/delete-after/2016-01-31.lua index fbae56e..00d5bad 100644 --- a/lib/std/delete-after/2016-01-31.lua +++ b/lib/std/delete-after/2016-01-31.lua @@ -32,7 +32,6 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", std = require "std.base", - strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -43,8 +42,6 @@ if not require "std.debug_init"._DEBUG.deprecate then local DEPRECATED = _.maturity.DEPRECATED local _ipairs = _.std.ipairs - -- Only the above symbols are used below this line. - local _, _ENV = nil, _.strict {} --[[ ========== ]]-- diff --git a/lib/std/delete-after/2016-03-08.lua b/lib/std/delete-after/2016-03-08.lua index c6ed718..f3ee24f 100644 --- a/lib/std/delete-after/2016-03-08.lua +++ b/lib/std/delete-after/2016-03-08.lua @@ -31,7 +31,6 @@ if not require "std.debug_init"._DEBUG.deprecate then -- Adding anything else here will probably cause a require loop. maturity = require "std.maturity", std = require "std.base", - strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -41,9 +40,6 @@ if not require "std.debug_init"._DEBUG.deprecate then local DEPRECATED = _.maturity.DEPRECATED - -- Only the above symbols are used below this line. - local _, _ENV = nil, _.strict {} - --[[ ========== ]]-- --[[ Death Row! ]]-- diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua index 72c8817..8e71ca5 100644 --- a/lib/std/delete-after/a-year.lua +++ b/lib/std/delete-after/a-year.lua @@ -54,7 +54,6 @@ if not require "std.debug_init"._DEBUG.deprecate then debug_init = require "std.debug_init", maturity = require "std.maturity", std = require "std.base", - strict = require "std.strict", } -- Merge in deprecated APIs from previous release if still available. @@ -79,9 +78,6 @@ if not require "std.debug_init"._DEBUG.deprecate then local sortkeys = _.std.base.sortkeys local split = _.std.string.split - -- Only the above symbols are used below this line. - local _, _ENV = nil, _.strict {} - --[[ ========== ]]-- diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 52b6cf5..68dd23a 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -8,6 +8,7 @@ ]] +local _ENV = _ENV local loadstring = loadstring or load local next = next local pcall = pcall @@ -21,11 +22,12 @@ local table_unpack = table.unpack or unpack local _ = { + debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } +local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs local argscheck = _.typing.argscheck @@ -39,7 +41,14 @@ local reduce = _.std.functional.reduce local leaves = _.std.tree.leaves -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/init.lua b/lib/std/init.lua index d309780..889369a 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -20,6 +20,7 @@ ]] +local _ENV = _ENV local _G = _G local error = error local ipairs = ipairs @@ -35,12 +36,15 @@ local string_format = string.format local string_match = string.match +local deprecated = require "std.delete-after.a-year" + local _ = { - strict = require "std.strict", + debug_init = require "std.debug_init", std = require "std.base", typing = require "std.typing", } +local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs local _tostring = _.std.tostring @@ -55,9 +59,16 @@ local merge = _.std.base.merge local ripairs = _.std.ripairs local split = _.std.string.split -local deprecated = require "std.delete-after.a-year" -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil + diff --git a/lib/std/io.lua b/lib/std/io.lua index 8d552d2..7f2762a 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,6 +11,7 @@ ]] +local _ENV = _ENV local _G = _G local arg = arg local error = error @@ -34,11 +35,12 @@ local table_insert = table.insert local _ = { + debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } +local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _tostring = _.std.tostring local argerror = _.typing.argerror @@ -52,7 +54,14 @@ local merge = _.std.base.merge local split = _.std.string.split -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/list.lua b/lib/std/list.lua index 4336450..8465c5b 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -17,19 +17,22 @@ ]] +local _ENV = _ENV + local table_unpack = table.unpack or unpack local _ = { + debug_init = require "std.debug_init", object = require "std.object", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } local Module = _.std.object.Module local Object = _.object.prototype +local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs local argscheck = _.typing.argscheck @@ -37,7 +40,14 @@ local compare = _.std.list.compare local len = _.std.operator.len -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/math.lua b/lib/std/math.lua index e893a97..41b0a2c 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,23 +11,31 @@ ]] +local _ENV = _ENV local math = math - local math_floor = math.floor local _ = { + debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } +local _DEBUG = _.debug_init._DEBUG local argscheck = _.typing.argscheck local copy = _.std.base.copy local merge = _.std.base.merge -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua index 11eaa51..2ad007d 100644 --- a/lib/std/maturity.lua +++ b/lib/std/maturity.lua @@ -25,6 +25,8 @@ @module std.maturity ]] + +local _ENV = _ENV local error = error local pcall = pcall local select = select @@ -37,13 +39,19 @@ local table_unpack = table.unpack or unpack local _ = { debug_init = require "std.debug_init", - strict = require "std.strict", } local _DEBUG = _.debug_init._DEBUG -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/object.lua b/lib/std/object.lua index 2e1e890..876b868 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -22,29 +22,38 @@ ]] +local _ENV = _ENV local getmetatable = getmetatable +local deprecated = require "std.delete-after.a-year" + local _ = { + debug_init = require "std.debug_init", container = require "std.container", maturity = require "std.maturity", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } local Container = _.container.prototype local Module = _.std.object.Module +local _DEBUG = _.debug_init._DEBUG local argscheck = _.typing.argscheck local getmetamethod = _.std.getmetamethod local mapfields = _.std.object.mapfields local merge = _.std.base.merge -local deprecated = require "std.delete-after.a-year" +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end -local _, _ENV = nil, _.strict {} +_ = nil diff --git a/lib/std/operator.lua b/lib/std/operator.lua index 168b3c2..fb4679d 100644 --- a/lib/std/operator.lua +++ b/lib/std/operator.lua @@ -5,20 +5,29 @@ ]] +local _ENV = _ENV local type = type local _ = { + debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", } +local _DEBUG = _.debug_init._DEBUG local _tostring = _.std.tostring local len = _.std.operator.len local serialize = _.std.base.mnemonic -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/package.lua b/lib/std/package.lua index 157cf67..d4859ed 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -36,6 +36,7 @@ ]] +local _ENV = _ENV local ipairs = ipairs local package = package @@ -48,11 +49,12 @@ local table_unpack = table.unpack or unpack local _ = { + debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } +local _DEBUG = _.debug_init._DEBUG local argscheck = _.typing.argscheck local catfile = _.std.io.catfile local escape_pattern = _.std.string.escape_pattern @@ -62,7 +64,14 @@ local merge = _.std.base.merge local split = _.std.string.split -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/set.lua b/lib/std/set.lua index dba2462..1389e6e 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -19,9 +19,10 @@ `-> Set @prototype std.set - ]] +]] +local _ENV = _ENV local getmetatable = getmetatable local next = next local rawget = rawget @@ -35,21 +36,29 @@ local table_sort = table.sort local _ = { container = require "std.container", + debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } local Container = _.container.prototype local Module = _.std.object.Module +local _DEBUG = _.debug_init._DEBUG local _pairs = _.std.pairs local _tostring = _.std.tostring local argscheck = _.typing.argscheck local pickle = _.std.string.pickle -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 8640ff3..996ec9e 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -27,29 +27,38 @@ ]] +local _ENV = _ENV local ipairs = ipairs local tostring = tostring local table_concat = table.concat +local deprecated = require "std.delete-after.2016-01-31" + local _ = { + debug_init = require "std.debug_init", object = require "std.object", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } local Module = _.std.object.Module local Object = _.object.prototype +local _DEBUG = _.debug_init._DEBUG local argscheck = _.typing.argscheck local merge = _.std.base.merge -local deprecated = require "std.delete-after.2016-01-31" +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end -local _, _ENV = nil, _.strict {} +_ = nil diff --git a/lib/std/strict.lua b/lib/std/strict.lua deleted file mode 100644 index a5f59af..0000000 --- a/lib/std/strict.lua +++ /dev/null @@ -1,102 +0,0 @@ ---[[-- - Checks uses of undeclared variables. - - All variables (including functions!) must be "declared" through a regular - assignment (even assigning `nil` will do) in a strict scope before being - used anywhere or assigned to inside a function. - - Use the callable returned by this module to interpose a strictness check - proxy table to the given environment. - - The implementation calls `setfenv` appropriately in Lua 5.1 interpreters - to ensure the same semantics. - - @module std.strict -]] - -local debug_getinfo = debug.getinfo -local error = error -local rawset = rawset -local rawget = rawget -local setfenv = setfenv or function () end -local setmetatable = setmetatable - -local _DEBUG = require "std.debug_init"._DEBUG - -local _ENV = {} -setfenv (1, _ENV) - - ---- What kind of variable declaration is this? --- @treturn string "C", "Lua" or "main" -local function what () - local d = debug_getinfo (3, "S") - return d and d.what or "C" -end - - -return setmetatable ({ - --- Enforce variable declarations required before use in scope *env*. - -- @function strict - -- @tparam table env lexical environment table - -- @treturn table *env* proxy table with metamethods to enforce strict declarations - -- @usage - -- local _ENV = setmetatable ({}, {__index = _G}) - -- if require "std.debug_init"._DEBUG.strict then - -- _ENV = require "std.strict".strict (_ENV) - -- end - -- -- ...and for Lua 5.1 compatibility: - -- if rawget (_G, "setfenv") ~= nil then setfenv (1, _ENV) end - strict = function (env) - -- The set of declared variables in this scope. - local declared = {} - - --- Metamethods - -- @section metamethods - - return setmetatable ({}, { - --- Detect dereference of undeclared global. - -- @function env:__index - -- @string n name of the variable being dereferenced - __index = function (_, n) - local v = env[n] - if v ~= nil then - declared[n] = true - elseif not declared[n] and what () ~= "C" then - error ("variable '" .. n .. "' is not declared", 2) - end - return v - end, - - --- Detect assignment to undeclared global. - -- @function env:__newindex - -- @string n name of the variable being declared - -- @param v initial value of the variable - __newindex = function (_, n, v) - local x = env[n] - if x == nil and not declared[n] then - local w = what () - if w ~= "main" and w ~= "C" then - error ("assignment to undeclared variable '" .. n .. "'", 2) - end - end - declared[n] = true - env[n] = v - end, - }) - end, -}, { - --- Enforce strict variable declarations in *env* according to `_DEBUG`. - -- @function strict:__call - -- @tparam table env lexical environment table - -- @treturn table *env* which must be assigned to `_ENV` - -- @usage - -- local _ENV = require "std.strict" (setmetatable ({}, {__index = _G})) - __call = function (self, env) - if _DEBUG.strict then - env = self.strict (env) - end - setfenv (2, env) - return env - end, -}) diff --git a/lib/std/string.lua b/lib/std/string.lua index 77b7f89..1bdf0fc 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -11,6 +11,7 @@ ]] +local _ENV = _ENV local assert = assert local getmetatable = getmetatable local string = string @@ -24,16 +25,19 @@ local math_floor = math.floor local table_insert = table.insert +local deprecated = nil + local _ = { + debug_init = require "std.debug_init", maturity = require "std.maturity", std = require "std.base", strbuf = require "std.strbuf", - strict = require "std.strict", typing = require "std.typing", } local StrBuf = _.strbuf.prototype +local _DEBUG = _.debug_init._DEBUG local _tostring = _.std.tostring local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG local argscheck = _.typing.argscheck @@ -48,9 +52,14 @@ local split = _.std.string.split local toqstring = _.std.base.toqstring -local deprecated = nil +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end -local _, _ENV = nil, _.strict {} +_ = nil diff --git a/lib/std/table.lua b/lib/std/table.lua index af4da61..5291540 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,6 +11,7 @@ ]] +local _ENV = _ENV local getmetatable = getmetatable local next = next local setmetatable = setmetatable @@ -23,12 +24,15 @@ local table_insert = table.insert local table_unpack = table.unpack or unpack +local deprecated = require "std.delete-after.a-year" + local _ = { + debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } +local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs local argscheck = _.typing.argscheck @@ -43,10 +47,14 @@ local merge = _.std.base.merge local pack = _.std.table.pack -local deprecated = require "std.delete-after.a-year" - -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end +_ = nil diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 6afd7f6..396830b 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -24,6 +24,7 @@ ]] +local _ENV = _ENV local getmetatable = getmetatable local rawget = rawget local rawset = rawset @@ -37,15 +38,16 @@ local table_remove = table.remove local _ = { container = require "std.container", + debug_init = require "std.debug_init", operator = require "std.operator", std = require "std.base", - strict = require "std.strict", typing = require "std.typing", } local Container = _.container.prototype local Module = _.std.object.Module +local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs local argscheck = _.typing.argscheck @@ -57,7 +59,14 @@ local len = _.std.operator.len local reduce = _.std.functional.reduce -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua index 1c47a29..409a3dc 100644 --- a/lib/std/tuple.lua +++ b/lib/std/tuple.lua @@ -26,6 +26,7 @@ ]] +local _ENV = _ENV local error = error local getmetatable = getmetatable local next = next @@ -41,18 +42,26 @@ local table_unpack = table.unpack or unpack local _ = { container = require "std.container", + debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", } local Container = _.container.prototype local Module = _.std.object.Module +local _DEBUG = _.debug_init._DEBUG local pickle = _.std.string.pickle local toqstring = _.std.base.toqstring -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/lib/std/typing.lua b/lib/std/typing.lua index a3c5907..d463e56 100644 --- a/lib/std/typing.lua +++ b/lib/std/typing.lua @@ -23,6 +23,7 @@ ]] +local _ENV = _ENV local error = error local getmetatable = getmetatable local next = next @@ -46,7 +47,6 @@ local table_unpack = table.unpack or unpack local _ = { debug_init = require "std.debug_init", std = require "std.base", - strict = require "std.strict", } local _DEBUG = _.debug_init._DEBUG @@ -62,7 +62,14 @@ local pack = _.std.table.pack local split = _.std.string.split -local _, _ENV = nil, _.strict {} +if _DEBUG.strict then + local ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + end +end + +_ = nil diff --git a/local.mk b/local.mk index be73cf9..7d9f304 100644 --- a/local.mk +++ b/local.mk @@ -78,7 +78,6 @@ dist_luastd_DATA = \ lib/std/package.lua \ lib/std/set.lua \ lib/std/strbuf.lua \ - lib/std/strict.lua \ lib/std/string.lua \ lib/std/table.lua \ lib/std/tree.lua \ @@ -162,7 +161,6 @@ dist_docfunctional_DATA += \ dist_docmodules_DATA += \ $(docmodules).maturity.html \ - $(docmodules).strict.html \ $(docmodules).typing.html \ $(NOTHING_ELSE) diff --git a/specs/specs.mk b/specs/specs.mk index cee83b2..a8a6913 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -25,7 +25,6 @@ specl_SPECS = \ $(srcdir)/specs/package_spec.yaml \ $(srcdir)/specs/set_spec.yaml \ $(srcdir)/specs/strbuf_spec.yaml \ - $(srcdir)/specs/strict_spec.yaml \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ diff --git a/specs/strict_spec.yaml b/specs/strict_spec.yaml deleted file mode 100644 index 1d31074..0000000 --- a/specs/strict_spec.yaml +++ /dev/null @@ -1,67 +0,0 @@ -before: - strict = require "std.strict" - -specify std.strict: -- describe require: - - it returns a callable: - expect (getmetatable (strict).__call).not_to_be (nil) - - -- describe strict: - - before: - f = strict.strict - - - it allows assignment to declared variables: - scope = f { foo = "bar" } - - expect ((function () scope.foo = "baz" end) ()). - not_to_raise "not declared" - expect (scope.foo).to_be "baz" - - - it diagnoses assignment to undeclared variable: - scope = f { foo = "bar" } - - expect ((function () scope.undefined = "rval" end) ()). - to_raise "assignment to undeclared variable 'undefined'" - - - it allows reference to declared variables: - scope = f { foo = "bar" } - - expect ((function () return scope.foo end) ()).to_be "bar" - - - it diagnoses reference to undeclared variable: - scope = f {} - - expect ((function () return scope.undefined end) ()). - to_raise "variable 'undefined' is not declared" - - - it allows assignemnt to undeclared global variables: - _ENV = f (setmetatable ({}, {__index=_G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end - - defined = "rval" - expect (_ENV.defined).to_be "rval" - expect ((function () defined = "foo" end) ()). - not_to_raise "undeclared variable" - - - it diagnoses assignment to undeclared global variable: - _ENV = f (setmetatable ({}, {__index=_G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end - - expect ((function () undefined = "rval" end) ()). - to_raise "assignment to undeclared variable 'undefined'" - - - it diagnoses reference to undeclared global variable: - _ENV = f (setmetatable ({}, {__index=_G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end - - expect ((function () foo = undefined end) ()). - to_raise "variable 'undefined' is not declared" - - - it does not leak into surrounding scope: - _ENV = f (setmetatable ({}, {__index=_G})) - if rawget (_G, "setfenv") then setfenv (1, _ENV) end - - expect ((function () _G.undefined = "rval" end) ()). - not_to_raise "undefined" - From 367cea69a32530e9c9c21ac6bd507fe46010c1e9 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 23 Jan 2016 23:33:32 +0000 Subject: [PATCH 661/703] specs: no need for --unicode any more. * specs/specs.mk (SPECL_OPTS): Remove. --unicode option was only needed for recently separated optparse module. Signed-off-by: Gary V. Vaughan --- specs/specs.mk | 2 -- 1 file changed, 2 deletions(-) diff --git a/specs/specs.mk b/specs/specs.mk index a8a6913..7a0b00f 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -5,8 +5,6 @@ ## Specs. ## ## ------ ## -SPECL_OPTS = --unicode - ## For compatibility with Specl < 11, std_spec.yaml has to be ## last, so that when `require "std"` leaks symbols into the ## Specl global environment, subsequent example blocks are not From 3a9564462a65bdb3daf502a18cc39c5bdb7fb3c0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 Jan 2016 00:10:07 +0000 Subject: [PATCH 662/703] debug: simplify _DEBUG initialisation. * lib/std/debug_init/init.lua (_DEBUG): Drastic simplification. (set): Factor out logic for choosing return field value based on _DEBUG global setting at load time. Signed-off-by: Gary V. Vaughan --- lib/std/debug_init/init.lua | 81 +++++++++++++------------------------ 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index a99a33d..4693cb3 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -1,60 +1,35 @@ -local _ENV = { - _G = _G, - math_huge = math.huge, - rawget = rawget, - setfenv = setfenv or function () end, - type = type, -} -setfenv (1, _ENV) - - --- Debugging is on by default -local M = {} - --- Use rawget to satisfy std.strict. -local _DEBUG = rawget (_G, "_DEBUG") - --- User specified fields. -if type (_DEBUG) == "table" then - M._DEBUG = _DEBUG - --- Turn everything off. -elseif _DEBUG == false then - M._DEBUG = { - argcheck = false, - call = false, - deprecate = false, - level = math_huge, - strict = false, - } +--[[ + Return a table of debug parameters. --- Turn everything on (except _DEBUG.call must be set explicitly). -elseif _DEBUG == true then - M._DEBUG = { - argcheck = true, - call = false, - deprecate = true, - strict = true, - } - -else - M._DEBUG = {} -end + Before loading this module, set the global `_DEBUG` according to what + debugging features you wish to use until the application exits. +]] -local function setdefault (field, value) - if M._DEBUG[field] == nil then - M._DEBUG[field] = value +local function set (explicit, default, development, production) + if type(_DEBUG) == "table" then + if _DEBUG[explicit] == nil then + return default + end + return _DEBUG[explicit] + end + if _DEBUG == false then + return production + elseif _DEBUG == nil then + return default end + return development end --- Default settings if otherwise unspecified. -setdefault ("argcheck", true) -setdefault ("call", false) -setdefault ("deprecate", nil) -setdefault ("level", 1) -setdefault ("strict", true) - - -return M +return { + _DEBUG = { + -- _G._DEBUG is: table[name] nil true false + -- ------------------------------------------------ + argcheck = set ("argcheck", true, true, false), + call = set ("call", false, false, false), + deprecate = set ("deprecate", nil, true, false), + level = set ("level", 1, 1, math.huge), + strict = set ("strict", true, true, false), + }, +} From f4be64086ae061a6f69d95a6ee317ce761897a1f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 Jan 2016 17:51:34 +0000 Subject: [PATCH 663/703] debug: improve _DEBUG initialisation readability. * lib/std/debug_init/init.lua: Use more readable code here! Signed-off-by: Gary V. Vaughan --- lib/std/debug_init/init.lua | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index 4693cb3..2117529 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -6,30 +6,30 @@ ]] -local function set (explicit, default, development, production) - if type(_DEBUG) == "table" then - if _DEBUG[explicit] == nil then - return default +local function choose (t) + for k, v in pairs (t) do + if _DEBUG == false then + t[k] = v.fast + elseif _DEBUG == nil then + t[k] = v.default + elseif type(_DEBUG) ~= "table" then + t[k] = v.safe + elseif _DEBUG[k] ~= nil then + t[k] = _DEBUG[k] + else + t[k] = v.default end - return _DEBUG[explicit] end - if _DEBUG == false then - return production - elseif _DEBUG == nil then - return default - end - return development + return t end return { - _DEBUG = { - -- _G._DEBUG is: table[name] nil true false - -- ------------------------------------------------ - argcheck = set ("argcheck", true, true, false), - call = set ("call", false, false, false), - deprecate = set ("deprecate", nil, true, false), - level = set ("level", 1, 1, math.huge), - strict = set ("strict", true, true, false), + _DEBUG = choose { + argcheck = { default = true, safe = true, fast = false}, + call = { default = false, safe = false, fast = false}, + deprecate = { default = nil, safe = true, fast = false}, + level = { default = 1, safe = 1, fast = math.huge}, + strict = { default = true, safe = true, fast = false}, }, } From 1a92a2bb1e9a3b66e2e4b1c25f4e8d115612c8c7 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Jan 2016 00:19:05 +0000 Subject: [PATCH 664/703] typing: split out into its own package. * README.md: Remove item about type checking facilites. * lib/std/typing.lua: Remove file. * local.mk (dist_luastd_DATA): Remove lib/std/typing.lua. (dist_docmodules_DATA): Remove doc/modules/typing.html. * specs/typing_spec.yaml: Remove file. * specs/specs.mk (specl_SPECS): Remove specs/typing_spec.yaml. * build-aux/config.ld.in: Remove typing. * lib/std/base.lua (argerror): Provide an argerror function for reporting argument errors idiomatically. * lib/std/container.lua, lib/std/functional.lua, lib/std/init.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/maturity.lua, lib/std/object.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua: Make use of type checking facilities, only if external "typecheck" module is loadable. * specs/container_spec.yaml, specs/io_spec.yaml, specs/spec_helper.lua, specs/string_spec.yaml, specs/table_spec.yaml, specs/tree_spec.yaml: Adjust type checking examples to work even when typecheck module is not available. * NEWS.md (New features): Remove mention of new std.typing module. (Deprecations): Remove mention of deprecated argcheck functions. (Incompatibilities): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 15 +- build-aux/config.ld.in | 9 +- lib/std/base.lua | 11 + lib/std/container.lua | 25 +- lib/std/functional.lua | 16 +- lib/std/init.lua | 16 +- lib/std/io.lua | 18 +- lib/std/list.lua | 16 +- lib/std/math.lua | 16 +- lib/std/maturity.lua | 6 +- lib/std/object.lua | 16 +- lib/std/package.lua | 16 +- lib/std/set.lua | 16 +- lib/std/strbuf.lua | 16 +- lib/std/string.lua | 16 +- lib/std/table.lua | 18 +- lib/std/tree.lua | 16 +- lib/std/typing.lua | 700 ------------------------------- local.mk | 2 - specs/container_spec.yaml | 12 +- specs/io_spec.yaml | 68 +-- specs/spec_helper.lua | 19 +- specs/specs.mk | 1 - specs/string_spec.yaml | 12 +- specs/table_spec.yaml | 2 +- specs/tree_spec.yaml | 36 +- specs/typing_spec.yaml | 840 -------------------------------------- 27 files changed, 309 insertions(+), 1645 deletions(-) delete mode 100644 lib/std/typing.lua delete mode 100644 specs/typing_spec.yaml diff --git a/NEWS.md b/NEWS.md index 890536d..26da4aa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,9 +30,6 @@ _DEBUG.deprecate = true ``` - - New `std.typing` module contains the typechecking functions - previously in `std.debug`. - - Objects and Modules are no longer conflated - what you get back from a `require "std.something"` is now ALWAYS a module: @@ -193,12 +190,6 @@ - `std.string.render` function arguments have been deprecated in favour of a table of named functions backed by defaults. - - `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 deprecated in favour of their namesakes in the newly - factored out `std.typing` module. - ### Bug fixes @@ -234,6 +225,12 @@ - `std.optparse` and `std.strict` have been moved to their own packages, and are no longer shipped as part of stdlib. + - `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 moved to a new module `std.maturity`. Deprecation DEPRECATED with multi-level deprecation warnings was more confusing than simply moving the diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 86bb8f8..8fea8fc 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -20,11 +20,6 @@ LuaJIT), 5.2 and 5.3 written in pure Lua, comprising: 4. A foundation for programming in a functional style: @{std.functional} and @{std.operator}; -5. A runtime gradual typing system, for typechecking argument and return - types at function boundaries with simple annotations that can be - enabled or disabled for production code, with a Lua API modelled on - the core Lua C language API: also in @{std.typing}; - ## LICENSE The code is copyright by its respective authors, and released under the @@ -60,21 +55,23 @@ file = { -- Other Modules "../lib/std/maturity.lua", - "../lib/std/typing.lua", } new_type ("corefunction", "Core_Functions", true) new_type ("corelibrary", "Core_Libraries", true) +new_type ("coremodule", "Core_Modules", true) new_type ("prototype", "Object_System", true) new_type ("functional", "Functional_Style", true) function postprocess_html(s) s = s:gsub("

    %s*Corefunction (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Corelibrary (.-)

    ", '

    Module %1

    ') + s = s:gsub("

    %s*Coremodule (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Prototype (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Functional (.-)

    ", '

    Module %1

    ') s = s:gsub("

    Core_Functions

    ", '

    Core Functions

    ') s = s:gsub("

    Core_Libraries

    ", '

    Core Libraries

    ') + s = s:gsub("

    Core_Modules

    ", '

    Core Modules

    ') s = s:gsub("

    Object_System

    ", '

    Object System

    ') s = s:gsub("

    Functional_Style

    ", '

    Functional Style

    ') return s diff --git a/lib/std/base.lua b/lib/std/base.lua index 473f55f..a97fa07 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -117,6 +117,16 @@ end --[[ ============================ ]]-- +local function argerror (name, i, extramsg, level) + level = level or 1 + local s = string_format ("bad %s #%d %s '%s'", bad, i, to, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) +end + + -- No need to recurse because functables are second class citizens in -- Lua: -- func=function () print "called" end @@ -609,6 +619,7 @@ return { }, debug = { + argerror = argerror, getfenv = _getfenv, setfenv = _setfenv, }, diff --git a/lib/std/container.lua b/lib/std/container.lua index 5b6bdbf..a04d904 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -43,23 +43,38 @@ local table_concat = table.concat local _ = { debug_init = require "std.debug_init", std = require "std.base", - typing = require "std.typing", } local Module = _.std.object.Module local _DEBUG = _.debug_init._DEBUG -local argcheck = _.typing.argcheck -local argscheck = _.typing.argscheck -local argerror = _.typing.argerror +local argerror = _.std.debug.argerror local copy = _.std.base.copy -local extramsg_toomany = _.typing.extramsg_toomany local mapfields = _.std.object.mapfields local pickle = _.std.string.pickle local render = _.std.string.render local sortkeys = _.std.base.sortkeys +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argcheck, argscheck, extramsg_toomany +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argcheck = typecheck.argcheck + argscheck = typecheck.argscheck + extramsg_toomany = typecheck.extramsg_toomany + else + _DEBUG.argcheck = false + end +end +argcheck = argcheck or function () end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/functional.lua b/lib/std/functional.lua index 68dd23a..f251655 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -24,13 +24,11 @@ local table_unpack = table.unpack or unpack local _ = { debug_init = require "std.debug_init", std = require "std.base", - typing = require "std.typing", } local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argscheck = _.typing.argscheck local callable = _.std.functional.callable local ielems = _.std.ielems local len = _.std.operator.len @@ -41,6 +39,20 @@ local reduce = _.std.functional.reduce local leaves = _.std.tree.leaves +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argcheck, argerror, argscheck, extramsg_toomany +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/init.lua b/lib/std/init.lua index 889369a..bf2494a 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -41,14 +41,12 @@ local deprecated = require "std.delete-after.a-year" local _ = { debug_init = require "std.debug_init", std = require "std.base", - typing = require "std.typing", } local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs local _tostring = _.std.tostring -local argscheck = _.typing.argscheck local compare = _.std.list.compare local copy = _.std.base.copy local eval = _.std.eval @@ -60,6 +58,20 @@ local ripairs = _.std.ripairs local split = _.std.string.split +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/io.lua b/lib/std/io.lua index 7f2762a..b4a05f4 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -37,14 +37,12 @@ local table_insert = table.insert local _ = { debug_init = require "std.debug_init", std = require "std.base", - typing = require "std.typing", } local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _tostring = _.std.tostring -local argerror = _.typing.argerror -local argscheck = _.typing.argscheck +local argerror = _.std.debug.argerror local catfile = _.std.io.catfile local copy = _.std.base.copy local dirsep = _.std.package.dirsep @@ -54,6 +52,20 @@ local merge = _.std.base.merge local split = _.std.string.split +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/list.lua b/lib/std/list.lua index 8465c5b..424cabd 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -26,7 +26,6 @@ local _ = { debug_init = require "std.debug_init", object = require "std.object", std = require "std.base", - typing = require "std.typing", } local Module = _.std.object.Module @@ -35,11 +34,24 @@ local Object = _.object.prototype local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argscheck = _.typing.argscheck local compare = _.std.list.compare local len = _.std.operator.len +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/math.lua b/lib/std/math.lua index 41b0a2c..2e218e7 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -19,15 +19,27 @@ local math_floor = math.floor local _ = { debug_init = require "std.debug_init", std = require "std.base", - typing = require "std.typing", } local _DEBUG = _.debug_init._DEBUG -local argscheck = _.typing.argscheck local copy = _.std.base.copy local merge = _.std.base.merge +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argcheck, argscheck, extramsg_toomany +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua index 2ad007d..4baa8ef 100644 --- a/lib/std/maturity.lua +++ b/lib/std/maturity.lua @@ -22,7 +22,7 @@ Not setting `_DEBUG.deprecate` will warn on every call to deprecated APIs. - @module std.maturity + @coremodule std.maturity ]] @@ -97,7 +97,7 @@ end return { - --- Provide a deprecated function definition according to _DEBUG.deprecate. + -- Provide a deprecated function definition according to _DEBUG.deprecate. -- You can check whether your covered code uses deprecated functions by -- setting `_DEBUG.deprecate` to `true` before loading any stdlib modules, -- or silence deprecation warnings by setting `_DEBUG.deprecate = false`. @@ -111,7 +111,7 @@ return { -- M.op = DEPRECATED ("41", "'std.functional.op'", std.operator) DEPRECATED = DEPRECATED, - --- Format a deprecation warning message. + -- Format a deprecation warning message. -- @function DEPRECATIONMSG -- @string version first deprecation release version -- @string name function name for automatic warning message diff --git a/lib/std/object.lua b/lib/std/object.lua index 876b868..1921772 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -33,19 +33,31 @@ local _ = { container = require "std.container", maturity = require "std.maturity", std = require "std.base", - typing = require "std.typing", } local Container = _.container.prototype local Module = _.std.object.Module local _DEBUG = _.debug_init._DEBUG -local argscheck = _.typing.argscheck local getmetamethod = _.std.getmetamethod local mapfields = _.std.object.mapfields local merge = _.std.base.merge +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/package.lua b/lib/std/package.lua index d4859ed..c44ab38 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -51,11 +51,9 @@ local table_unpack = table.unpack or unpack local _ = { debug_init = require "std.debug_init", std = require "std.base", - typing = require "std.typing", } local _DEBUG = _.debug_init._DEBUG -local argscheck = _.typing.argscheck local catfile = _.std.io.catfile local escape_pattern = _.std.string.escape_pattern local invert = _.std.table.invert @@ -64,6 +62,20 @@ local merge = _.std.base.merge local split = _.std.string.split +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/set.lua b/lib/std/set.lua index 1389e6e..121c31a 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -38,7 +38,6 @@ local _ = { container = require "std.container", debug_init = require "std.debug_init", std = require "std.base", - typing = require "std.typing", } local Container = _.container.prototype @@ -47,10 +46,23 @@ local Module = _.std.object.Module local _DEBUG = _.debug_init._DEBUG local _pairs = _.std.pairs local _tostring = _.std.tostring -local argscheck = _.typing.argscheck local pickle = _.std.string.pickle +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 996ec9e..5ee3e3f 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -40,17 +40,29 @@ local _ = { debug_init = require "std.debug_init", object = require "std.object", std = require "std.base", - typing = require "std.typing", } local Module = _.std.object.Module local Object = _.object.prototype local _DEBUG = _.debug_init._DEBUG -local argscheck = _.typing.argscheck local merge = _.std.base.merge +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/string.lua b/lib/std/string.lua index 1bdf0fc..fe69d41 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -32,7 +32,6 @@ local _ = { maturity = require "std.maturity", std = require "std.base", strbuf = require "std.strbuf", - typing = require "std.typing", } local StrBuf = _.strbuf.prototype @@ -40,7 +39,6 @@ local StrBuf = _.strbuf.prototype local _DEBUG = _.debug_init._DEBUG local _tostring = _.std.tostring local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG -local argscheck = _.typing.argscheck local copy = _.std.base.copy local escape_pattern = _.std.string.escape_pattern local len = _.std.operator.len @@ -52,6 +50,20 @@ local split = _.std.string.split local toqstring = _.std.base.toqstring +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/table.lua b/lib/std/table.lua index 5291540..87a05f6 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -29,14 +29,12 @@ local deprecated = require "std.delete-after.a-year" local _ = { debug_init = require "std.debug_init", std = require "std.base", - typing = require "std.typing", } local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argscheck = _.typing.argscheck -local argerror = _.typing.argerror +local argerror = _.std.debug.argerror local collect = _.std.functional.collect local copy = _.std.base.copy local getmetamethod = _.std.getmetamethod @@ -47,6 +45,20 @@ local merge = _.std.base.merge local pack = _.std.table.pack +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 396830b..8710c79 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -41,7 +41,6 @@ local _ = { debug_init = require "std.debug_init", operator = require "std.operator", std = require "std.base", - typing = require "std.typing", } local Container = _.container.prototype @@ -50,7 +49,6 @@ local Module = _.std.object.Module local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argscheck = _.typing.argscheck local get = _.operator.get local ielems = _.std.ielems local last = _.std.base.last @@ -59,6 +57,20 @@ local len = _.std.operator.len local reduce = _.std.functional.reduce +-- Perform typechecking with functions exported from this module, unless +-- disabled in `_DEBUG` or the "typecheck" module is not loadable. +local argscheck +if _DEBUG.argcheck then + local ok, typecheck = pcall (require, "typecheck") + if ok then + argscheck = typecheck.argscheck + end +end +argscheck = argscheck or function (decl, inner) return inner end + + +-- Use a strict environment for the rest of this module, unless disabled +-- in `_DEBUG` or the "strict" module is not loadable. if _DEBUG.strict then local ok, strict = pcall (require, "strict") if ok then diff --git a/lib/std/typing.lua b/lib/std/typing.lua deleted file mode 100644 index d463e56..0000000 --- a/lib/std/typing.lua +++ /dev/null @@ -1,700 +0,0 @@ ---[[-- - Gradual typing facilities for function calls. - - The behaviour of the functions in this module are controlled by the value - of the global `_DEBUG`. Not setting `_DEBUG` prior to requiring **any** of - stdlib's modules is equivalent to having `_DEBUG = true`. - - The first line of Lua code in production quality projects that use stdlib - should be either: - - _DEBUG = false - - or alternatively, if you need to be careful not to damage the global - environment: - - local init = require "std.debug_init" - init._DEBUG = false - - This mitigates almost all of the overhead of type checking in the stdlib - API functions. - - @module std.typing -]] - - -local _ENV = _ENV -local error = error -local getmetatable = getmetatable -local next = next -local pcall = pcall -local type = type - -local io_type = io.type -local math_floor = math.floor -local math_max = math.max -local string_find = string.find -local string_format = string.format -local string_gsub = string.gsub -local string_match = string.match -local table_concat = table.concat -local table_insert = table.insert -local table_remove = table.remove -local table_sort = table.sort -local table_unpack = table.unpack or unpack - - -local _ = { - debug_init = require "std.debug_init", - std = require "std.base", -} - -local _DEBUG = _.debug_init._DEBUG -local _getfenv = _.std.debug.getfenv -local _ipairs = _.std.ipairs -local _pairs = _.std.pairs -local _setfenv = _.std.debug.setfenv -local _tostring = _.std.tostring -local copy = _.std.base.copy -local len = _.std.operator.len -local nop = _.std.functional.nop -local pack = _.std.table.pack -local split = _.std.string.split - - -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end - -_ = nil - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - -local function raise (bad, to, name, i, extramsg, level) - level = level or 1 - local s = string_format ("bad %s #%d %s '%s'", bad, i, to, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) -end - - -local function argerror (name, i, extramsg, level) - level = level or 1 - raise ("argument", "to", name, i, extramsg, level + 1) -end - - -local function resulterror (name, i, extramsg, level) - level = level or 1 - raise ("result", "from", name, i, extramsg, level + 1) -end - - -local function extramsg_toomany (bad, expected, actual) - local s = "no more than %d %s%s expected, got %d" - return string_format (s, expected, bad, expected == 1 and "" or "s", actual) -end - - ---- Concatenate a table of strings using ", " and " or " delimiters. --- @tparam table alternatives a table of strings --- @treturn string string of elements from alternatives delimited by ", " --- and " or " -local function concat (alternatives) - if len (alternatives) > 1 then - local t = copy (alternatives) - local top = table_remove (t) - t[#t] = t[#t] .. " or " .. top - alternatives = t - end - return table_concat (alternatives, ", ") -end - - -local function _type (x) - return (getmetatable (x) or {})._type or io_type (x) or type (x) -end - - -local function extramsg_mismatch (expectedtypes, actual, index) - local actualtype = _type (actual) or type (actual) - - -- Tidy up actual type for display. - if actualtype == "nil" then - actualtype = "no value" - elseif actualtype == "string" and actual:sub (1, 1) == ":" then - actualtype = actual - elseif type (actual) == "table" and next (actual) == nil then - local matchstr = "," .. table_concat (expectedtypes, ",") .. "," - if actualtype == "table" and matchstr == ",#list," then - actualtype = "empty list" - elseif actualtype == "table" or matchstr:match ",#" then - actualtype = "empty " .. actualtype - end - end - - if index then - actualtype = actualtype .. " at index " .. _tostring (index) - end - - -- Tidy up expected types for display. - local expectedstr = expectedtypes - if type (expectedtypes) == "table" then - local t = {} - for i, v in _ipairs (expectedtypes) do - if v == "func" then - t[i] = "function" - elseif v == "bool" then - t[i] = "boolean" - elseif v == "any" then - t[i] = "any value" - elseif v == "file" then - t[i] = "FILE*" - elseif not index then - t[i] = v:match "(%S+) of %S+" or v - else - t[i] = v - end - end - expectedstr = (concat (t) .. " expected"): - gsub ("#table", "non-empty table"): - gsub ("#list", "non-empty list"): - gsub ("(%S+ of [^,%s]-)s? ", "%1s "): - gsub ("(%S+ of [^,%s]-)s?,", "%1s,"): - gsub ("(s, [^,%s]-)s? ", "%1s "): - gsub ("(s, [^,%s]-)s?,", "%1s,"): - gsub ("(of .-)s? or ([^,%s]-)s? ", "%1s or %2s ") - end - - return expectedstr .. ", got " .. actualtype -end - - ---- Strip trailing ellipsis from final argument if any, storing maximum --- number of values that can be matched directly in `t.maxvalues`. --- @tparam table t table to act on --- @string v element added to *t*, to match against ... suffix --- @treturn table *t* with ellipsis stripped and maxvalues field set -local function markdots (t, v) - return (string_gsub (v, "%.%.%.$", function () t.dots = true return "" end)) -end - - ---- Calculate permutations of type lists with and without [optionals]. --- @tparam table t a list of expected types by argument position --- @treturn table set of possible type lists -local function permute (t) - if t[#t] then t[#t] = string_gsub (t[#t], "%]%.%.%.$", "...]") end - - local p = {{}} - for i, v in _ipairs (t) do - local optional = string_match (v, "%[(.+)%]") - - if optional == nil then - -- Append non-optional type-spec to each permutation. - for b = 1, #p do - table_insert (p[b], markdots (p[b], v)) - end - else - -- Duplicate all existing permutations, and add optional type-spec - -- to the unduplicated permutations. - local o = #p - for b = 1, o do - p[b + o] = copy (p[b]) - table_insert (p[b], markdots (p[b], optional)) - end - end - end - return p -end - - -local function typesplit (types) - if type (types) == "string" then - types = split (string_gsub (types, "%s+or%s+", "|"), "%s*|%s*") - end - local r, seen, add_nil = {}, {}, false - for _, v in _ipairs (types) do - local m = string_match (v, "^%?(.+)$") - if m then - add_nil, v = true, m - end - if not seen[v] then - r[#r + 1] = v - seen[v] = true - end - end - if add_nil then - r[#r + 1] = "nil" - end - return r -end - - -local function projectuniq (fkey, tt) - -- project - local t = {} - for _, u in _ipairs (tt) do - t[#t + 1] = u[fkey] - end - - -- split and remove duplicates - local r, s = {}, {} - for _, e in _ipairs (t) do - for _, v in _ipairs (typesplit (e)) do - if s[v] == nil then - r[#r + 1], s[v] = v, true - end - end - end - return r -end - - -local function parsetypes (types) - local r, permutations = {}, permute (types) - for i = 1, #permutations[1] do - r[i] = projectuniq (i, permutations) - end - r.dots = permutations[1].dots - return r -end - - - -local argcheck, argscheck -- forward declarations - -if _DEBUG.argcheck then - - --- Return index of the first mismatch between types and values, or `nil`. - -- @tparam table typelist a list of expected types - -- @tparam table valuelist a table of arguments to compare - -- @treturn int|nil position of first mismatch in *typelist* - local function match (typelist, valuelist) - local n = #typelist - for i = 1, n do -- normal parameters - local ok = pcall (argcheck, "pcall", i, typelist[i], valuelist[i]) - if not ok then return i end - end - for i = n + 1, valuelist.n do -- additional values against final type - local ok = pcall (argcheck, "pcall", i, typelist[n], valuelist[i]) - if not ok then return i end - end - end - - - --- Compare *check* against type of *actual* - -- @string check extended type name expected - -- @param actual object being typechecked - -- @treturn boolean `true` if *actual* is of type *check*, otherwise - -- `false` - local function checktype (check, actual) - if check == "any" and actual ~= nil then - return true - elseif check == "file" and io_type (actual) == "file" then - return true - end - - local actualtype = type (actual) - if check == actualtype then - return true - elseif check == "bool" and actualtype == "boolean" then - return true - elseif check == "#table" then - if actualtype == "table" and next (actual) then - return true - end - elseif check == "function" or check == "func" then - if actualtype == "function" or - (getmetatable (actual) or {}).__call ~= nil - then - return true - end - elseif check == "int" then - if actualtype == "number" and actual == math_floor (actual) then - return true - end - elseif type (check) == "string" and check:sub (1, 1) == ":" then - if check == actual then - return true - end - end - - actualtype = _type (actual) - if check == actualtype then - return true - elseif check == "list" or check == "#list" then - if actualtype == "table" or actualtype == "List" then - local len, count = len (actual), 0 - local i = next (actual) - repeat - if i ~= nil then count = count + 1 end - i = next (actual, i) - until i == nil or count > len - if count == len and (check == "list" or count > 0) then - return true - end - end - elseif check == "object" then - if actualtype ~= "table" and type (actual) == "table" then - return true - end - end - - return false - end - - - local function empty (t) return not next (t) end - - -- Pattern to normalize: [types...] to [types]... - local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" - - --- Diagnose mismatches between *valuelist* and type *permutations*. - -- @tparam table valuelist list of actual values to be checked - -- @tparam table argt table of precalculated values and handler functiens - local function diagnose (valuelist, argt) - local permutations = argt.permutations - - local bestmismatch, t = 0 - for i, typelist in _ipairs (permutations) do - local mismatch = match (typelist, valuelist) - if mismatch == nil then - bestmismatch, t = nil, nil - break -- every *valuelist* matched types from this *typelist* - elseif mismatch > bestmismatch then - bestmismatch, t = mismatch, permutations[i] - end - end - - if bestmismatch ~= nil then - -- Report an error for all possible types at bestmismatch index. - local i, expected = bestmismatch - if t.dots and i > #t then - expected = typesplit (t[#t]) - else - expected = projectuniq (i, permutations) - end - - -- This relies on the `permute()` algorithm leaving the longest - -- possible permutation (with dots if necessary) at permutations[1]. - local typelist = permutations[1] - - -- For "container of things", check all elements are a thing too. - if typelist[i] then - local check, contents = string_match (typelist[i], "^(%S+) of (%S-)s?$") - if contents and type (valuelist[i]) == "table" then - for k, v in _pairs (valuelist[i]) do - if not checktype (contents, v) then - argt.badtype (i, extramsg_mismatch (expected, v, k), 3) - end - end - end - end - - -- Otherwise the argument type itself was mismatched. - if t.dots or #t >= valuelist.n then - argt.badtype (i, extramsg_mismatch (expected, valuelist[i]), 3) - end - end - - local n, t = valuelist.n, t or permutations[1] - if t and t.dots == nil and n > #t then - argt.badtype (#t + 1, extramsg_toomany (argt.bad, #t, n), 3) - end - end - - - function argcheck (name, i, expected, actual, level) - level = level or 2 - expected = typesplit (expected) - - -- Check actual has one of the types from expected - local ok = false - for _, expect in _ipairs (expected) do - local check, contents = string_match (expect, "^(%S+) of (%S-)s?$") - check = check or expect - - -- Does the type of actual check out? - ok = checktype (check, actual) - - -- For "table of things", check all elements are a thing too. - if ok and contents and type (actual) == "table" then - for k, v in _pairs (actual) do - if not checktype (contents, v) then - argerror (name, i, extramsg_mismatch (expected, v, k), level + 1) - end - end - end - if ok then break end - end - - if not ok then - argerror (name, i, extramsg_mismatch (expected, actual), level + 1) - end - end - - - -- Pattern to extract: fname ([types]?[, types]*) - local args_pat = "^%s*([%w_][%.%:%d%w_]*)%s*%(%s*(.*)%s*%)" - - function argscheck (decl, inner) - -- Parse "fname (argtype, argtype, argtype...)". - local fname, argtypes = string_match (decl, args_pat) - if argtypes == "" then - argtypes = {} - elseif argtypes then - argtypes = split (argtypes, "%s*,%s*") - else - fname = string_match (decl, "^%s*([%w_][%.%:%d%w_]*)") - end - - -- Precalculate vtables once to make multiple calls faster. - local input, output = { - bad = "argument", - badtype = function (i, extramsg, level) - level = level or 1 - argerror (fname, i, extramsg, level + 1) - end, - permutations = permute (argtypes), - } - - -- Parse "... => returntype, returntype, returntype...". - local returntypes = string_match (decl, "=>%s*(.+)%s*$") - if returntypes then - local i, permutations = 0, {} - for _, group in _ipairs (split (returntypes, "%s+or%s+")) do - returntypes = split (group, ",%s*") - for _, t in _ipairs (permute (returntypes)) do - i = i + 1 - permutations[i] = t - end - end - - -- Ensure the longest permutation is first in the list. - table_sort (permutations, function (a, b) return #a > #b end) - - output = { - bad = "result", - badtype = function (i, extramsg, level) - level = level or 1 - resulterror (fname, i, extramsg, level + 1) - end, - permutations = permutations, - } - end - - return function (...) - local argt = pack (...) - - -- Don't check type of self if fname has a ':' in it. - if string_find (fname, ":") then - table_remove (argt, 1) - argt.n = argt.n - 1 - end - - -- Diagnose bad inputs. - diagnose (argt, input) - - -- Propagate outer environment to inner function. - local x = math_max -- ??? FIXME: getfenv(1) fails if we remove this ??? - _setfenv (inner, _getfenv (1)) - - -- Execute. - local results = pack (inner (...)) - - -- Diagnose bad outputs. - if returntypes then - diagnose (results, output) - end - - return table_unpack (results, 1, results.n) - end - end - -else - - -- Turn off argument checking if _DEBUG is false, or a table containing - -- a false valued `argcheck` field. - - argcheck = nop - argscheck = function (decl, inner) return inner end - -end - - -return { - --- Check the type of an argument against expected types. - -- Equivalent to luaL_argcheck in the Lua C API. - -- - -- Call `argerror` if there is a type mismatch. - -- - -- Argument `actual` must match one of the types from in `expected`, each - -- of which can be the name of a primitive Lua type, a stdlib object type, - -- or one of the special options below: - -- - -- #table accept any non-empty table - -- any accept any non-nil argument type - -- file accept an open file object - -- function accept a function, or object with a __call metamethod - -- int accept an integer valued number - -- list accept a table where all keys are a contiguous 1-based integer range - -- #list accept any non-empty list - -- object accept any std.Object derived type - -- :foo accept only the exact string ":foo", works for any :-prefixed string - -- - -- The `:foo` format allows for type-checking of self-documenting - -- boolean-like constant string parameters predicated on `nil` versus - -- `:option` instead of `false` versus `true`. Or you could support - -- both: - -- - -- argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa) - -- - -- A very common pattern is to have a list of possible types including - -- "nil" when the argument is optional. Rather than writing long-hand - -- as above, prepend a question mark to the list of types and omit the - -- explicit "nil" entry: - -- - -- argcheck ("table.copy", 2, "?boolean|:nometa", predicate) - -- - -- Normally, you should not need to use the `level` parameter, as the - -- default is to blame the caller of the function using `argcheck` in - -- error messages; which is almost certainly what you want. - -- @function argcheck - -- @string name function to blame in error message - -- @int i argument number to blame in error message - -- @string expected specification for acceptable argument types - -- @param actual argument passed - -- @int[opt=2] level call stack level to blame for the error - -- @usage - -- local function case (with, branches) - -- argcheck ("std.functional.case", 2, "#table", branches) - -- ... - argcheck = argcheck, - - --- Raise a bad argument error. - -- Equivalent to luaL_argerror in the Lua C API. This function does not - -- return. The `level` argument behaves just like the core `error` - -- function. - -- @function argerror - -- @string name function to callout in error message - -- @int i argument number - -- @string[opt] extramsg additional text to append to message inside parentheses - -- @int[opt=1] level call stack level to blame for the error - -- @see resulterror - -- @see extramsg_mismatch - -- @usage - -- local function slurp (file) - -- local h, err = input_handle (file) - -- if h == nil then argerror ("std.io.slurp", 1, err, 2) end - -- ... - argerror = argerror, - - --- Wrap a function definition with argument type and arity checking. - -- In addition to checking that each argument type matches the corresponding - -- element in the *types* table with `argcheck`, if the final element of - -- *types* ends with an ellipsis, remaining unchecked arguments are checked - -- against that type: - -- - -- format = argscheck ("string.format (string, ?any...)", string.format) - -- - -- A colon in the function name indicates that the argument type list does - -- not have a type for `self`: - -- - -- format = argscheck ("string:format (?any...)", string.format) - -- - -- If an argument can be omitted entirely, then put its type specification - -- in square brackets: - -- - -- insert = argscheck ("table.insert (table, [int], ?any)", table.insert) - -- - -- Similarly return types can be checked with the same list syntax as - -- arguments: - -- - -- len = argscheck ("string.len (string) => int", string.len) - -- - -- Additionally, variant return type lists can be listed like this: - -- - -- open = argscheck ("io.open (string, ?string) => file or nil, string", - -- io.open) - -- - -- @function argscheck - -- @string decl function type declaration string - -- @func inner function to wrap with argument checking - -- @usage - -- local case = argscheck ("std.functional.case (?any, #table) => [any...]", - -- function (with, branches) - -- ... - -- end) - argscheck = argscheck, - - --- Format a type mismatch error. - -- @function extramsg_mismatch - -- @string expected a pipe delimited list of matchable types - -- @param actual the actual argument to match with - -- @number[opt] index erroring container element index - -- @treturn string formatted *extramsg* for this mismatch for @{argerror} - -- @see argerror - -- @see resulterror - -- @usage - -- if fmt ~= nil and type (fmt) ~= "string" then - -- argerror ("format", 1, extramsg_mismatch ("?string", fmt)) - -- end - extramsg_mismatch = function (expected, actual, index) - return extramsg_mismatch (typesplit (expected), actual, index) - end, - - --- Format a too many things error. - -- @function extramsg_toomany - -- @string bad the thing there are too many of - -- @int expected maximum number of *bad* things expected - -- @int actual actual number of *bad* things that triggered the error - -- @see argerror - -- @see resulterror - -- @see extramsg_mismatch - -- @usage - -- if select ("#", ...) > 7 then - -- argerror ("sevenses", 8, extramsg_toomany ("argument", 7, select ("#", ...))) - -- end - extramsg_toomany = extramsg_toomany, - - --- Compact permutation list into a list of valid types at each argument. - -- Eliminate bracketed types by combining all valid types at each position - -- for all permutations of *typelist*. - -- @function parsetypes - -- @tparam list types a normalized list of type names - -- @treturn list valid types for each positional parameter - parsetypes = parsetypes, - - --- Raise a bad result error. - -- Like @{argerror} for bad results. This function does not - -- return. The `level` argument behaves just like the core `error` - -- function. - -- @function resulterror - -- @string name function to callout in error message - -- @int i result number - -- @string[opt] extramsg additional text to append to message inside parentheses - -- @int[opt=1] level call stack level to blame for the error - -- @usage - -- local function slurp (file) - -- ... - -- if type (result) ~= "string" then resulterror ("std.io.slurp", 1, err, 2) end - resulterror = resulterror, - - --- Split a typespec string into a table of normalized type names. - -- @function typesplit - -- @tparam string|table either `"?bool|:nometa"` or `{"boolean", ":nometa"}` - -- @treturn table a new list with duplicates removed and leading "?"s - -- replaced by a "nil" element - typesplit = typesplit, -} diff --git a/local.mk b/local.mk index 7d9f304..a1ff504 100644 --- a/local.mk +++ b/local.mk @@ -82,7 +82,6 @@ dist_luastd_DATA = \ lib/std/table.lua \ lib/std/tree.lua \ lib/std/tuple.lua \ - lib/std/typing.lua \ lib/std/version.lua \ $(NOTHING_ELSE) @@ -161,7 +160,6 @@ dist_docfunctional_DATA += \ dist_docmodules_DATA += \ $(docmodules).maturity.html \ - $(docmodules).typing.html \ $(NOTHING_ELSE) dist_docobjects_DATA += \ diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 8afff15..61590c8 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -11,11 +11,15 @@ specify std.container: - describe construction: - context with table _init: - it diagnoses missing arguments: | - expect (Container ()). - to_raise "bad argument #1 to 'Container' (table expected, got no value)" + if have_typecheck then + expect (Container ()). + to_raise "bad argument #1 to 'Container' (table expected, got no value)" + end - it diagnoses too many arguments: | - expect (Container ({}, false)). - to_raise "bad argument #2 to 'Container' (no more than 1 argument expected, got 2)" + if have_typecheck then + expect (Container ({}, false)). + to_raise "bad argument #2 to 'Container' (no more than 1 argument expected, got 2)" + end - context with function _init: - before: Thing = Container { _type = "Thing", _init = function (obj) return obj end } diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 15acb17..e9399a0 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -244,18 +244,20 @@ specify std.io: - context with bad arguments: | badargs.diagnose (f, "std.io.readlines (?file|string)") - 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 - } + 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) @@ -297,18 +299,20 @@ specify std.io: - context with bad arguments: | badargs.diagnose (f, "std.io.slurp (?file|string)") - 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 - } + 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) @@ -414,14 +418,22 @@ specify std.io: - context with bad arguments: - 'it diagnoses argument #1 type not FILE*, string, number or nil': - expect (f (false)).to_raise (badarg (1, "?file|string|number", "boolean")) + 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': - expect (f (1, false)).to_raise (badarg (2, "string|number", "boolean")) + 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': - expect (f (1, 2, false)).to_raise (badarg (3, "string|number", "boolean")) + 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 () - expect (f (closed)).to_raise (badarg (1, "?file|string|number", "closed file")) + 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" diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 06d65ad..64a36c4 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -1,8 +1,12 @@ -local inprocess = require "specl.inprocess" -local hell = require "specl.shell" -local std = require "specl.std" +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" -badargs = require "specl.badargs" local top_srcdir = os.getenv "top_srcdir" or "." local top_builddir = os.getenv "top_builddir" or "." @@ -67,6 +71,13 @@ 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 i, want = i - 1, i end -- numbers only for narg error diff --git a/specs/specs.mk b/specs/specs.mk index 7a0b00f..b84aaeb 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -27,7 +27,6 @@ specl_SPECS = \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ $(srcdir)/specs/tuple_spec.yaml \ - $(srcdir)/specs/typing_spec.yaml \ $(srcdir)/specs/std_spec.yaml \ $(NOTHING_ELSE) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index c7f01c4..49300fd 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -163,8 +163,10 @@ specify std.string: newstring = f (subject) expect (subject).to_be (original) - "it diagnoses non-string arguments": - expect (f ()).to_raise ("string expected") - expect (f {"a table"}).to_raise ("string expected") + if typecheck then + expect (f ()).to_raise ("string expected") + expect (f {"a table"}).to_raise ("string expected") + end - describe finds: @@ -687,5 +689,7 @@ specify std.string: 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: - expect (f ()).to_raise ("string expected") - expect (f {"a table"}).to_raise ("string expected") + if have_typecheck then + expect (f ()).to_raise ("string expected") + expect (f {"a table"}).to_raise ("string expected") + end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index aa2b865..b31b8e2 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -30,7 +30,7 @@ specify std.table: - it contains apis from the core table table: apis = {} for _, v in ipairs (extend_base) do - if v ~= "pack" or M.pack ~= table.pack then + if M[v] ~= table[v] then apis[#apis + 1] = v end end diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index 18a7ab0..e7ed66c 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -57,8 +57,10 @@ specify std.tree: expect (subject).to_equal (target) expect (subject).to_be (subject) - it diagnoses non-table arguments: - expect (f ()).to_raise ("table expected") - expect (f "foo").to_raise ("table expected") + if have_typecheck then + expect (f ()).to_raise ("table expected") + expect (f "foo").to_raise ("table expected") + end - describe ileaves: @@ -92,8 +94,10 @@ specify std.tree: end expect (l).to_equal {"one", "three", "five"} - it diagnoses non-table arguments: - expect (f ()).to_raise ("table expected") - expect (f "string").to_raise ("table expected") + if have_typecheck then + expect (f ()).to_raise ("table expected") + expect (f "string").to_raise ("table expected") + end - describe inodes: @@ -178,8 +182,10 @@ specify std.tree: {"leaf", {2}, subject[2]}, -- five, {"join", {}, subject}} -- } - it diagnoses non-table arguments: - expect (f ()).to_raise ("table expected") - expect (f "string").to_raise ("table expected") + if have_typecheck then + expect (f ()).to_raise ("table expected") + expect (f "string").to_raise ("table expected") + end - describe leaves: @@ -215,8 +221,10 @@ specify std.tree: expect (l).to_contain. a_permutation_of {"one", 2, "three", 4, "bar", "five"} - it diagnoses non-table arguments: - expect (f ()).to_raise ("table expected") - expect (f "string").to_raise ("table expected") + if have_typecheck then + expect (f ()).to_raise ("table expected") + expect (f "string").to_raise ("table expected") + end - describe merge: @@ -245,8 +253,10 @@ specify std.tree: expect (m.k3).to_be (t2.k3) expect (m.k3).not_to_be (original.k3) - it diagnoses non-table arguments: - expect (f (nil, {})).to_raise ("table expected") - expect (f ({}, nil)).to_raise ("table expected") + if have_typecheck then + expect (f (nil, {})).to_raise ("table expected") + expect (f ({}, nil)).to_raise ("table expected") + end - describe nodes: @@ -354,8 +364,10 @@ specify std.tree: {"leaf", {3}, subject[{3}]}, -- 3rd, {"join", {}, subject[{}]}} -- } - it diagnoses non-table arguments: - expect (f ()).to_raise ("table expected") - expect (f "string").to_raise ("table expected") + if have_typecheck then + expect (f ()).to_raise ("table expected") + expect (f "string").to_raise ("table expected") + end - describe __index: diff --git a/specs/typing_spec.yaml b/specs/typing_spec.yaml deleted file mode 100644 index 4dd2f0f..0000000 --- a/specs/typing_spec.yaml +++ /dev/null @@ -1,840 +0,0 @@ -before: - this_module = "std.typing" - - M = require (this_module) - -specify std.typing: -- describe require: - - it does not perturb the global namespace: - expect (show_apis {added_to="_G", by="std.typing"}). - to_equal {} - - -- describe resulterror: - - before: | - function mkstack (level) - return string.format ([[ - _DEBUG = true -- line 1 - local typing = require "std.typing" -- line 2 - function ohnoes () -- line 3 - typing.resulterror ("ohnoes", 1, nil, %s) -- line 4 - end -- line 5 - function caller () -- line 6 - local r = ohnoes () -- line 7 - return "not a tail call" -- line 8 - end -- line 9 - caller () -- line 10 - ]], tostring (level)) - end - - f = M.resulterror - - - it blames the call site by default: | - expect (luaproc (mkstack ())).to_contain_error ":4: bad result" - - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":4: bad result" - expect (luaproc (mkstack (2))).to_contain_error ":7: bad result" - - it reports the calling function name: - expect (f ('expect', 1)).to_raise "'expect'" - - it reports the argument number: | - expect (f ('expect', 12345)).to_raise "#12345" - - it reports extra message in parentheses: - expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" - - -- describe argerror: - - before: | - function mkstack (level) - return string.format ([[ - _DEBUG = true -- line 1 - local typing = require "std.typing" -- line 2 - function ohnoes () -- line 3 - typing.argerror ("ohnoes", 1, nil, %s) -- line 4 - end -- line 5 - function caller () -- line 6 - local r = ohnoes () -- line 7 - return "not a tail call" -- line 8 - end -- line 9 - caller () -- line 10 - ]], tostring (level)) - end - - f, badarg = init (M, this_module, "argerror") - - - it blames the call site by default: | - expect (luaproc (mkstack ())).to_contain_error ":4: bad argument" - - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" - expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" - - it reports the calling function name: - expect (f ('expect', 1)).to_raise "'expect'" - - it reports the argument number: | - expect (f ('expect', 12345)).to_raise "#12345" - - it reports extra message in parentheses: - expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" - - -- describe argcheck: - - before: | - Object = require "std.object".prototype - List = Object { _type = "List" } - Foo = Object { _type = "Foo" } - - function fn (...) return M.argcheck ('expect', 1, ...) end - - function mkstack (level, debugp) - return string.format ([[ - _DEBUG = %s -- line 1 - local typing = require "std.typing" -- line 2 - function ohnoes (t) -- line 3 - typing.argcheck ("ohnoes", 1, "table", t, %s) -- line 4 - end -- line 5 - function caller () -- line 6 - local r = ohnoes "not a table" -- line 7 - return "not a tail call" -- line 8 - end -- line 9 - caller () -- line 10 - ]], tostring (debugp), tostring (level)) - end - - f, badarg = init (M, this_module, "argcheck") - - - it blames the calling function by default: | - expect (luaproc (mkstack ())).to_contain_error ":7: bad argument" - - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":4: bad argument" - expect (luaproc (mkstack (2))).to_contain_error ":7: bad argument" - expect (luaproc (mkstack (3))).to_contain_error ":10: bad argument" - - it can be disabled by setting _DEBUG to false: - expect (luaproc (mkstack (nil, false))). - not_to_contain_error "bad argument" - - it can be disabled by setting _DEBUG.argcheck to false: - expect (luaproc (mkstack (nil, "{ argcheck = false }"))). - not_to_contain_error "bad argument" - - it is not disabled by setting _DEBUG.argcheck to true: - expect (luaproc (mkstack (nil, "{ argcheck = true }"))). - to_contain_error "bad argument" - - it is not disabled by leaving _DEBUG.argcheck unset: - expect (luaproc (mkstack (nil, "{}"))). - to_contain_error "bad argument" - - - context with primitives: - - it diagnoses missing types: - expect (fn ("bool", nil)).to_raise "boolean expected, got no value" - expect (fn ("boolean", nil)).to_raise "boolean expected, got no value" - expect (fn ("file", nil)).to_raise "FILE* expected, got no value" - expect (fn ("number", nil)).to_raise "number expected, got no value" - expect (fn ("string", nil)).to_raise "string expected, got no value" - expect (fn ("table", nil)).to_raise "table expected, got no value" - - it diagnoses mismatched types: - expect (fn ("bool", {0})).to_raise "boolean expected, got table" - expect (fn ("boolean", {0})).to_raise "boolean expected, got table" - expect (fn ("file", {0})).to_raise "FILE* expected, got table" - expect (fn ("number", {0})).to_raise "number expected, got table" - expect (fn ("string", {0})).to_raise "string expected, got table" - expect (fn ("table", false)).to_raise "table expected, got boolean" - - it matches types: - expect (fn ("bool", true)).not_to_raise "any error" - expect (fn ("boolean", true)).not_to_raise "any error" - expect (fn ("file", io.stderr)).not_to_raise "any error" - expect (fn ("number", 1)).not_to_raise "any error" - expect (fn ("string", "s")).not_to_raise "any error" - expect (fn ("table", {})).not_to_raise "any error" - expect (fn ("table", require "std.object")).not_to_raise "any error" - - - context with int: - - it diagnoses missing types: - expect (fn ("int", nil)).to_raise "int expected, got no value" - - it diagnoses mismatched types: - expect (fn ("int", false)).to_raise "int expected, got boolean" - expect (fn ("int", 1.234)).to_raise "int expected, got number" - expect (fn ("int", 1234e-3)).to_raise "int expected, got number" - - it matches types: - expect (fn ("int", 1)).not_to_raise "any error" - expect (fn ("int", 1.0)).not_to_raise "any error" - expect (fn ("int", 0x1234)).not_to_raise "any error" - expect (fn ("int", 1.234e3)).not_to_raise "any error" - - context with constant string: - - it diagnoses missing types: - expect (fn (":foo", nil)).to_raise ":foo expected, got no value" - - it diagnoses mismatched types: - expect (fn (":foo", false)).to_raise ":foo expected, got boolean" - expect (fn (":foo", ":bar")).to_raise ":foo expected, got :bar" - expect (fn (":foo", "foo")).to_raise ":foo expected, got string" - - it matches types: - expect (fn (":foo", ":foo")).not_to_raise "any error" - - context with callable types: - - it diagnoses missing types: - expect (fn ("func", nil)).to_raise "function expected, got no value" - expect (fn ("function", nil)).to_raise "function expected, got no value" - - it diagnoses mismatched types: - expect (fn ("func", {0})).to_raise "function expected, got table" - expect (fn ("function", {0})).to_raise "function expected, got table" - - it matches types: - expect (fn ("func", function () end)).not_to_raise "any error" - expect (fn ("func", setmetatable ({}, {__call = function () end}))). - not_to_raise "any error" - expect (fn ("function", function () end)).not_to_raise "any error" - expect (fn ("function", setmetatable ({}, {__call = function () end}))). - not_to_raise "any error" - - context with table of homogenous elements: - - it diagnoses missing types: - expect (fn ("table of boolean", nil)). - to_raise "table expected, got no value" - expect (fn ("table of booleans", nil)). - to_raise "table expected, got no value" - - it diagnoses mismatched types: - expect (fn ("table of file", io.stderr)). - to_raise "table expected, got file" - expect (fn ("table of files", io.stderr)). - to_raise "table expected, got file" - - it diagnoses mismatched element types: - expect (fn ("table of number", {false})). - to_raise "table of numbers expected, got boolean at index 1" - expect (fn ("table of numbers", {1, 2, "3"})). - to_raise "table of numbers expected, got string at index 3" - expect (fn ("table of numbers", {a=1, b=2, c="3"})). - to_raise "table of numbers expected, got string at index c" - - it matches types: - expect (fn ("table of string", {})).not_to_raise "any error" - expect (fn ("table of string", {"foo"})).not_to_raise "any error" - expect (fn ("table of string", {"f", "o", "o"})).not_to_raise "any error" - expect (fn ("table of string", {b="b", a="a", r="r"})).not_to_raise "any error" - - context with non-empty table types: - - it diagnoses missing types: - expect (fn ("#table", nil)). - to_raise "non-empty table expected, got no value" - - it diagnoses mismatched types: - expect (fn ("#table", false)). - to_raise "non-empty table expected, got boolean" - expect (fn ("#table", {})). - to_raise "non-empty table expected, got empty table" - - it matches types: - expect (fn ("#table", {0})).not_to_raise "any error" - - context with non-empty table of homogenous elements: - - it diagnoses missing types: - expect (fn ("#table of boolean", nil)). - to_raise "non-empty table expected, got no value" - expect (fn ("#table of booleans", nil)). - to_raise "non-empty table expected, got no value" - - it diagnoses mismatched types: - expect (fn ("#table of file", {})). - to_raise "non-empty table expected, got empty table" - expect (fn ("#table of file", io.stderr)). - to_raise "non-empty table expected, got file" - - it diagnoses mismatched element types: - expect (fn ("#table of number", {false})). - to_raise "non-empty table of numbers expected, got boolean at index 1" - expect (fn ("#table of numbers", {1, 2, "3"})). - to_raise "non-empty table of numbers expected, got string at index 3" - expect (fn ("#table of numbers", {a=1, b=2, c="3"})). - to_raise "non-empty table of numbers expected, got string at index c" - - it matches types: - expect (fn ("#table of string", {"foo"})).not_to_raise "any error" - expect (fn ("#table of string", {"f", "o", "o"})).not_to_raise "any error" - expect (fn ("#table of string", {b="b", a="a", r="r"})).not_to_raise "any error" - - context with list: - - it diagnonses missing types: - expect (fn ("list", nil)). - to_raise "list expected, got no value" - - it diagnoses mismatched types: - expect (fn ("list", false)). - to_raise "list expected, got boolean" - expect (fn ("list", {foo=1})). - to_raise "list expected, got table" - expect (fn ("list", Object)). - to_raise "list expected, got Object" - - it matches types: - expect (fn ("list", {})).not_to_raise "any error" - expect (fn ("list", {1})).not_to_raise "any error" - - context with list of homogenous elements: - - it diagnoses missing types: - expect (fn ("list of boolean", nil)). - to_raise "list expected, got no value" - expect (fn ("list of booleans", nil)). - to_raise "list expected, got no value" - - it diagnoses mismatched types: - expect (fn ("list of file", io.stderr)). - to_raise "list expected, got file" - expect (fn ("list of files", io.stderr)). - to_raise "list expected, got file" - expect (fn ("list of files", {file=io.stderr})). - to_raise "list expected, got table" - - it diagnoses mismatched element types: - expect (fn ("list of number", {false})). - to_raise "list of numbers expected, got boolean at index 1" - expect (fn ("list of numbers", {1, 2, "3"})). - to_raise "list of numbers expected, got string at index 3" - - it matches types: - expect (fn ("list of string", {})).not_to_raise "any error" - expect (fn ("list of string", {"foo"})).not_to_raise "any error" - expect (fn ("list of string", {"f", "o", "o"})).not_to_raise "any error" - - context with non-empty list: - - it diagnonses missing types: - expect (fn ("#list", nil)). - to_raise "non-empty list expected, got no value" - - it diagnoses mismatched types: - expect (fn ("#list", false)). - to_raise "non-empty list expected, got boolean" - expect (fn ("#list", {})). - to_raise "non-empty list expected, got empty list" - expect (fn ("#list", {foo=1})). - to_raise "non-empty list expected, got table" - expect (fn ("#list", Object)). - to_raise "non-empty list expected, got empty Object" - expect (fn ("#list", List {})). - to_raise "non-empty list expected, got empty List" - - it matches types: - expect (fn ("#list", {1})).not_to_raise "any error" - - context with non-empty list of homogenous elements: - - it diagnoses missing types: - expect (fn ("#list of boolean", nil)). - to_raise "non-empty list expected, got no value" - expect (fn ("#list of booleans", nil)). - to_raise "non-empty list expected, got no value" - - it diagnoses mismatched types: - expect (fn ("#list of file", {})). - to_raise "non-empty list expected, got empty table" - expect (fn ("#list of file", io.stderr)). - to_raise "non-empty list expected, got file" - expect (fn ("#list of files", {file=io.stderr})). - to_raise "non-empty list expected, got table" - - it diagnoses mismatched element types: - expect (fn ("#list of number", {false})). - to_raise "non-empty list of numbers expected, got boolean at index 1" - expect (fn ("#list of numbers", {1, 2, "3"})). - to_raise "non-empty list of numbers expected, got string at index 3" - - it matches types: - expect (fn ("#list of string", {"foo"})).not_to_raise "any error" - expect (fn ("#list of string", {"f", "o", "o"})).not_to_raise "any error" - - context with container: - - it diagnoses missing types: - expect (fn ("List of boolean", nil)). - to_raise "List expected, got no value" - expect (fn ("List of booleans", nil)). - to_raise "List expected, got no value" - - it diagnoses mismatched types: - expect (fn ("List of file", io.stderr)). - to_raise "List expected, got file" - expect (fn ("List of files", io.stderr)). - to_raise "List expected, got file" - expect (fn ("List of files", {file=io.stderr})). - to_raise "List expected, got table" - - it diagnoses mismatched element types: - expect (fn ("List of number", List {false})). - to_raise "List of numbers expected, got boolean at index 1" - expect (fn ("List of numbers", List {1, 2, "3"})). - to_raise "List of numbers expected, got string at index 3" - - it matches types: - expect (fn ("list of string", List {})).not_to_raise "any error" - expect (fn ("list of string", List {"foo"})).not_to_raise "any error" - expect (fn ("list of string", List {"f", "o", "o"})).not_to_raise "any error" - - context with object: - - it diagnoses missing types: - expect (fn ("object", nil)).to_raise "object expected, got no value" - expect (fn ("Object", nil)).to_raise "Object expected, got no value" - expect (fn ("Foo", nil)).to_raise "Foo expected, got no value" - expect (fn ("any", nil)).to_raise "any value expected, got no value" - - it diagnoses mismatched types: - expect (fn ("object", {0})).to_raise "object expected, got table" - expect (fn ("Object", {0})).to_raise "Object expected, got table" - expect (fn ("object", {_type="Object"})).to_raise "object expected, got table" - expect (fn ("Object", {_type="Object"})).to_raise "Object expected, got table" - expect (fn ("Object", Foo)).to_raise "Object expected, got Foo" - expect (fn ("Foo", {0})).to_raise "Foo expected, got table" - expect (fn ("Foo", Object)).to_raise "Foo expected, got Object" - - it matches types: - expect (fn ("object", Object)).not_to_raise "any error" - expect (fn ("object", Object {})).not_to_raise "any error" - expect (fn ("object", Foo)).not_to_raise "any error" - expect (fn ("object", Foo {})).not_to_raise "any error" - - it matches anything: - expect (fn ("any", true)).not_to_raise "any error" - expect (fn ("any", {})).not_to_raise "any error" - expect (fn ("any", Object)).not_to_raise "any error" - expect (fn ("any", Foo {})).not_to_raise "any error" - - context with a list of valid types: - - it diagnoses missing elements: - expect (fn ("string|table", nil)). - to_raise "string or table expected, got no value" - expect (fn ("string|list|#table", nil)). - to_raise "string, list or non-empty table expected, got no value" - expect (fn ("string|number|list|object", nil)). - to_raise "string, number, list or object expected, got no value" - - it diagnoses mismatched elements: - expect (fn ("string|table", false)). - to_raise "string or table expected, got boolean" - expect (fn ("string|#table", {})). - to_raise "string or non-empty table expected, got empty table" - expect (fn ("string|number|#list|object", {})). - to_raise "string, number, non-empty list or object expected, got empty table" - - it matches any type from a list: - expect (fn ("string|table", "foo")).not_to_raise "any error" - expect (fn ("string|table", {})).not_to_raise "any error" - expect (fn ("string|table", {0})).not_to_raise "any error" - expect (fn ("table|table", {})).not_to_raise "any error" - expect (fn ("#table|table", {})).not_to_raise "any error" - - context with an optional type element: - - it diagnoses mismatched elements: - expect (fn ("?boolean", "string")). - to_raise "boolean or nil expected, got string" - expect (fn ("?boolean|:symbol", {})). - to_raise "boolean, :symbol or nil expected, got empty table" - - it matches nil against a single type: - expect (fn ("?any", nil)).not_to_raise "any error" - expect (fn ("?boolean", nil)).not_to_raise "any error" - expect (fn ("?string", nil)).not_to_raise "any error" - - it matches nil against a list of types: - expect (fn ("?boolean|table", nil)).not_to_raise "any error" - expect (fn ("?string|table", nil)).not_to_raise "any error" - expect (fn ("?table|#table", nil)).not_to_raise "any error" - expect (fn ("?#table|table", nil)).not_to_raise "any error" - - it matches nil against a list of optional types: - expect (fn ("?boolean|?table", nil)).not_to_raise "any error" - expect (fn ("?string|?table", nil)).not_to_raise "any error" - expect (fn ("?table|?#table", nil)).not_to_raise "any error" - expect (fn ("?#table|?table", nil)).not_to_raise "any error" - - it matches any named type: - expect (fn ("?any", false)).not_to_raise "any error" - expect (fn ("?boolean", false)).not_to_raise "any error" - expect (fn ("?string", "string")).not_to_raise "any error" - - it matches any type from a list: - expect (fn ("?boolean|table", {})).not_to_raise "any error" - expect (fn ("?string|table", {0})).not_to_raise "any error" - expect (fn ("?table|#table", {})).not_to_raise "any error" - expect (fn ("?#table|table", {})).not_to_raise "any error" - - it matches any type from a list with several optional specifiers: - expect (fn ("?boolean|?table", {})).not_to_raise "any error" - expect (fn ("?string|?table", {0})).not_to_raise "any error" - expect (fn ("?table|?table", {})).not_to_raise "any error" - expect (fn ("?#table|?table", {})).not_to_raise "any error" - - -- describe argscheck: - - before: | - function mkstack (name, spec) - return string.format ([[ - local argscheck = require "std.typing".argscheck -- line 1 - local function caller () -- line 2 - argscheck ("%s", function () end) -- line 3 - end -- line 4 - caller () -- line 5 - ]], tostring (name), tostring (spec)) - end - - f = M.argscheck - - mkmagic = function () return "MAGIC" end - wrapped = f ("inner ()", mkmagic) - - _, badarg, badresult = init (M, "", "inner") - id = function (...) return ... end - - - it returns the wrapped function: - expect (wrapped).not_to_be (inner) - expect (wrapped ()).to_be "MAGIC" - - it does not wrap the function when _ARGCHECK is disabled: | - script = [[ - _DEBUG = false - local debug = require "std.debug_init" - local argscheck = require "std.typing".argscheck - local function inner () return "MAGIC" end - local wrapped = argscheck ("inner (?any)", inner) - os.exit (wrapped == inner and 0 or 1) - ]] - expect (luaproc (script)).to_succeed () - - - context when checking zero argument function: - - it diagnoses too many arguments: - expect (wrapped (false)).to_raise (badarg (1)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - - - context when checking single argument function: - - before: - wrapped = f ("inner (#table)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "non-empty table")) - - it diagnoses wrong argument types: - expect (wrapped {}).to_raise (badarg (1, "non-empty table", "empty table")) - - it diagnoses too many arguments: - expect (wrapped ({1}, 2, nop, "", false)).to_raise (badarg (1, 5)) - - it accepts correct argument types: - expect (wrapped ({1})).to_be "MAGIC" - - - context when checking multi-argument function: - - before: - wrapped = f ("inner (table, function)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "table")) - expect (wrapped ({})).to_raise (badarg (2, "function")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "table", "boolean")) - expect (wrapped ({}, false)).to_raise (badarg (2, "function", "boolean")) - - it diagnoses too many arguments: - expect (wrapped ({}, nop, false)).to_raise (badarg (3)) - - it accepts correct argument types: - expect (wrapped ({}, nop)).to_be "MAGIC" - - - context when checking nil argument function: - - before: - wrapped = f ("inner (?int, string)", mkmagic) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) - expect (wrapped (1, false)).to_raise (badarg (2, "string", "boolean")) - expect (wrapped (nil, false)).to_raise (badarg (2, "string", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, "foo", nop)).to_raise (badarg (3)) - expect (wrapped (nil, "foo", nop)).to_raise (badarg (3)) - - it accepts correct argument types: - expect (wrapped (1, "foo")).to_be "MAGIC" - expect (wrapped (nil, "foo")).to_be "MAGIC" - - - context when checking optional multi-argument function: - - before: - wrapped = f ("inner ([int], string)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "int or string")) - expect (wrapped (1)).to_raise (badarg (2, "string")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int or string", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, "two", nop)).to_raise (badarg (3)) - - it accepts correct argument types: - expect (wrapped ("two")).to_be "MAGIC" - expect (wrapped (1, "two")).to_be "MAGIC" - - - context when checking final optional multi-argument function: - - before: - wrapped = f ("inner (?any, ?string, [any])", mkmagic) - - it diagnoses wrong argument types: - expect (wrapped (1, false)).to_raise (badarg (2, "string or nil", "boolean")) - expect (wrapped (nil, false)).to_raise (badarg (2, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, "two", 3, false)).to_raise (badarg (4)) - expect (wrapped (nil, "two", 3, false)).to_raise (badarg (4)) - expect (wrapped (1, nil, 3, false)).to_raise (badarg (4)) - expect (wrapped (nil, nil, 3, false)).to_raise (badarg (4)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - expect (wrapped (1)).to_be "MAGIC" - expect (wrapped (nil, "two")).to_be "MAGIC" - expect (wrapped (1, "two")).to_be "MAGIC" - expect (wrapped (nil, nil, 3)).to_be "MAGIC" - expect (wrapped (1, nil, 3)).to_be "MAGIC" - expect (wrapped (nil, "two", 3)).to_be "MAGIC" - expect (wrapped ("one", "two", 3)).to_be "MAGIC" - - - context when checking final ellipsis function: - - before: - wrapped = f ("inner (string, int...)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "string")) - expect (wrapped ("foo")).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badarg (12, "int", "boolean")) - - it accepts correct argument types: - expect (wrapped ("foo", 1)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" - - - context when checking optional final parameter: - - context with single argument: - - before: - wrapped = f ("inner ([int])", mkmagic) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, nop)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - expect (wrapped (1)).to_be "MAGIC" - - context with trailing ellipsis: - - before: - wrapped = f ("inner (string, [int]...)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badarg (12, "int", "boolean")) - - it accepts correct argument types: - expect (wrapped ("foo")).to_be "MAGIC" - expect (wrapped ("foo", 1)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" - - context with inner ellipsis: - - before: - wrapped = f ("inner (string, [int...])", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badarg (12, "int", "boolean")) - - it accepts correct argument types: - expect (wrapped ("foo")).to_be "MAGIC" - expect (wrapped ("foo", 1)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" - - - context when omitting self type: - - before: - me = { - wrapped = f ("me:inner (string)", mkmagic) - } - _, badarg, badresult = init (M, "", "me:inner") - - it diagnoses missing arguments: - expect (me:wrapped ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (me:wrapped (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (me:wrapped ("foo", false)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (me:wrapped ("foo")).to_be "MAGIC" - - - context with too many args: - - before: - wrapped = f ("inner ([string], int)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "string or int")) - expect (wrapped ("one")).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "string or int", "boolean")) - expect (wrapped ("one", false)).to_raise (badarg (2, "int", "boolean")) - - it diagnoses too many arguments: - expect (wrapped ("one", 2, false)).to_raise (badarg (3)) - expect (wrapped (1, false)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (wrapped (1)).to_be "MAGIC" - expect (wrapped ("one", 2)).to_be "MAGIC" - - - context when checking single return value function: - - before: | - wrapped = f ("inner (?any...) => #table", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "non-empty table")) - - it diagnoses wrong result types: - expect (wrapped {}). - to_raise (badresult (1, "non-empty table", "empty table")) - - it diagnoses too many results: - expect (wrapped ({1}, 2, nop, "", false)).to_raise (badresult (1, 5)) - - it accepts correct results: - expect ({wrapped {1}}).to_equal {{1}} - - - context with variant single return value function: - - before: - wrapped = f ("inner (?any...) => int or nil", id) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, nop)).to_raise (badresult (2)) - - it accepts correct result types: - expect ({wrapped ()}).to_equal {} - expect ({wrapped (1)}).to_equal {1} - - - context when checking multi-return value function: - - before: - wrapped = f ("inner (?any...) => int, string", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "int")) - expect (wrapped (1)).to_raise (badresult (2, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int", "boolean")) - expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", false)).to_raise (badresult (3)) - - it accepts correct argument types: - expect ({wrapped (1, "two")}).to_equal {1, "two"} - - - context when checking nil return specifier: - - before: - wrapped = f ("inner (?any...) => ?int, string", id) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) - expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) - expect (wrapped (nil, false)).to_raise (badresult (2, "string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "foo", nop)).to_raise (badresult (3)) - expect (wrapped (nil, "foo", nop)).to_raise (badresult (3)) - - it accepts correct result types: - expect ({wrapped (1, "foo")}).to_equal {1, "foo"} - expect ({wrapped (nil, "foo")}).to_equal {[2] = "foo"} - - - context when checking variant multi-return value function: - - before: - wrapped = f ("inner (?any...) => int, string or string", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "int or string")) - expect (wrapped (1)).to_raise (badresult (2, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", nop)).to_raise (badresult (3)) - - it accepts correct result types: - expect ({wrapped ("two")}).to_equal {"two"} - expect ({wrapped (1, "two")}).to_equal {1, "two"} - - - context when checking variant nil,errmsg pattern function: - - before: - wrapped = f ("inner (?any...) => int, string or nil, string", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (2, "string")) - expect (wrapped (1)).to_raise (badresult (2, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) - expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", nop)).to_raise (badresult (3)) - expect (wrapped (nil, "errmsg", nop)).to_raise (badresult (3)) - - it accepts correct result types: - expect ({wrapped (1, "two")}).to_equal {1, "two"} - expect ({wrapped (nil, "errmsg")}).to_equal {[2] = "errmsg"} - - - context when checking optional multi-return value function: - - before: - wrapped = f ("inner (?any...) => [int], string", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "int or string")) - expect (wrapped (1)).to_raise (badresult (2, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", nop)).to_raise (badresult (3)) - - it accepts correct result types: - expect ({wrapped ("two")}).to_equal {"two"} - expect ({wrapped (1, "two")}).to_equal {1, "two"} - - - context when checking final optional multi-return value function: - - before: - wrapped = f ("inner (?any...) => ?any, ?string, [any]", id) - - it diagnoses wrong result types: - expect (wrapped (1, false)).to_raise (badresult (2, "string or nil", "boolean")) - expect (wrapped (nil, false)).to_raise (badresult (2, "string or nil", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", 3, false)).to_raise (badresult (4)) - expect (wrapped (nil, "two", 3, false)).to_raise (badresult (4)) - expect (wrapped (1, nil, 3, false)).to_raise (badresult (4)) - expect (wrapped (nil, nil, 3, false)).to_raise (badresult (4)) - - it accepts correct result types: - expect ({wrapped ()}).to_equal {} - expect ({wrapped (1)}).to_equal {1} - expect ({wrapped (nil, "two")}).to_equal {[2]="two"} - expect ({wrapped (1, "two")}).to_equal {1, "two"} - expect ({wrapped (nil, nil, 3)}).to_equal {[3]=3} - expect ({wrapped (1, nil, 3)}).to_equal {1, [3]=3} - expect ({wrapped (nil, "two", 3)}).to_equal {[2]="two", [3]=3} - expect ({wrapped ("one", "two", 3)}).to_equal {"one", "two", 3} - - - context when checking optional final result: - - context with single result: - - before: - wrapped = f ("inner (?any...) => [int]", id) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, nop)).to_raise (badresult (2)) - - it accepts correct result types: - expect ({wrapped ()}).to_equal {} - expect ({wrapped (1)}).to_equal {1} - - context with trailing ellipsis: - - before: - wrapped = f ("inner (?any...) => string, [int]...", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badresult (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badresult (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badresult (12, "int", "boolean")) - - it accepts correct result types: - expect ({wrapped ("foo")}).to_equal {"foo"} - expect ({wrapped ("foo", 1)}).to_equal {"foo", 1} - expect ({wrapped ("foo", 1, 2)}).to_equal {"foo", 1, 2} - expect ({wrapped ("foo", 1, 2, 5)}).to_equal {"foo", 1, 2, 5} - - context with inner ellipsis: - - before: - wrapped = f ("inner (?any...) => string, [int...]", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badresult (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badresult (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badresult (12, "int", "boolean")) - - it accepts correct result types: - expect ({wrapped ("foo")}).to_equal {"foo"} - expect ({wrapped ("foo", 1)}).to_equal {"foo", 1} - expect ({wrapped ("foo", 1, 2)}).to_equal {"foo", 1, 2} - expect ({wrapped ("foo", 1, 2, 5)}).to_equal {"foo", 1, 2, 5} - - - context with too many results: - - before: - wrapped = f ("inner (?any...) => [string], int", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "string or int")) - expect (wrapped "one").to_raise (badresult (2, "int")) - - it diagnoses wrong result types: - expect (wrapped (false)). - to_raise (badresult (1, "string or int", "boolean")) - expect (wrapped ("one", false)). - to_raise (badresult (2, "int", "boolean")) - - it diagnoses too many results: - expect (wrapped ("one", 2, false)).to_raise (badresult (3)) - expect (wrapped (1, false)).to_raise (badresult (2)) - - it accepts correct argument types: - expect ({wrapped (1)}).to_equal {1} - expect ({wrapped ("one", 2)}).to_equal {"one", 2} - - -- describe extramsg_mismatch: - - before: - f = M.extramsg_mismatch - - - it returns the expected types: - expect (f "nil").to_contain "nil expected, " - expect (f "bool").to_contain "boolean expected, " - expect (f "?bool").to_contain "boolean or nil expected, " - expect (f "string|table").to_contain "string or table expected, " - - it returns expected container types: - expect (f ("table of int", nil, 1)).to_contain "table of ints expected, " - expect (f ("table of int|bool", nil, 1)). - to_contain "table of ints or booleans expected, " - expect (f ("table of int|bool|string", nil, 1)). - to_contain "table of ints, booleans or strings expected, " - expect (f ("table of int|bool|string|table", nil, 1)). - to_contain "table of ints, booleans, strings or tables expected, " - - it returns the actual type: - expect (f ("int")).to_contain ", got no value" - expect (f ("int", false)).to_contain ", got boolean" - expect (f ("int", {})).to_contain ", got empty table" - - it returns table field type: - expect (f ("table of int", nil, 1)).to_contain ", got no value at index 1" - expect (f ("table of int", "two", 2)).to_contain ", got string at index 2" - expect (f ("table of int|bool", "five", 3)).to_contain ", got string at index 3" - - -- describe extramsg_toomany: - - before: - f = M.extramsg_toomany - - - it returns the expected thing: - expect (f ("mojo", 1, 2)).to_contain "no more than 1 mojo" - - it uses singular thing when 1 is expected: - expect (f ("argument", 1, 2)).to_contain "no more than 1 argument" - - it uses plural thing otherwise: - expect (f ("thing", 0, 3)).to_contain "no more than 0 things" - expect (f ("result", 2, 3)).to_contain "no more than 2 results" - - it returns the actual count: - expect (f ("bad", 0, 1)).to_contain ", got 1" - expect (f ("bad", 99, 999)).to_contain ", got 999" From d0f9f95e2ce4f1ee620e02abb4c786e6b38b87e0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Jan 2016 01:52:12 +0000 Subject: [PATCH 665/703] maint: also work when typecheck is installed. * lib/std/container.lua (argerror): Use typecheck.argerror when _DEBUG.argcheck is set, and not at all otherwise. * lib/std/io.lua (argerror): Likewise. (_DEBUG.argcheck): Set to false if typecheck module is not loadable. * lib/std/table.lua (argerror, _DEBUG.argcheck): Likewise. * lib/std/functional.lua (argerror, argcheck, extramsg_toomany): remove unused loacals. Signed-off-by: Gary V. Vaughan --- lib/std/container.lua | 5 ++--- lib/std/functional.lua | 2 +- lib/std/io.lua | 7 +++++-- lib/std/table.lua | 7 +++++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/std/container.lua b/lib/std/container.lua index a04d904..be52202 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -48,7 +48,6 @@ local _ = { local Module = _.std.object.Module local _DEBUG = _.debug_init._DEBUG -local argerror = _.std.debug.argerror local copy = _.std.base.copy local mapfields = _.std.object.mapfields local pickle = _.std.string.pickle @@ -58,18 +57,18 @@ local sortkeys = _.std.base.sortkeys -- Perform typechecking with functions exported from this module, unless -- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argcheck, argscheck, extramsg_toomany +local argcheck, argerror, argscheck, extramsg_toomany if _DEBUG.argcheck then local ok, typecheck = pcall (require, "typecheck") if ok then argcheck = typecheck.argcheck + argerror = typecheck.argerror argscheck = typecheck.argscheck extramsg_toomany = typecheck.extramsg_toomany else _DEBUG.argcheck = false end end -argcheck = argcheck or function () end argscheck = argscheck or function (decl, inner) return inner end diff --git a/lib/std/functional.lua b/lib/std/functional.lua index f251655..4d4141c 100644 --- a/lib/std/functional.lua +++ b/lib/std/functional.lua @@ -41,7 +41,7 @@ local leaves = _.std.tree.leaves -- Perform typechecking with functions exported from this module, unless -- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argcheck, argerror, argscheck, extramsg_toomany +local argscheck if _DEBUG.argcheck then local ok, typecheck = pcall (require, "typecheck") if ok then diff --git a/lib/std/io.lua b/lib/std/io.lua index b4a05f4..d6c7180 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -42,7 +42,6 @@ local _ = { local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _tostring = _.std.tostring -local argerror = _.std.debug.argerror local catfile = _.std.io.catfile local copy = _.std.base.copy local dirsep = _.std.package.dirsep @@ -54,13 +53,17 @@ local split = _.std.string.split -- Perform typechecking with functions exported from this module, unless -- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck +local argerror, argscheck if _DEBUG.argcheck then local ok, typecheck = pcall (require, "typecheck") if ok then + argerror = typecheck.argerror argscheck = typecheck.argscheck + else + _DEBUG.argcheck = false end end +argerror = argerror or _.std.debug.argerror argscheck = argscheck or function (decl, inner) return inner end diff --git a/lib/std/table.lua b/lib/std/table.lua index 87a05f6..b9b9975 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -34,7 +34,6 @@ local _ = { local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local argerror = _.std.debug.argerror local collect = _.std.functional.collect local copy = _.std.base.copy local getmetamethod = _.std.getmetamethod @@ -47,13 +46,17 @@ local pack = _.std.table.pack -- Perform typechecking with functions exported from this module, unless -- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck +local argerror, argscheck if _DEBUG.argcheck then local ok, typecheck = pcall (require, "typecheck") if ok then + argerror = typecheck.argerror argscheck = typecheck.argscheck + else + _DEBUG.argcheck = false end end +argerror = argerror or _.std.debug.argerror argscheck = argscheck or function (decl, inner) return inner end From 01afb6f059f55879d50ad28f95c3e961e6946314 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Jan 2016 02:01:37 +0000 Subject: [PATCH 666/703] configury: update the ldoc maturity html directory. * local.mk (docmodules): Finish a kludge to keep LDoc happy until we split maturity.lua into its own module. Signed-off-by: Gary V. Vaughan --- local.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local.mk b/local.mk index a1ff504..39f2db9 100644 --- a/local.mk +++ b/local.mk @@ -133,7 +133,7 @@ EXTRA_DIST += \ doccorefunctions = $(srcdir)/doc/core_functions/std doccorelibraries = $(srcdir)/doc/core_libraries/std docfunctional = $(srcdir)/doc/functional_style/std -docmodules = $(srcdir)/doc/modules/std +docmodules = $(srcdir)/doc/core_modules/std docobjects = $(srcdir)/doc/object_system/std dist_doc_DATA += \ From 1c8f8cbef8fbe4b03b9c099a4d62748e3a875d28 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Jan 2016 02:11:43 +0000 Subject: [PATCH 667/703] base: fix unused arguments to in debug.argerror. * lib/std/base.lua (argerror): Don't pass additional uninitialised variables to string.format! Signed-off-by: Gary V. Vaughan --- lib/std/base.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/base.lua b/lib/std/base.lua index a97fa07..cc4537c 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -119,7 +119,7 @@ end local function argerror (name, i, extramsg, level) level = level or 1 - local s = string_format ("bad %s #%d %s '%s'", bad, i, to, name) + local s = string_format ("bad argument #%d to '%s'", i, name) if extramsg ~= nil then s = s .. " (" .. extramsg .. ")" end From 665460bde6c5ffb25f2b185d78a95cfbc871e0dd Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 28 Jan 2016 00:41:29 +0000 Subject: [PATCH 668/703] maint: remove all deprecated code. The maintenance overhead had become too high. Remove over 2000 lines of code, and 492 Specl examples! * lib/std/delete-after/2016-01-31.lua, lib/std/delete-after/2016-03-08.lua, lib/std/delete-after/a-year.lua: Remove unnecessary files. * local.mk (luastddeletedir, dist_luastddelete_DATA): Remove. * lib/std/debug.lua (_setdebug): Remove. * lib/std/debug.lua, lib/std/init.lua, lib/std/object.lua, lib/std/strbuf.lua, lib/std/table.lua (deprecated): Remove, and tidy up remaining code accordingly. * specs/debug_spec.yaml, specs/functional_spec.yaml, specs/list_spec.yaml, specs/object_spec.yaml, specs/std_spec.yaml, specs/strbuf_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Remove examples of deprecated apis. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 48 +- lib/std/debug.lua | 16 - lib/std/delete-after/2016-01-31.lua | 85 --- lib/std/delete-after/2016-03-08.lua | 80 --- lib/std/delete-after/a-year.lua | 713 --------------------- lib/std/init.lua | 8 - lib/std/object.lua | 18 +- lib/std/strbuf.lua | 15 +- lib/std/table.lua | 7 - local.mk | 8 - specs/debug_spec.yaml | 918 +--------------------------- specs/functional_spec.yaml | 6 - specs/list_spec.yaml | 5 - specs/object_spec.yaml | 120 +--- specs/std_spec.yaml | 34 +- specs/strbuf_spec.yaml | 29 +- specs/string_spec.yaml | 77 +-- specs/table_spec.yaml | 101 --- 18 files changed, 33 insertions(+), 2255 deletions(-) delete mode 100644 lib/std/delete-after/2016-01-31.lua delete mode 100644 lib/std/delete-after/2016-03-08.lua delete mode 100644 lib/std/delete-after/a-year.lua diff --git a/NEWS.md b/NEWS.md index 26da4aa..255a122 100644 --- a/NEWS.md +++ b/NEWS.md @@ -21,14 +21,8 @@ luarocks install strict ``` - - All support for deprecated APIs has been moved out of the module - sources, so that it needn't be loaded in production code that has - been properly updated not to use deprecated calls, i.e: - - ```lua - local _DEBUG = require "std.debug_init"._DEBUG - _DEBUG.deprecate = true - ``` + - All support for deprecated APIs has been removed, reducing the + install size even further. - Objects and Modules are no longer conflated - what you get back from a `require "std.something"` is now ALWAYS a module: @@ -124,10 +118,10 @@ - New `operator.eqv` is similar to `operator.eq`, except that it succeeds when recursive table contents are equivalent. - - New `operator.len` replaces deprecated `table.len`. `operator.len` - is always deterministic; counting only numerically indexed elements - immediately up to the first `nil` valued element (PUC-Rio Lua does - not have this feature for its `#` operator): + - New `operator.len` replaces `table.len`. `operator.len` is always + deterministic; counting only numerically indexed elements immediately up + to the first `nil` valued element (PUC-Rio Lua does not guarantee this + with its `#` operator): ```lua local t1 = {1, 2, [5]=3} @@ -145,14 +139,6 @@ - New `std.maturity` module now contains the `DEPRECATED` and `DEPRECATIONMSG` functions previously found in `std.debug`. -### Deprecations - - - Deprecated functions no longer support argument checks, partially to - simplify the deprecation plumbing, but also because if you are - developing code with argument checking on, then you are already - getting deprecation messages that tell you not to keep using these - functions. - - We used to have an object module method, `std.object.type`, which often got imported using: @@ -167,28 +153,28 @@ orthogonality with core Lua, we're going back to using `std.object.type`, because that just makes more sense. Sorry! - - `std.ireverse` has been deprecated in favour of - `std.functional.ireverse` because of the functional style of a - non-destructive sequence reversing operation. + - `std.ireverse` has been replaced by `std.functional.ireverse` because + of the functional style of a non-destructive sequence reversing + operation. - - `std.table.flatten` and `std.table.shape` have been deprecated in + - `std.table.flatten` and `std.table.shape` have been replaced by favour of `std.functional.flatten` and `std.functional.shape` because these functions are far more useful in conjunction with a functional programming style than with regular tables in imperative code. - - `std.table.len` has been deprecated in favour of `std.operator.len`, - because it is not just for tables! + - `std.table.len` has moved to `std.operator.len`, because it is not just + for tables! - - `std.table.okeys` has been deprecated for lack of utility. If you + - `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) ``` - - `std.string.render` function arguments have been deprecated in favour - of a table of named functions backed by defaults. + - `std.string.render` function arguments have been replaced by a table + of named functions backed by defaults. ### Bug fixes @@ -206,8 +192,8 @@ `nil` arguments correctly. - `std.functional.memoize` now considers trailing nil arguments when - looking up memoized value for those particular arguments, and propagates - `nil` return values from `mnemonic` functions correctly. + looking up a memoized value for those particular arguments, and + propagates `nil` return values from `mnemonic` functions correctly. - `std.functional.filter`, `std.functional.map` and `std.functional.reduce` now pass trailing nil arguments to their diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 639d2cc..cbfc287 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -22,8 +22,6 @@ local math_max = math.max local table_concat = table.concat -local deprecated = require "std.delete-after.a-year" - local _ = { debug_init = require "std.debug_init", std = require "std.base", @@ -168,15 +166,6 @@ local M = { -- _DEBUG = { call = true } -- local debug = require "std.debug" trace = trace, - - - -- Private: - _setdebug = function (t) - for k, v in _pairs (t) do - if v == "nil" then v = nil end - _DEBUG[k] = v - end - end, } @@ -196,9 +185,4 @@ local metatable = { } -if deprecated then - M = merge (M, deprecated.debug) -end - - return setmetatable (merge (M, debug), metatable) diff --git a/lib/std/delete-after/2016-01-31.lua b/lib/std/delete-after/2016-01-31.lua deleted file mode 100644 index 00d5bad..0000000 --- a/lib/std/delete-after/2016-01-31.lua +++ /dev/null @@ -1,85 +0,0 @@ ---[[-- - Provide at least one year of support for deprecated APIs, or at - least one release cycle if that is longer. - - When `_DEBUG.deprecate` is `true` we don`t even load this support, in - which case `require`ing this module returns `false`. - - Otherwise, return a table of all functions deprecated in the given - `RELEASE` and earlier, going back at least one year. The table is - keyed on the original module to enable merging deprecated APIs back - into their previous namespaces - this is handled automatically by the - documented modules according to the contents of `_DEBUG`. - - In some release after the date of this module, it will be removed and - these APIs will not be available any longer. -]] - - -local RELEASE = "41.1.1" - - -local M = false - -if not require "std.debug_init"._DEBUG.deprecate then - - local pairs = pairs - local tostring = tostring - local type = type - local table_concat = table.concat - - local _, deprecated = { - -- Adding anything else here will probably cause a require loop. - maturity = require "std.maturity", - std = require "std.base", - } - - -- Merge in deprecated APIs from previous release if still available. - _.ok, deprecated = pcall (require, "std.delete-after.2016-01-03") - if not _.ok then deprecated = {} end - - - local DEPRECATED = _.maturity.DEPRECATED - local _ipairs = _.std.ipairs - - - - --[[ ========== ]]-- - --[[ Death Row! ]]-- - --[[ ========== ]]-- - - local function strbuf_tostring (strbuf) - local strs = {} - for _, v in _ipairs (strbuf) do strs[#strs + 1] = tostring (v) end - return table_concat (strs) - end - - - -- Ensure deprecated APIs observe _DEBUG warning standards. - local function X (old, new, fn) - return DEPRECATED (RELEASE, "'std." .. old .. "'", "use '" .. new .. "' instead", fn) - end - - local function acyclic_merge (dest, src) - for k, v in pairs (src) do - if type (v) == "table" then - dest[k] = dest[k] or {} - if type (dest[k]) == "table" then acyclic_merge (dest[k], v) end - else - dest[k] = dest[k] or v - end - end - return dest - end - - M = acyclic_merge ({ - strbuf = { - tostring = X ("strbuf.tostring", "tostring (strbuf)", strbuf_tostring) - }, - }, - deprecated) - -end - - -return M diff --git a/lib/std/delete-after/2016-03-08.lua b/lib/std/delete-after/2016-03-08.lua deleted file mode 100644 index f3ee24f..0000000 --- a/lib/std/delete-after/2016-03-08.lua +++ /dev/null @@ -1,80 +0,0 @@ ---[[-- - Provide at least one year of support for deprecated APIs, or at - least one release cycle if that is longer. - - When `_DEBUG.deprecate` is `true` we don`t even load this support, in - which case `require`ing this module returns `false`. - - Otherwise, return a table of all functions deprecated in the given - `RELEASE` and earlier, going back at least one year. The table is - keyed on the original module to enable merging deprecated APIs back - into their previous namespaces - this is handled automatically by the - documented modules according to the contents of `_DEBUG`. - - In some release after the date of this module, it will be removed and - these APIs will not be available any longer. -]] - - -local RELEASE = "41.2.0" - - -local M = false - -if not require "std.debug_init"._DEBUG.deprecate then - - local pairs = pairs - local type = type - local string_format = string.format - - local _, deprecated = { - -- Adding anything else here will probably cause a require loop. - maturity = require "std.maturity", - std = require "std.base", - } - - -- Merge in deprecated APIs from previous release if still available. - _.ok, deprecated = pcall (require, "std.delete-after.2016-01-31") - if not _.ok then deprecated = {} end - - - local DEPRECATED = _.maturity.DEPRECATED - - - --[[ ========== ]]-- - --[[ Death Row! ]]-- - --[[ ========== ]]-- - - local function toomanyargmsg (name, expect, actual) - local s = "bad argument #%d to '%s' (no more than %d argument%s expected, got %d)" - return string_format (s, expect + 1, name, expect, expect == 1 and "" or "s", actual) - end - - - -- Ensure deprecated APIs observe _DEBUG warning standards. - local function X (old, new, fn) - return DEPRECATED (RELEASE, "'std." .. old .. "'", "use '" .. new .. "' instead", fn) - end - - local function acyclic_merge (dest, src) - for k, v in pairs (src) do - if type (v) == "table" then - dest[k] = dest[k] or {} - if type (dest[k]) == "table" then acyclic_merge (dest[k], v) end - else - dest[k] = dest[k] or v - end - end - return dest - end - - M = acyclic_merge ({ - debug = { - toomanyargmsg = X ("debug.toomanyargmsg", "std.debug.extramsg_toomany", toomanyargmsg), - }, - }, - deprecated) - -end - -return M diff --git a/lib/std/delete-after/a-year.lua b/lib/std/delete-after/a-year.lua deleted file mode 100644 index 8e71ca5..0000000 --- a/lib/std/delete-after/a-year.lua +++ /dev/null @@ -1,713 +0,0 @@ ---[[-- - Provide at least one year of support for deprecated APIs, or at - least one release cycle if that is longer. - - When `_DEBUG.deprecate` is `true` we don`t even load this support, in - which case `require`ing this module returns `false`. - - Otherwise, return a table of all functions deprecated in the given - `RELEASE` and earlier, going back at least one year. The table is - keyed on the original module to enable merging deprecated APIs back - into their previous namespaces - this is handled automatically by the - documented modules according to the contents of `_DEBUG`. - - In some release after the date of this module, it will be removed and - these APIs will not be available any longer. -]] - - -local RELEASE = "upcoming" - - -local M = false - -if not require "std.debug_init"._DEBUG.deprecate then - - local error = error - local getmetatable = getmetatable - local next = next - local pairs = pairs - local pcall = pcall - local select = select - local type = type - - local io_stderr = io.stderr - local io_type = io.type - local math_ceil = math.ceil - local math_floor = math.floor - local math_max = math.max - local string_find = string.find - local string_format = string.format - local string_gsub = string.gsub - local string_match = string.match - local table_concat = table.concat - local table_insert = table.insert - local table_pack = table.pack or function (...) - return {n = select ("#", ...), ...} - end - local table_remove = table.remove - local table_sort = table.sort - local table_unpack = table.unpack or unpack - - local _, deprecated = { - -- Adding anything else here will probably cause a require loop. - debug_init = require "std.debug_init", - maturity = require "std.maturity", - std = require "std.base", - } - - -- Merge in deprecated APIs from previous release if still available. - _.ok, deprecated = pcall (require, "std.delete-after.2016-03-08") - if not _.ok then deprecated = {} end - - - local _DEBUG = _.debug_init._DEBUG - local _getfenv = _.std.debug.getfenv - local _ipairs = _.std.ipairs - local _pairs = _.std.pairs - local _setfenv = _.std.debug.setfenv - local _tostring = _.std.tostring - local DEPRECATED = _.maturity.DEPRECATED - local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG - local copy = _.std.base.copy - local leaves = _.std.tree.leaves - local len = _.std.operator.len - local maxn = _.std.table.maxn - local nop = _.std.functional.nop - local pack = _.std.table.pack - local sortkeys = _.std.base.sortkeys - local split = _.std.string.split - - - - --[[ ========== ]]-- - --[[ Death Row! ]]-- - --[[ ========== ]]-- - - - local function raise (bad, to, name, i, extramsg, level) - level = level or 1 - local s = string_format ("bad %s #%d %s '%s'", bad, i, to, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) - end - - - local function argerror (name, i, extramsg, level) - level = level or 1 - raise ("argument", "to", name, i, extramsg, level + 1) - end - - - local function resulterror (name, i, extramsg, level) - level = level or 1 - raise ("result", "from", name, i, extramsg, level + 1) - end - - - local function extramsg_toomany (bad, expected, actual) - local s = "no more than %d %s%s expected, got %d" - return string_format (s, expected, bad, expected == 1 and "" or "s", actual) - end - - - --- Concatenate a table of strings using ", " and " or " delimiters. - -- @tparam table alternatives a table of strings - -- @treturn string string of elements from alternatives delimited by ", " - -- and " or " - local function concat (alternatives) - if len (alternatives) > 1 then - local t = copy (alternatives) - local top = table_remove (t) - t[#t] = t[#t] .. " or " .. top - alternatives = t - end - return table_concat (alternatives, ", ") - end - - - local function _type (x) - return (getmetatable (x) or {})._type or io_type (x) or type (x) - end - - - local function extramsg_mismatch (expectedtypes, actual, index) - local actualtype = _type (actual) or type (actual) - - -- Tidy up actual type for display. - if actualtype == "nil" then - actualtype = "no value" - elseif actualtype == "string" and actual:sub (1, 1) == ":" then - actualtype = actual - elseif type (actual) == "table" and next (actual) == nil then - local matchstr = "," .. table_concat (expectedtypes, ",") .. "," - if actualtype == "table" and matchstr == ",#list," then - actualtype = "empty list" - elseif actualtype == "table" or matchstr:match ",#" then - actualtype = "empty " .. actualtype - end - end - - if index then - actualtype = actualtype .. " at index " .. _tostring (index) - end - - -- Tidy up expected types for display. - local expectedstr = expectedtypes - if type (expectedtypes) == "table" then - local t = {} - for i, v in _ipairs (expectedtypes) do - if v == "func" then - t[i] = "function" - elseif v == "bool" then - t[i] = "boolean" - elseif v == "any" then - t[i] = "any value" - elseif v == "file" then - t[i] = "FILE*" - elseif not index then - t[i] = v:match "(%S+) of %S+" or v - else - t[i] = v - end - end - expectedstr = (concat (t) .. " expected"): - gsub ("#table", "non-empty table"): - gsub ("#list", "non-empty list"): - gsub ("(%S+ of [^,%s]-)s? ", "%1s "): - gsub ("(%S+ of [^,%s]-)s?,", "%1s,"): - gsub ("(s, [^,%s]-)s? ", "%1s "): - gsub ("(s, [^,%s]-)s?,", "%1s,"): - gsub ("(of .-)s? or ([^,%s]-)s? ", "%1s or %2s ") - end - - return expectedstr .. ", got " .. actualtype - end - - - --- Strip trailing ellipsis from final argument if any, storing maximum - -- number of values that can be matched directly in `t.maxvalues`. - -- @tparam table t table to act on - -- @string v element added to *t*, to match against ... suffix - -- @treturn table *t* with ellipsis stripped and maxvalues field set - local function markdots (t, v) - return (string_gsub (v, "%.%.%.$", function () t.dots = true return "" end)) - end - - - --- Calculate permutations of type lists with and without [optionals]. - -- @tparam table t a list of expected types by argument position - -- @treturn table set of possible type lists - local function permute (t) - if t[#t] then t[#t] = string_gsub (t[#t], "%]%.%.%.$", "...]") end - - local p = {{}} - for i, v in _ipairs (t) do - local optional = string_match (v, "%[(.+)%]") - - if optional == nil then - -- Append non-optional type-spec to each permutation. - for b = 1, #p do - table_insert (p[b], markdots (p[b], v)) - end - else - -- Duplicate all existing permutations, and add optional type-spec - -- to the unduplicated permutations. - local o = #p - for b = 1, o do - p[b + o] = copy (p[b]) - table_insert (p[b], markdots (p[b], optional)) - end - end - end - return p - end - - - local function typesplit (types) - if type (types) == "string" then - types = split (string_gsub (types, "%s+or%s+", "|"), "%s*|%s*") - end - local r, seen, add_nil = {}, {}, false - for _, v in _ipairs (types) do - local m = string_match (v, "^%?(.+)$") - if m then - add_nil, v = true, m - end - if not seen[v] then - r[#r + 1] = v - seen[v] = true - end - end - if add_nil then - r[#r + 1] = "nil" - end - return r - end - - - local function projectuniq (fkey, tt) - -- project - local t = {} - for _, u in _ipairs (tt) do - t[#t + 1] = u[fkey] - end - - -- split and remove duplicates - local r, s = {}, {} - for _, e in _ipairs (t) do - for _, v in _ipairs (typesplit (e)) do - if s[v] == nil then - r[#r + 1], s[v] = v, true - end - end - end - return r - end - - - local function parsetypes (types) - local r, permutations = {}, permute (types) - for i = 1, #permutations[1] do - r[i] = projectuniq (i, permutations) - end - r.dots = permutations[1].dots - return r - end - - - - local argcheck, argscheck -- forward declarations - - if _DEBUG.argcheck then - - --- Return index of the first mismatch between types and values, or `nil`. - -- @tparam table typelist a list of expected types - -- @tparam table valuelist a table of arguments to compare - -- @treturn int|nil position of first mismatch in *typelist* - local function match (typelist, valuelist) - local n = #typelist - for i = 1, n do -- normal parameters - local ok = pcall (argcheck, "pcall", i, typelist[i], valuelist[i]) - if not ok then return i end - end - for i = n + 1, valuelist.n do -- additional values against final type - local ok = pcall (argcheck, "pcall", i, typelist[n], valuelist[i]) - if not ok then return i end - end - end - - - --- Compare *check* against type of *actual* - -- @string check extended type name expected - -- @param actual object being typechecked - -- @treturn boolean `true` if *actual* is of type *check*, otherwise - -- `false` - local function checktype (check, actual) - if check == "any" and actual ~= nil then - return true - elseif check == "file" and io_type (actual) == "file" then - return true - end - - local actualtype = type (actual) - if check == actualtype then - return true - elseif check == "bool" and actualtype == "boolean" then - return true - elseif check == "#table" then - if actualtype == "table" and next (actual) then - return true - end - elseif check == "function" or check == "func" then - if actualtype == "function" or - (getmetatable (actual) or {}).__call ~= nil - then - return true - end - elseif check == "int" then - if actualtype == "number" and actual == math_floor (actual) then - return true - end - elseif type (check) == "string" and check:sub (1, 1) == ":" then - if check == actual then - return true - end - end - - actualtype = _type (actual) - if check == actualtype then - return true - elseif check == "list" or check == "#list" then - if actualtype == "table" or actualtype == "List" then - local len, count = len (actual), 0 - local i = next (actual) - repeat - if i ~= nil then count = count + 1 end - i = next (actual, i) - until i == nil or count > len - if count == len and (check == "list" or count > 0) then - return true - end - end - elseif check == "object" then - if actualtype ~= "table" and type (actual) == "table" then - return true - end - end - - return false - end - - - local function empty (t) return not next (t) end - - -- Pattern to normalize: [types...] to [types]... - local last_pat = "^%[([^%]%.]+)%]?(%.*)%]?" - - --- Diagnose mismatches between *valuelist* and type *permutations*. - -- @tparam table valuelist list of actual values to be checked - -- @tparam table argt table of precalculated values and handler functiens - local function diagnose (valuelist, argt) - local permutations = argt.permutations - - local bestmismatch, t = 0 - for i, typelist in _ipairs (permutations) do - local mismatch = match (typelist, valuelist) - if mismatch == nil then - bestmismatch, t = nil, nil - break -- every *valuelist* matched types from this *typelist* - elseif mismatch > bestmismatch then - bestmismatch, t = mismatch, permutations[i] - end - end - - if bestmismatch ~= nil then - -- Report an error for all possible types at bestmismatch index. - local i, expected = bestmismatch - if t.dots and i > #t then - expected = typesplit (t[#t]) - else - expected = projectuniq (i, permutations) - end - - -- This relies on the `permute()` algorithm leaving the longest - -- possible permutation (with dots if necessary) at permutations[1]. - local typelist = permutations[1] - - -- For "container of things", check all elements are a thing too. - if typelist[i] then - local check, contents = string_match (typelist[i], "^(%S+) of (%S-)s?$") - if contents and type (valuelist[i]) == "table" then - for k, v in _pairs (valuelist[i]) do - if not checktype (contents, v) then - argt.badtype (i, extramsg_mismatch (expected, v, k), 3) - end - end - end - end - - -- Otherwise the argument type itself was mismatched. - if t.dots or #t >= valuelist.n then - argt.badtype (i, extramsg_mismatch (expected, valuelist[i]), 3) - end - end - - local n, t = valuelist.n, t or permutations[1] - if t and t.dots == nil and n > #t then - argt.badtype (#t + 1, extramsg_toomany (argt.bad, #t, n), 3) - end - end - - - function argcheck (name, i, expected, actual, level) - level = level or 2 - expected = typesplit (expected) - - -- Check actual has one of the types from expected - local ok = false - for _, expect in _ipairs (expected) do - local check, contents = string_match (expect, "^(%S+) of (%S-)s?$") - check = check or expect - - -- Does the type of actual check out? - ok = checktype (check, actual) - - -- For "table of things", check all elements are a thing too. - if ok and contents and type (actual) == "table" then - for k, v in _pairs (actual) do - if not checktype (contents, v) then - argerror (name, i, extramsg_mismatch (expected, v, k), level + 1) - end - end - end - if ok then break end - end - - if not ok then - argerror (name, i, extramsg_mismatch (expected, actual), level + 1) - end - end - - - -- Pattern to extract: fname ([types]?[, types]*) - local args_pat = "^%s*([%w_][%.%:%d%w_]*)%s*%(%s*(.*)%s*%)" - - function argscheck (decl, inner) - -- Parse "fname (argtype, argtype, argtype...)". - local fname, argtypes = string_match (decl, args_pat) - if argtypes == "" then - argtypes = {} - elseif argtypes then - argtypes = split (argtypes, "%s*,%s*") - else - fname = string_match (decl, "^%s*([%w_][%.%:%d%w_]*)") - end - - -- Precalculate vtables once to make multiple calls faster. - local input, output = { - bad = "argument", - badtype = function (i, extramsg, level) - level = level or 1 - argerror (fname, i, extramsg, level + 1) - end, - permutations = permute (argtypes), - } - - -- Parse "... => returntype, returntype, returntype...". - local returntypes = string_match (decl, "=>%s*(.+)%s*$") - if returntypes then - local i, permutations = 0, {} - for _, group in _ipairs (split (returntypes, "%s+or%s+")) do - returntypes = split (group, ",%s*") - for _, t in _ipairs (permute (returntypes)) do - i = i + 1 - permutations[i] = t - end - end - - -- Ensure the longest permutation is first in the list. - table_sort (permutations, function (a, b) return #a > #b end) - - output = { - bad = "result", - badtype = function (i, extramsg, level) - level = level or 1 - resulterror (fname, i, extramsg, level + 1) - end, - permutations = permutations, - } - end - - return function (...) - local argt = pack (...) - - -- Don't check type of self if fname has a ':' in it. - if string_find (fname, ":") then - table_remove (argt, 1) - argt.n = argt.n - 1 - end - - -- Diagnose bad inputs. - diagnose (argt, input) - - -- Propagate outer environment to inner function. - local x = math_max -- ??? FIXME: getfenv(1) fails if we remove this ??? - _setfenv (inner, _getfenv (1)) - - -- Execute. - local results = pack (inner (...)) - - -- Diagnose bad outputs. - if returntypes then - diagnose (results, output) - end - - return table_unpack (results, 1, results.n) - end - end - - else - - -- Turn off argument checking if _DEBUG is false, or a table containing - -- a false valued `argcheck` field. - - argcheck = nop - argscheck = function (decl, inner) return inner end - - end - - - local function collect (ifn, ...) - local argt, r = pack (...), {} - - -- How many return values from ifn? - local arity = 1 - for e, v in ifn (table_unpack (argt, 1, argt.n)) do - if v then arity, r = 2, {} break end - -- Build an arity-1 result table on first pass... - r[#r + 1] = e - end - - if arity == 2 then - -- ...oops, it was arity-2 all along, start again! - for k, v in ifn (table_unpack (argt, 1, argt.n)) do - r[k] = v - end - end - - return r - end - - - local function flatten (t) - return collect (leaves, _ipairs, t) - end - - - local function ireverse (t) - local oob = 1 - while t[oob] ~= nil do - oob = oob + 1 - end - - local r = {} - for i = 1, oob - 1 do r[oob - i] = t[i] end - return r - end - - - local function okeys (t) - local r = {} - for k in _pairs (t) do r[#r + 1] = k end - return sortkeys (r) - end - - - local function shape (dims, t) - t = flatten (t) - -- Check the shape and calculate the size of the zero, if any - local size = 1 - local zero - for i, v in _ipairs (dims) do - if v == 0 then - if zero then -- bad shape: two zeros - return nil - else - zero = i - end - else - size = size * v - end - end - if zero then - dims[zero] = math_ceil (len (t) / size) - end - local function fill (i, d) - if d > len (dims) then - return t[i], i + 1 - else - local r = {} - for j = 1, dims[d] do - local e - e, i = fill (i, d + 1) - r[#r + 1] = e - end - return r, i - end - end - return (fill (1, 1)) - end - - - local function _type (x) - return (getmetatable (x) or {})._type or io_type (x) or type (x) - end - - - -- Ensure deprecated APIs observe _DEBUG warning standards. - local function X (old, new, fn) - return DEPRECATED (RELEASE, "'std." .. old .. "'", "use 'std." .. new .. "' instead", fn) - end - - local function XX (base, fn) - return DEPRECATED (RELEASE, "'std.debug." .. base .. "'", "use 'std.argcheck." .. base .. "' instead", fn) or nil - end - - local function acyclic_merge (dest, src) - for k, v in pairs (src) do - if type (v) == "table" then - dest[k] = dest[k] or {} - if type (dest[k]) == "table" then acyclic_merge (dest[k], v) end - else - dest[k] = dest[k] or v - end - end - return dest - end - - M = acyclic_merge ({ - debug = { - argcheck = XX ("argcheck", function (name, i, expected, actual, level) - -- Add 2 to the level, this anonymous function and XX, being - -- careful not to let tail call elimination remove a stack - -- frame: - local r = table_pack (argcheck (name, i, expected, actual, (level or 1) + 2)) - return table_unpack (r, 1, r.n) - end), - argerror = XX ("argerror", function (name, i, extramsg, level) - local r = table_pack (argerror (name, i, extramsg, (level or 1) + 2)) - return table_unpack (r, 1, r.n) - end), - argscheck = XX ("argscheck", argscheck), - extramsg_mismatch = XX ("extramsg_mismatch", function (expected, actual, index) - local r = table_pack (extramsg_mismatch (typesplit (expected), actual, index)) - return table_unpack (r, 1, r.n) - end), - extramsg_toomany = XX ("extramsg_toomany", extramsg_toomany), - parsetypes = XX ("parsetypes", parsetypes), - resulterror = XX ("resulterror", function (name, i, extramsg, level) - local r = table_pack (resulterror (name, i, extramsg, (level or 1) + 2)) - return table_unpack (r, 1, r.n) - end), - typesplit = XX ("typesplit", typesplit), - }, - - object = { - type = function (x) - local r = (getmetatable (x) or {})._type - if r == nil then - io_stderr:write (DEPRECATIONMSG (RELEASE, - "non-object argument to 'std.object.type'", - [[check for 'type (x) == "table"' before calling 'std.object.type (x)' instead]], - 2)) - end - return r or io_type (x) or type (x) - end, - }, - - std = { - ireverse = X ("ireverse", "functional.ireverse", ireverse), - }, - - table = { - flatten = X ("table.flatten", "functional.flatten", flatten), - len = X ("table.len", "operator.len", len), - okeys = DEPRECATED (RELEASE, "'std.table.okeys'", "compose 'std.table.keys' and 'std.table.sort' instead", okeys), - shape = X ("table.shape", "functional.shape", shape), - }, - - methods = { - object = { - prototype = DEPRECATED (RELEASE, "'std.object.prototype'", "use 'std.functional.any (std.object.type, io.type, type)' instead", _type), - type = DEPRECATED (RELEASE, "'std.object.type'", "use 'std.functional.any (std.object.type, io.type, type)' instead", _type), - }, - }, - }, - deprecated) - -end - -return M diff --git a/lib/std/init.lua b/lib/std/init.lua index bf2494a..af6b3d7 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -36,8 +36,6 @@ local string_format = string.format local string_match = string.match -local deprecated = require "std.delete-after.a-year" - local _ = { debug_init = require "std.debug_init", std = require "std.base", @@ -437,12 +435,6 @@ for _, api in ipairs {"barrel", "monkey_patch", "version"} do end -if deprecated then - M = merge (M, deprecated.std) -end - - - --- Metamethods -- @section Metamethods diff --git a/lib/std/object.lua b/lib/std/object.lua index 1921772..7b11740 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -26,8 +26,6 @@ local _ENV = _ENV local getmetatable = getmetatable -local deprecated = require "std.delete-after.a-year" - local _ = { debug_init = require "std.debug_init", container = require "std.container", @@ -126,11 +124,6 @@ local methods = { } -if deprecated then - methods = merge (methods, deprecated.methods.object) -end - - --- Object prototype. -- @object prototype -- @string[opt="Object"] _type object name @@ -162,16 +155,7 @@ local prototype = Container { } -local M = { +return Module { prototype = prototype, type = function (x) return (getmetatable (x) or {})._type end, } - -if deprecated then - -- Yes, we really are overwriting the new fast object.type with the - -- deprecation warning backwards compatible version here! - M = merge (deprecated.object, M) -end - - -return Module (M) diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 5ee3e3f..91fad0b 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -34,8 +34,6 @@ local tostring = tostring local table_concat = table.concat -local deprecated = require "std.delete-after.2016-01-31" - local _ = { debug_init = require "std.debug_init", object = require "std.object", @@ -113,10 +111,6 @@ local methods = { concat = argscheck ("std.strbuf.concat (StrBuf, any)", __concat), } -if deprecated then - methods = merge (methods, deprecated.strbuf) -end - --[[ ================== ]]-- @@ -137,7 +131,7 @@ end -- print (a, b) --> 1234 1234fivesixseven -- os.exit (0) -local M = { +return Module { prototype = Object { _type = "std.strbuf.StrBuf", @@ -164,10 +158,3 @@ local M = { __tostring = __tostring, }, } - -if deprecated then - M = merge (M, deprecated.strbuf) -end - - -return Module (M) diff --git a/lib/std/table.lua b/lib/std/table.lua index b9b9975..0e0a618 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -24,8 +24,6 @@ local table_insert = table.insert local table_unpack = table.unpack or unpack -local deprecated = require "std.delete-after.a-year" - local _ = { debug_init = require "std.debug_init", std = require "std.base", @@ -464,11 +462,6 @@ M = { monkeys = copy ({}, M) -- before deprecations and core merge -if deprecated then - M = merge (M, deprecated.table) -end - - return merge (M, table) diff --git a/local.mk b/local.mk index 39f2db9..1a9d9df 100644 --- a/local.mk +++ b/local.mk @@ -85,14 +85,6 @@ dist_luastd_DATA = \ lib/std/version.lua \ $(NOTHING_ELSE) -luastddeletedir = $(luastddir)/delete-after - -dist_luastddelete_DATA = \ - lib/std/delete-after/2016-01-31.lua \ - lib/std/delete-after/2016-03-08.lua \ - lib/std/delete-after/a-year.lua \ - $(NOTHING_ELSE) - # For bugwards compatibility with LuaRocks 2.1, while ensuring that # `require "std.debug_init"` continues to work, we have to install # the former `$(luadir)/std/debug_init.lua` to `debug_init/init.lua`. diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 022bdc4..21758de 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -3,16 +3,7 @@ before: | this_module = "std.debug" global_table = "_G" - extend_base = { "getfenv", "setfenv", "say", "trace", "_setdebug" } - - deprecations = { "argcheck", "argerror", "argscheck", "extramsg_mismatch", - "extramsg_toomany", "parsetypes", "resulterror", - "toomanyargmsg", "typesplit" } - - setdebug { deprecate = false } - - deprecate_on = bind (deprecation, {"nil", this_module}) - deprecate_off = bind (deprecation, {false, this_module}) + extend_base = { "getfenv", "setfenv", "say", "trace" } M = require (this_module) @@ -27,11 +18,8 @@ specify std.debug: expect (show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core debug table: - apis = {} - for _, v in ipairs (extend_base) do apis[#apis + 1] = v end - for _, v in ipairs (deprecations) do apis[#apis + 1] = v end expect (show_apis {from=base_module, not_in=this_module}). - to_contain.a_permutation_of (apis) + to_contain.a_permutation_of (extend_base) - context via the std module: - it does not touch the global table: @@ -42,427 +30,6 @@ specify std.debug: to_equal {} -- describe resulterror: - - before: | - function mkstack (level) - return string.format ([[ - _DEBUG = nil -- line 1 - local debug = require "std.debug" -- line 2 - require "std.debug_init"._DEBUG.deprecate = false -- line 3 - function ohnoes () -- line 4 - debug.resulterror ("ohnoes", 1, nil, %s) -- line 5 - end -- line 6 - function caller () -- line 7 - local r = ohnoes () -- line 8 - return "not a tail call" -- line 9 - end -- line 10 - caller () -- line 11 - ]], tostring (level)) - end - - f = M.resulterror - - - it writes a deprecation warning: - expect (deprecate_on ("resulterror", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("resulterror", "{}")). - not_to_contain_error "was deprecated" - - - it blames the call site by default: | - expect (luaproc (mkstack ())).to_contain_error ":5: bad result" - - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":5: bad result" - expect (luaproc (mkstack (2))).to_contain_error ":8: bad result" - - it reports the calling function name: - expect (f ('expect', 1)).to_raise "'expect'" - - it reports the argument number: | - expect (f ('expect', 12345)).to_raise "#12345" - - it reports extra message in parentheses: - expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" - - -- describe argerror: - - before: | - function mkstack (level) - return string.format ([[ - _DEBUG = nil -- line 1 - local debug = require "std.debug" -- line 2 - require "std.debug_init"._DEBUG.deprecate = false -- line 3 - function ohnoes () -- line 4 - debug.argerror ("ohnoes", 1, nil, %s) -- line 5 - end -- line 6 - function caller () -- line 7 - local r = ohnoes () -- line 8 - return "not a tail call" -- line 9 - end -- line 10 - caller () -- line 11 - ]], tostring (level)) - end - - f, badarg = init (M, this_module, "argerror") - - - it writes a deprecation warning: - expect (deprecate_on ("argerror", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("argerror", "{}")). - not_to_contain_error "was deprecated" - - - it blames the call site by default: | - expect (luaproc (mkstack ())).to_contain_error ":5: bad argument" - - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":5: bad argument" - expect (luaproc (mkstack (2))).to_contain_error ":8: bad argument" - - it reports the calling function name: - expect (f ('expect', 1)).to_raise "'expect'" - - it reports the argument number: | - expect (f ('expect', 12345)).to_raise "#12345" - - it reports extra message in parentheses: - expect (f ('expect', 1, "extramsg")).to_raise " (extramsg)" - - -- describe argcheck: - - before: | - Object = require "std.object".prototype - List = Object { _type = "List" } - Foo = Object { _type = "Foo" } - - function fn (...) return M.argcheck ('expect', 1, ...) end - - function mkstack (level, debugp) - return string.format ([[ - _DEBUG = %s -- line 1 - local debug = require "std.debug" -- line 2 - require "std.debug_init"._DEBUG.deprecate = false -- line 3 - function ohnoes (t) -- line 4 - debug.argcheck ("ohnoes", 1, "table", t, %s) -- line 5 - end -- line 6 - function caller () -- line 7 - local r = ohnoes "not a table" -- line 8 - return "not a tail call" -- line 9 - end -- line 10 - caller () -- line 11 - ]], tostring (debugp), tostring (level)) - end - - f, badarg = init (M, this_module, "argcheck") - - - it writes a deprecation warning: - expect (deprecate_on ("argcheck", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("argcheck", "{}")). - not_to_contain_error "was deprecated" - - - it blames the calling function by default: | - expect (luaproc (mkstack ())).to_contain_error ":5: bad argument" - - it honors optional call stack level reporting: | - expect (luaproc (mkstack (1))).to_contain_error ":5: bad argument" - expect (luaproc (mkstack (2))).to_contain_error ":8: bad argument" - expect (luaproc (mkstack (3))).to_contain_error ":11: bad argument" - - it can be disabled by setting _DEBUG to false: - expect (luaproc (mkstack (nil, false))). - not_to_contain_error "bad argument" - - it can be disabled by setting _DEBUG.argcheck to false: - expect (luaproc (mkstack (nil, "{ argcheck = false }"))). - not_to_contain_error "bad argument" - - it is not disabled by setting _DEBUG.argcheck to true: - expect (luaproc (mkstack (nil, "{ argcheck = true }"))). - to_contain_error "bad argument" - - it is not disabled by leaving _DEBUG.argcheck unset: - expect (luaproc (mkstack (nil, "{}"))). - to_contain_error "bad argument" - - - context with primitives: - - it diagnoses missing types: - expect (fn ("bool", nil)).to_raise "boolean expected, got no value" - expect (fn ("boolean", nil)).to_raise "boolean expected, got no value" - expect (fn ("file", nil)).to_raise "FILE* expected, got no value" - expect (fn ("number", nil)).to_raise "number expected, got no value" - expect (fn ("string", nil)).to_raise "string expected, got no value" - expect (fn ("table", nil)).to_raise "table expected, got no value" - - it diagnoses mismatched types: - expect (fn ("bool", {0})).to_raise "boolean expected, got table" - expect (fn ("boolean", {0})).to_raise "boolean expected, got table" - expect (fn ("file", {0})).to_raise "FILE* expected, got table" - expect (fn ("number", {0})).to_raise "number expected, got table" - expect (fn ("string", {0})).to_raise "string expected, got table" - expect (fn ("table", false)).to_raise "table expected, got boolean" - - it matches types: - expect (fn ("bool", true)).not_to_raise "any error" - expect (fn ("boolean", true)).not_to_raise "any error" - expect (fn ("file", io.stderr)).not_to_raise "any error" - expect (fn ("number", 1)).not_to_raise "any error" - expect (fn ("string", "s")).not_to_raise "any error" - expect (fn ("table", {})).not_to_raise "any error" - expect (fn ("table", require "std.object")).not_to_raise "any error" - - - context with int: - - it diagnoses missing types: - expect (fn ("int", nil)).to_raise "int expected, got no value" - - it diagnoses mismatched types: - expect (fn ("int", false)).to_raise "int expected, got boolean" - expect (fn ("int", 1.234)).to_raise "int expected, got number" - expect (fn ("int", 1234e-3)).to_raise "int expected, got number" - - it matches types: - expect (fn ("int", 1)).not_to_raise "any error" - expect (fn ("int", 1.0)).not_to_raise "any error" - expect (fn ("int", 0x1234)).not_to_raise "any error" - expect (fn ("int", 1.234e3)).not_to_raise "any error" - - context with constant string: - - it diagnoses missing types: - expect (fn (":foo", nil)).to_raise ":foo expected, got no value" - - it diagnoses mismatched types: - expect (fn (":foo", false)).to_raise ":foo expected, got boolean" - expect (fn (":foo", ":bar")).to_raise ":foo expected, got :bar" - expect (fn (":foo", "foo")).to_raise ":foo expected, got string" - - it matches types: - expect (fn (":foo", ":foo")).not_to_raise "any error" - - context with callable types: - - it diagnoses missing types: - expect (fn ("func", nil)).to_raise "function expected, got no value" - expect (fn ("function", nil)).to_raise "function expected, got no value" - - it diagnoses mismatched types: - expect (fn ("func", {0})).to_raise "function expected, got table" - expect (fn ("function", {0})).to_raise "function expected, got table" - - it matches types: - expect (fn ("func", function () end)).not_to_raise "any error" - expect (fn ("func", setmetatable ({}, {__call = function () end}))). - not_to_raise "any error" - expect (fn ("function", function () end)).not_to_raise "any error" - expect (fn ("function", setmetatable ({}, {__call = function () end}))). - not_to_raise "any error" - - context with table of homogenous elements: - - it diagnoses missing types: - expect (fn ("table of boolean", nil)). - to_raise "table expected, got no value" - expect (fn ("table of booleans", nil)). - to_raise "table expected, got no value" - - it diagnoses mismatched types: - expect (fn ("table of file", io.stderr)). - to_raise "table expected, got file" - expect (fn ("table of files", io.stderr)). - to_raise "table expected, got file" - - it diagnoses mismatched element types: - expect (fn ("table of number", {false})). - to_raise "table of numbers expected, got boolean at index 1" - expect (fn ("table of numbers", {1, 2, "3"})). - to_raise "table of numbers expected, got string at index 3" - expect (fn ("table of numbers", {a=1, b=2, c="3"})). - to_raise "table of numbers expected, got string at index c" - - it matches types: - expect (fn ("table of string", {})).not_to_raise "any error" - expect (fn ("table of string", {"foo"})).not_to_raise "any error" - expect (fn ("table of string", {"f", "o", "o"})).not_to_raise "any error" - expect (fn ("table of string", {b="b", a="a", r="r"})).not_to_raise "any error" - - context with non-empty table types: - - it diagnoses missing types: - expect (fn ("#table", nil)). - to_raise "non-empty table expected, got no value" - - it diagnoses mismatched types: - expect (fn ("#table", false)). - to_raise "non-empty table expected, got boolean" - expect (fn ("#table", {})). - to_raise "non-empty table expected, got empty table" - - it matches types: - expect (fn ("#table", {0})).not_to_raise "any error" - - context with non-empty table of homogenous elements: - - it diagnoses missing types: - expect (fn ("#table of boolean", nil)). - to_raise "non-empty table expected, got no value" - expect (fn ("#table of booleans", nil)). - to_raise "non-empty table expected, got no value" - - it diagnoses mismatched types: - expect (fn ("#table of file", {})). - to_raise "non-empty table expected, got empty table" - expect (fn ("#table of file", io.stderr)). - to_raise "non-empty table expected, got file" - - it diagnoses mismatched element types: - expect (fn ("#table of number", {false})). - to_raise "non-empty table of numbers expected, got boolean at index 1" - expect (fn ("#table of numbers", {1, 2, "3"})). - to_raise "non-empty table of numbers expected, got string at index 3" - expect (fn ("#table of numbers", {a=1, b=2, c="3"})). - to_raise "non-empty table of numbers expected, got string at index c" - - it matches types: - expect (fn ("#table of string", {"foo"})).not_to_raise "any error" - expect (fn ("#table of string", {"f", "o", "o"})).not_to_raise "any error" - expect (fn ("#table of string", {b="b", a="a", r="r"})).not_to_raise "any error" - - context with list: - - it diagnonses missing types: - expect (fn ("list", nil)). - to_raise "list expected, got no value" - - it diagnoses mismatched types: - expect (fn ("list", false)). - to_raise "list expected, got boolean" - expect (fn ("list", {foo=1})). - to_raise "list expected, got table" - expect (fn ("list", Object)). - to_raise "list expected, got Object" - - it matches types: - expect (fn ("list", {})).not_to_raise "any error" - expect (fn ("list", {1})).not_to_raise "any error" - - context with list of homogenous elements: - - it diagnoses missing types: - expect (fn ("list of boolean", nil)). - to_raise "list expected, got no value" - expect (fn ("list of booleans", nil)). - to_raise "list expected, got no value" - - it diagnoses mismatched types: - expect (fn ("list of file", io.stderr)). - to_raise "list expected, got file" - expect (fn ("list of files", io.stderr)). - to_raise "list expected, got file" - expect (fn ("list of files", {file=io.stderr})). - to_raise "list expected, got table" - - it diagnoses mismatched element types: - expect (fn ("list of number", {false})). - to_raise "list of numbers expected, got boolean at index 1" - expect (fn ("list of numbers", {1, 2, "3"})). - to_raise "list of numbers expected, got string at index 3" - - it matches types: - expect (fn ("list of string", {})).not_to_raise "any error" - expect (fn ("list of string", {"foo"})).not_to_raise "any error" - expect (fn ("list of string", {"f", "o", "o"})).not_to_raise "any error" - - context with non-empty list: - - it diagnonses missing types: - expect (fn ("#list", nil)). - to_raise "non-empty list expected, got no value" - - it diagnoses mismatched types: - expect (fn ("#list", false)). - to_raise "non-empty list expected, got boolean" - expect (fn ("#list", {})). - to_raise "non-empty list expected, got empty list" - expect (fn ("#list", {foo=1})). - to_raise "non-empty list expected, got table" - expect (fn ("#list", Object)). - to_raise "non-empty list expected, got empty Object" - expect (fn ("#list", List {})). - to_raise "non-empty list expected, got empty List" - - it matches types: - expect (fn ("#list", {1})).not_to_raise "any error" - - context with non-empty list of homogenous elements: - - it diagnoses missing types: - expect (fn ("#list of boolean", nil)). - to_raise "non-empty list expected, got no value" - expect (fn ("#list of booleans", nil)). - to_raise "non-empty list expected, got no value" - - it diagnoses mismatched types: - expect (fn ("#list of file", {})). - to_raise "non-empty list expected, got empty table" - expect (fn ("#list of file", io.stderr)). - to_raise "non-empty list expected, got file" - expect (fn ("#list of files", {file=io.stderr})). - to_raise "non-empty list expected, got table" - - it diagnoses mismatched element types: - expect (fn ("#list of number", {false})). - to_raise "non-empty list of numbers expected, got boolean at index 1" - expect (fn ("#list of numbers", {1, 2, "3"})). - to_raise "non-empty list of numbers expected, got string at index 3" - - it matches types: - expect (fn ("#list of string", {"foo"})).not_to_raise "any error" - expect (fn ("#list of string", {"f", "o", "o"})).not_to_raise "any error" - - context with container: - - it diagnoses missing types: - expect (fn ("List of boolean", nil)). - to_raise "List expected, got no value" - expect (fn ("List of booleans", nil)). - to_raise "List expected, got no value" - - it diagnoses mismatched types: - expect (fn ("List of file", io.stderr)). - to_raise "List expected, got file" - expect (fn ("List of files", io.stderr)). - to_raise "List expected, got file" - expect (fn ("List of files", {file=io.stderr})). - to_raise "List expected, got table" - - it diagnoses mismatched element types: - expect (fn ("List of number", List {false})). - to_raise "List of numbers expected, got boolean at index 1" - expect (fn ("List of numbers", List {1, 2, "3"})). - to_raise "List of numbers expected, got string at index 3" - - it matches types: - expect (fn ("list of string", List {})).not_to_raise "any error" - expect (fn ("list of string", List {"foo"})).not_to_raise "any error" - expect (fn ("list of string", List {"f", "o", "o"})).not_to_raise "any error" - - context with object: - - it diagnoses missing types: - expect (fn ("object", nil)).to_raise "object expected, got no value" - expect (fn ("Object", nil)).to_raise "Object expected, got no value" - expect (fn ("Foo", nil)).to_raise "Foo expected, got no value" - expect (fn ("any", nil)).to_raise "any value expected, got no value" - - it diagnoses mismatched types: - expect (fn ("object", {0})).to_raise "object expected, got table" - expect (fn ("Object", {0})).to_raise "Object expected, got table" - expect (fn ("object", {_type="Object"})).to_raise "object expected, got table" - expect (fn ("Object", {_type="Object"})).to_raise "Object expected, got table" - expect (fn ("Object", Foo)).to_raise "Object expected, got Foo" - expect (fn ("Foo", {0})).to_raise "Foo expected, got table" - expect (fn ("Foo", Object)).to_raise "Foo expected, got Object" - - it matches types: - expect (fn ("object", Object)).not_to_raise "any error" - expect (fn ("object", Object {})).not_to_raise "any error" - expect (fn ("object", Foo)).not_to_raise "any error" - expect (fn ("object", Foo {})).not_to_raise "any error" - - it matches anything: - expect (fn ("any", true)).not_to_raise "any error" - expect (fn ("any", {})).not_to_raise "any error" - expect (fn ("any", Object)).not_to_raise "any error" - expect (fn ("any", Foo {})).not_to_raise "any error" - - context with a list of valid types: - - it diagnoses missing elements: - expect (fn ("string|table", nil)). - to_raise "string or table expected, got no value" - expect (fn ("string|list|#table", nil)). - to_raise "string, list or non-empty table expected, got no value" - expect (fn ("string|number|list|object", nil)). - to_raise "string, number, list or object expected, got no value" - - it diagnoses mismatched elements: - expect (fn ("string|table", false)). - to_raise "string or table expected, got boolean" - expect (fn ("string|#table", {})). - to_raise "string or non-empty table expected, got empty table" - expect (fn ("string|number|#list|object", {})). - to_raise "string, number, non-empty list or object expected, got empty table" - - it matches any type from a list: - expect (fn ("string|table", "foo")).not_to_raise "any error" - expect (fn ("string|table", {})).not_to_raise "any error" - expect (fn ("string|table", {0})).not_to_raise "any error" - expect (fn ("table|table", {})).not_to_raise "any error" - expect (fn ("#table|table", {})).not_to_raise "any error" - - context with an optional type element: - - it diagnoses mismatched elements: - expect (fn ("?boolean", "string")). - to_raise "boolean or nil expected, got string" - expect (fn ("?boolean|:symbol", {})). - to_raise "boolean, :symbol or nil expected, got empty table" - - it matches nil against a single type: - expect (fn ("?any", nil)).not_to_raise "any error" - expect (fn ("?boolean", nil)).not_to_raise "any error" - expect (fn ("?string", nil)).not_to_raise "any error" - - it matches nil against a list of types: - expect (fn ("?boolean|table", nil)).not_to_raise "any error" - expect (fn ("?string|table", nil)).not_to_raise "any error" - expect (fn ("?table|#table", nil)).not_to_raise "any error" - expect (fn ("?#table|table", nil)).not_to_raise "any error" - - it matches nil against a list of optional types: - expect (fn ("?boolean|?table", nil)).not_to_raise "any error" - expect (fn ("?string|?table", nil)).not_to_raise "any error" - expect (fn ("?table|?#table", nil)).not_to_raise "any error" - expect (fn ("?#table|?table", nil)).not_to_raise "any error" - - it matches any named type: - expect (fn ("?any", false)).not_to_raise "any error" - expect (fn ("?boolean", false)).not_to_raise "any error" - expect (fn ("?string", "string")).not_to_raise "any error" - - it matches any type from a list: - expect (fn ("?boolean|table", {})).not_to_raise "any error" - expect (fn ("?string|table", {0})).not_to_raise "any error" - expect (fn ("?table|#table", {})).not_to_raise "any error" - expect (fn ("?#table|table", {})).not_to_raise "any error" - - it matches any type from a list with several optional specifiers: - expect (fn ("?boolean|?table", {})).not_to_raise "any error" - expect (fn ("?string|?table", {0})).not_to_raise "any error" - expect (fn ("?table|?table", {})).not_to_raise "any error" - expect (fn ("?#table|?table", {})).not_to_raise "any error" - - - describe debug: - before: | function mkwrap (k, v) @@ -501,487 +68,6 @@ specify std.debug: to_contain_error "debugging" -- describe argscheck: - - before: | - function mkstack (name, spec) - return string.format ([[ - local argscheck = require "std.debug".argscheck -- line 1 - local function caller () -- line 2 - argscheck ("%s", function () end) -- line 3 - end -- line 4 - caller () -- line 5 - ]], tostring (name), tostring (spec)) - end - - f = M.argscheck - - mkmagic = function () return "MAGIC" end - wrapped = f ("inner ()", mkmagic) - - _, badarg, badresult = init (M, "", "inner") - id = function (...) return ... end - - - it writes a deprecation warning: - expect (deprecate_on ("argscheck", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("argscheck", "{}")). - not_to_contain_error "was deprecated" - - - it returns the wrapped function: - expect (wrapped).not_to_be (inner) - expect (wrapped ()).to_be "MAGIC" - - it does not wrap the function when _ARGCHECK is disabled: | - script = [[ - _DEBUG = false - local debug = require "std.debug_init" - local argscheck = require "std.debug".argscheck - local function inner () return "MAGIC" end - local wrapped = argscheck ("inner (?any)", inner) - os.exit (wrapped == inner and 0 or 1) - ]] - expect (luaproc (script)).to_succeed () - - - context when checking zero argument function: - - it diagnoses too many arguments: - expect (wrapped (false)).to_raise (badarg (1)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - - - context when checking single argument function: - - before: - wrapped = f ("inner (#table)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "non-empty table")) - - it diagnoses wrong argument types: - expect (wrapped {}).to_raise (badarg (1, "non-empty table", "empty table")) - - it diagnoses too many arguments: - expect (wrapped ({1}, 2, nop, "", false)).to_raise (badarg (1, 5)) - - it accepts correct argument types: - expect (wrapped ({1})).to_be "MAGIC" - - - context when checking multi-argument function: - - before: - wrapped = f ("inner (table, function)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "table")) - expect (wrapped ({})).to_raise (badarg (2, "function")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "table", "boolean")) - expect (wrapped ({}, false)).to_raise (badarg (2, "function", "boolean")) - - it diagnoses too many arguments: - expect (wrapped ({}, nop, false)).to_raise (badarg (3)) - - it accepts correct argument types: - expect (wrapped ({}, nop)).to_be "MAGIC" - - - context when checking nil argument function: - - before: - wrapped = f ("inner (?int, string)", mkmagic) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int or nil", "boolean")) - expect (wrapped (1, false)).to_raise (badarg (2, "string", "boolean")) - expect (wrapped (nil, false)).to_raise (badarg (2, "string", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, "foo", nop)).to_raise (badarg (3)) - expect (wrapped (nil, "foo", nop)).to_raise (badarg (3)) - - it accepts correct argument types: - expect (wrapped (1, "foo")).to_be "MAGIC" - expect (wrapped (nil, "foo")).to_be "MAGIC" - - - context when checking optional multi-argument function: - - before: - wrapped = f ("inner ([int], string)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "int or string")) - expect (wrapped (1)).to_raise (badarg (2, "string")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int or string", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, "two", nop)).to_raise (badarg (3)) - - it accepts correct argument types: - expect (wrapped ("two")).to_be "MAGIC" - expect (wrapped (1, "two")).to_be "MAGIC" - - - context when checking final optional multi-argument function: - - before: - wrapped = f ("inner (?any, ?string, [any])", mkmagic) - - it diagnoses wrong argument types: - expect (wrapped (1, false)).to_raise (badarg (2, "string or nil", "boolean")) - expect (wrapped (nil, false)).to_raise (badarg (2, "string or nil", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, "two", 3, false)).to_raise (badarg (4)) - expect (wrapped (nil, "two", 3, false)).to_raise (badarg (4)) - expect (wrapped (1, nil, 3, false)).to_raise (badarg (4)) - expect (wrapped (nil, nil, 3, false)).to_raise (badarg (4)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - expect (wrapped (1)).to_be "MAGIC" - expect (wrapped (nil, "two")).to_be "MAGIC" - expect (wrapped (1, "two")).to_be "MAGIC" - expect (wrapped (nil, nil, 3)).to_be "MAGIC" - expect (wrapped (1, nil, 3)).to_be "MAGIC" - expect (wrapped (nil, "two", 3)).to_be "MAGIC" - expect (wrapped ("one", "two", 3)).to_be "MAGIC" - - - context when checking final ellipsis function: - - before: - wrapped = f ("inner (string, int...)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "string")) - expect (wrapped ("foo")).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badarg (12, "int", "boolean")) - - it accepts correct argument types: - expect (wrapped ("foo", 1)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" - - - context when checking optional final parameter: - - context with single argument: - - before: - wrapped = f ("inner ([int])", mkmagic) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "int", "boolean")) - - it diagnoses too many arguments: - expect (wrapped (1, nop)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (wrapped ()).to_be "MAGIC" - expect (wrapped (1)).to_be "MAGIC" - - context with trailing ellipsis: - - before: - wrapped = f ("inner (string, [int]...)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badarg (12, "int", "boolean")) - - it accepts correct argument types: - expect (wrapped ("foo")).to_be "MAGIC" - expect (wrapped ("foo", 1)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" - - context with inner ellipsis: - - before: - wrapped = f ("inner (string, [int...])", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badarg (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badarg (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badarg (12, "int", "boolean")) - - it accepts correct argument types: - expect (wrapped ("foo")).to_be "MAGIC" - expect (wrapped ("foo", 1)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2)).to_be "MAGIC" - expect (wrapped ("foo", 1, 2, 5)).to_be "MAGIC" - - - context when omitting self type: - - before: - me = { - wrapped = f ("me:inner (string)", mkmagic) - } - _, badarg, badresult = init (M, "", "me:inner") - - it diagnoses missing arguments: - expect (me:wrapped ()).to_raise (badarg (1, "string")) - - it diagnoses wrong argument types: - expect (me:wrapped (false)).to_raise (badarg (1, "string", "boolean")) - - it diagnoses too many arguments: - expect (me:wrapped ("foo", false)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (me:wrapped ("foo")).to_be "MAGIC" - - - context with too many args: - - before: - wrapped = f ("inner ([string], int)", mkmagic) - - it diagnoses missing arguments: - expect (wrapped ()).to_raise (badarg (1, "string or int")) - expect (wrapped ("one")).to_raise (badarg (2, "int")) - - it diagnoses wrong argument types: - expect (wrapped (false)).to_raise (badarg (1, "string or int", "boolean")) - expect (wrapped ("one", false)).to_raise (badarg (2, "int", "boolean")) - - it diagnoses too many arguments: - expect (wrapped ("one", 2, false)).to_raise (badarg (3)) - expect (wrapped (1, false)).to_raise (badarg (2)) - - it accepts correct argument types: - expect (wrapped (1)).to_be "MAGIC" - expect (wrapped ("one", 2)).to_be "MAGIC" - - - context when checking single return value function: - - before: | - wrapped = f ("inner (?any...) => #table", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "non-empty table")) - - it diagnoses wrong result types: - expect (wrapped {}). - to_raise (badresult (1, "non-empty table", "empty table")) - - it diagnoses too many results: - expect (wrapped ({1}, 2, nop, "", false)).to_raise (badresult (1, 5)) - - it accepts correct results: - expect ({wrapped {1}}).to_equal {{1}} - - - context with variant single return value function: - - before: - wrapped = f ("inner (?any...) => int or nil", id) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, nop)).to_raise (badresult (2)) - - it accepts correct result types: - expect ({wrapped ()}).to_equal {} - expect ({wrapped (1)}).to_equal {1} - - - context when checking multi-return value function: - - before: - wrapped = f ("inner (?any...) => int, string", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "int")) - expect (wrapped (1)).to_raise (badresult (2, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int", "boolean")) - expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", false)).to_raise (badresult (3)) - - it accepts correct argument types: - expect ({wrapped (1, "two")}).to_equal {1, "two"} - - - context when checking nil return specifier: - - before: - wrapped = f ("inner (?any...) => ?int, string", id) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) - expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) - expect (wrapped (nil, false)).to_raise (badresult (2, "string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "foo", nop)).to_raise (badresult (3)) - expect (wrapped (nil, "foo", nop)).to_raise (badresult (3)) - - it accepts correct result types: - expect ({wrapped (1, "foo")}).to_equal {1, "foo"} - expect ({wrapped (nil, "foo")}).to_equal {[2] = "foo"} - - - context when checking variant multi-return value function: - - before: - wrapped = f ("inner (?any...) => int, string or string", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "int or string")) - expect (wrapped (1)).to_raise (badresult (2, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", nop)).to_raise (badresult (3)) - - it accepts correct result types: - expect ({wrapped ("two")}).to_equal {"two"} - expect ({wrapped (1, "two")}).to_equal {1, "two"} - - - context when checking variant nil,errmsg pattern function: - - before: - wrapped = f ("inner (?any...) => int, string or nil, string", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (2, "string")) - expect (wrapped (1)).to_raise (badresult (2, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or nil", "boolean")) - expect (wrapped (1, false)).to_raise (badresult (2, "string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", nop)).to_raise (badresult (3)) - expect (wrapped (nil, "errmsg", nop)).to_raise (badresult (3)) - - it accepts correct result types: - expect ({wrapped (1, "two")}).to_equal {1, "two"} - expect ({wrapped (nil, "errmsg")}).to_equal {[2] = "errmsg"} - - - context when checking optional multi-return value function: - - before: - wrapped = f ("inner (?any...) => [int], string", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "int or string")) - expect (wrapped (1)).to_raise (badresult (2, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int or string", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", nop)).to_raise (badresult (3)) - - it accepts correct result types: - expect ({wrapped ("two")}).to_equal {"two"} - expect ({wrapped (1, "two")}).to_equal {1, "two"} - - - context when checking final optional multi-return value function: - - before: - wrapped = f ("inner (?any...) => ?any, ?string, [any]", id) - - it diagnoses wrong result types: - expect (wrapped (1, false)).to_raise (badresult (2, "string or nil", "boolean")) - expect (wrapped (nil, false)).to_raise (badresult (2, "string or nil", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, "two", 3, false)).to_raise (badresult (4)) - expect (wrapped (nil, "two", 3, false)).to_raise (badresult (4)) - expect (wrapped (1, nil, 3, false)).to_raise (badresult (4)) - expect (wrapped (nil, nil, 3, false)).to_raise (badresult (4)) - - it accepts correct result types: - expect ({wrapped ()}).to_equal {} - expect ({wrapped (1)}).to_equal {1} - expect ({wrapped (nil, "two")}).to_equal {[2]="two"} - expect ({wrapped (1, "two")}).to_equal {1, "two"} - expect ({wrapped (nil, nil, 3)}).to_equal {[3]=3} - expect ({wrapped (1, nil, 3)}).to_equal {1, [3]=3} - expect ({wrapped (nil, "two", 3)}).to_equal {[2]="two", [3]=3} - expect ({wrapped ("one", "two", 3)}).to_equal {"one", "two", 3} - - - context when checking optional final result: - - context with single result: - - before: - wrapped = f ("inner (?any...) => [int]", id) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "int", "boolean")) - - it diagnoses too many results: - expect (wrapped (1, nop)).to_raise (badresult (2)) - - it accepts correct result types: - expect ({wrapped ()}).to_equal {} - expect ({wrapped (1)}).to_equal {1} - - context with trailing ellipsis: - - before: - wrapped = f ("inner (?any...) => string, [int]...", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badresult (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badresult (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badresult (12, "int", "boolean")) - - it accepts correct result types: - expect ({wrapped ("foo")}).to_equal {"foo"} - expect ({wrapped ("foo", 1)}).to_equal {"foo", 1} - expect ({wrapped ("foo", 1, 2)}).to_equal {"foo", 1, 2} - expect ({wrapped ("foo", 1, 2, 5)}).to_equal {"foo", 1, 2, 5} - - context with inner ellipsis: - - before: - wrapped = f ("inner (?any...) => string, [int...]", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "string")) - - it diagnoses wrong result types: - expect (wrapped (false)).to_raise (badresult (1, "string", "boolean")) - expect (wrapped ("foo", false)).to_raise (badresult (2, "int", "boolean")) - expect (wrapped ("foo", 1, false)).to_raise (badresult (3, "int", "boolean")) - expect (wrapped ("foo", 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, false)). - to_raise (badresult (12, "int", "boolean")) - - it accepts correct result types: - expect ({wrapped ("foo")}).to_equal {"foo"} - expect ({wrapped ("foo", 1)}).to_equal {"foo", 1} - expect ({wrapped ("foo", 1, 2)}).to_equal {"foo", 1, 2} - expect ({wrapped ("foo", 1, 2, 5)}).to_equal {"foo", 1, 2, 5} - - - context with too many results: - - before: - wrapped = f ("inner (?any...) => [string], int", id) - - it diagnoses missing results: - expect (wrapped ()).to_raise (badresult (1, "string or int")) - expect (wrapped "one").to_raise (badresult (2, "int")) - - it diagnoses wrong result types: - expect (wrapped (false)). - to_raise (badresult (1, "string or int", "boolean")) - expect (wrapped ("one", false)). - to_raise (badresult (2, "int", "boolean")) - - it diagnoses too many results: - expect (wrapped ("one", 2, false)).to_raise (badresult (3)) - expect (wrapped (1, false)).to_raise (badresult (2)) - - it accepts correct argument types: - expect ({wrapped (1)}).to_equal {1} - expect ({wrapped ("one", 2)}).to_equal {"one", 2} - - -- describe extramsg_mismatch: - - before: - f = M.extramsg_mismatch - - - it writes a deprecation warning: - expect (deprecate_on ("extramsg_mismatch", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("extramsg_mismatch", "{}")). - not_to_contain_error "was deprecated" - - - it returns the expected types: - expect (f "nil").to_contain "nil expected, " - expect (f "bool").to_contain "boolean expected, " - expect (f "?bool").to_contain "boolean or nil expected, " - expect (f "string|table").to_contain "string or table expected, " - - it returns expected container types: - expect (f ("table of int", nil, 1)).to_contain "table of ints expected, " - expect (f ("table of int|bool", nil, 1)). - to_contain "table of ints or booleans expected, " - expect (f ("table of int|bool|string", nil, 1)). - to_contain "table of ints, booleans or strings expected, " - expect (f ("table of int|bool|string|table", nil, 1)). - to_contain "table of ints, booleans, strings or tables expected, " - - it returns the actual type: - expect (f ("int")).to_contain ", got no value" - expect (f ("int", false)).to_contain ", got boolean" - expect (f ("int", {})).to_contain ", got empty table" - - it returns table field type: - expect (f ("table of int", nil, 1)).to_contain ", got no value at index 1" - expect (f ("table of int", "two", 2)).to_contain ", got string at index 2" - expect (f ("table of int|bool", "five", 3)).to_contain ", got string at index 3" - - -- describe extramsg_toomany: - - before: - f = M.extramsg_toomany - - - it writes a deprecation warning: - expect (deprecate_on ("extramsg_toomany", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("extramsg_toomany", "{}")). - not_to_contain_error "was deprecated" - - - it returns the expected thing: - expect (f ("mojo", 1, 2)).to_contain "no more than 1 mojo" - - it uses singular thing when 1 is expected: - expect (f ("argument", 1, 2)).to_contain "no more than 1 argument" - - it uses plural thing otherwise: - expect (f ("thing", 0, 3)).to_contain "no more than 0 things" - expect (f ("result", 2, 3)).to_contain "no more than 2 results" - - it returns the actual count: - expect (f ("bad", 0, 1)).to_contain ", got 1" - expect (f ("bad", 99, 999)).to_contain ", got 999" - - -- context function environments: - - before: - env = { - tostring = function (x) return '"' .. tostring (x) .. '"' end, - } - fn = function (x) return tostring (x) end - ft = setmetatable ({}, { __call = function (_, ...) return fn (...) end }) - - - describe getfenv: - - before: - f = M.getfenv - - it returns a table: - expect (type (f (fn))).to_be "table" - - it gets a function execution environment: - M.setfenv (fn, env) - expect (f (fn)).to_be (env) - - it understands functables: - M.setfenv (ft, env) - expect (f (ft)).to_be (env) - - - describe setfenv: - - before: - f = M.setfenv - - it returns the passed function: - expect (f (fn, env)).to_be (fn) - - it sets a function execution environment: - f (fn, env) - expect (fn (42)).to_be '"42"' - - it understands functables: - f (ft, env) - expect (fn (5)).to_be '"5"' - - - describe say: - before: | function mkwrap (k, v) diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml index 1598aaa..379e6ad 100644 --- a/specs/functional_spec.yaml +++ b/specs/functional_spec.yaml @@ -9,12 +9,6 @@ before: "id", "ireverse", "lambda", "map", "map_with", "memoize", "nop", "product", "reduce", "shape", "zip", "zip_with" } - setdebug { deprecate = false } - - deprecate_on = bind (deprecation, {"nil", this_module}) - deprecate_off = bind (deprecation, {false, this_module}) - - function elems (t) local fn, istate, ctrl = base.pairs (t) return function (state, _) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index 34a9d85..d7535dd 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -4,11 +4,6 @@ before: List = M.prototype l = List {"foo", "bar", "baz"} - setdebug { deprecate = false } - - deprecate_on = bind (deprecation, {"nil", this_module}) - deprecate_off = bind (deprecation, {false, this_module}) - specify std.list: - context when required: diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 67c77a9..3d301f2 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -5,11 +5,6 @@ before: Object = object.prototype obj = Object {"foo", "bar", baz="quux"} - setdebug { deprecate = false } - - deprecate_on = bind (deprecation, {"nil", this_module}) - deprecate_off = bind (deprecation, {false, this_module}) - function copy (t) local r = {} for k, v in pairs (t) do r[k] = v end @@ -50,59 +45,6 @@ specify std.object: expect (getmetatable (o)).not_to_be (getmetatable (Object)) expect (getmetatable (o)._baz).to_be "quux" -# std.object.prototype is now something entirely different to -# Object.prototype as specified here. -- describe prototype: - - before: - o = Object {} - fn = Object.prototype - - - it writes a deprecation warning: - expect (deprecate_on ("prototype", "", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("prototype", "", "{}")). - not_to_contain_error "was deprecated" - - - context when called from the object module: - - it reports the prototype stored in the object's metatable: - expect (fn (o)).to_be "Object" - - it reports the type of a cloned object: - expect (fn (o {})).to_be "Object" - - it reports the type of a derived object: - Example = Object {_type = "Example"} - expect (fn (Example)).to_be "Example" - - it reports the type of a cloned derived object: - Portal = Object {_type = "Demon"} - p = Portal {} - expect (fn (p)).to_be "Demon" - expect (fn (p {})).to_be "Demon" - - it recognizes a file object: - h = io.open (os.tmpname ()) - expect (fn (h)).to_be "file" - h:close () - expect (fn (h)).to_be "closed file" - - it recognizes a primitive object: - expect (fn (nil)).to_be "nil" - expect (fn (false)).to_be "boolean" - expect (fn (0.0)).to_be "number" - expect (fn "0.0").to_be "string" - expect (fn (function () end)).to_be "function" - expect (fn {}).to_be "table" - - - context when called as an object method: - - it reports the type stored in the object's metatable: - expect (o:prototype ()).to_be "Object" - - it reports the type of a cloned object: - expect ((o {}):prototype ()).to_be "Object" - - it reports the type of a subclassed object: - Example = Object {_type = "Example"} - expect (Example:prototype ()).to_be "Example" - - it reports the type of a cloned subclassed object: - Portal = Object {_type = "Demon"} - p = Portal {} - expect (p:prototype ()).to_be "Demon" - expect ((p {}):prototype ()).to_be "Demon" - - describe type: - before: @@ -110,12 +52,6 @@ specify std.object: fn = object.type - context when called from the object module: - - it writes an argument passing deprecation warning: - expect (deprecate_on ("type", "{}")). - to_contain_error "non-object argument to 'std.object.type' was deprecated" - expect (deprecate_off ("type", "{}")). - not_to_contain_error "was deprecated" - - it reports the type stored in the object's metatable: expect (fn (o)).to_be "Object" - it reports the type of a cloned object: @@ -129,52 +65,11 @@ specify std.object: expect (fn (p)).to_be "Demon" expect (fn (p {})).to_be "Demon" - it returns nil for a primitive object: - # Have to use a separate process, because we already loaded - # debug_init and thus deprecated methods into this process - expect (deprecation ("true", this_module, "type", "nil")). - to_output "nil\n" - expect (deprecation ("true", this_module, "type", "false")). - to_output "nil\n" - expect (deprecation ("true", this_module, "type", "0.0")). - to_output "nil\n" - expect (deprecation ("true", this_module, "type", "'0.0'")). - to_output "nil\n" - expect (deprecation ("true", this_module, "type", "function () end")). - to_output "nil\n" - expect (deprecation ("true", this_module, "type", "{}")). - to_output "nil\n" - - - context when called as an object method: - - it writes a deprecation warning: - expect (deprecate_on ("type", "", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("type", "", "{}")). - not_to_contain_error "was deprecated" - - - it reports the type stored in the object's metatable: - expect (o:type ()).to_be "Object" - - it reports the type of a cloned object: - expect ((o {}):type ()).to_be "Object" - - it reports the type of a subclassed object: - Example = Object {_type = "Example"} - expect (Example:type ()).to_be "Example" - - it reports the type of a cloned subclassed object: - Portal = Object {_type = "Demon"} - p = Portal {} - expect (p:type ()).to_be "Demon" - expect ((p {}):type ()).to_be "Demon" - - it recognizes a file object: - h = io.open (os.tmpname ()) - expect (o.type (h)).to_be "file" - h:close () - expect (o.type (h)).to_be "closed file" - - it recognizes a primitive object: - expect (o.type (nil)).to_be "nil" - expect (o.type (false)).to_be "boolean" - expect (o.type (0.0)).to_be "number" - expect (o.type "0.0").to_be "string" - expect (o.type (function () end)).to_be "function" - expect (o.type {}).to_be "table" + expect (fn (nil)).to_be (nil) + expect (fn (0.0)).to_be (nil) + expect (fn ('0.0')).to_be (nil) + expect (fn (function () end)).to_be (nil) + expect (fn {}).to_be (nil) - describe instantiation from a prototype: @@ -314,10 +209,10 @@ specify std.object: - context with no custom instance methods: # :type is a method defined by the root object - it inherits prototype object methods: - instance = Object {} + instance = Object { type = object.type } expect (instance:type ()).to_be "Object" - it propagates prototype methods to derived instances: - Derived = Object {_type = "Derived"} + Derived = Object { _type = "Derived", type = object.type } instance = Derived {} expect (instance:type ()).to_be "Derived" - context with custom object methods: @@ -329,6 +224,7 @@ specify std.object: self[item] = (self[item] or 0) + 1 return self end, + type = object.type, }, } - it inherits prototype object methods: diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 7f17c4f..754d47f 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -5,12 +5,6 @@ before: | exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", "ielems", "ipairs", "monkey_patch", "npairs", "pairs", "require", "ripairs", "rnpairs", "tostring" } - deprecations = { "ireverse" } - - setdebug { deprecate = false } - - deprecate_on = bind (deprecation, {"nil", this_module}) - deprecate_off = bind (deprecation, {false, this_module}) -- Tables with iterator metamethods used by various examples. __pairs = setmetatable ({ content = "a string" }, { @@ -43,10 +37,7 @@ specify std: - it exports the documented apis: t = {} for k in pairs (M) do t[#t + 1] = k end - apis = {} - for i, v in ipairs (exported_apis) do apis[i] = v end - for _, v in ipairs (deprecations) do apis[#apis + 1] = v end - expect (t).to_contain.a_permutation_of (apis) + expect (t).to_contain.a_permutation_of (exported_apis) - context when lazy loading: - it has no submodules on initial load: @@ -332,29 +323,6 @@ specify std: expect (t).to_equal {} -- describe ireverse: - - before: - f = M.ireverse - - - it writes a deprecation warning: - expect (deprecate_on ("ireverse", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("ireverse", "{}")). - not_to_contain_error "was deprecated" - - - it returns a new list: - t = {1, 2, 5} - expect (f (t)).not_to_be (t) - - it reverses the elements relative to the original list: - expect (f {1, 2, "five"}).to_equal {"five", 2, 1} - - it ignores the dictionary part of a table: - expect (f {1, 2, "five"; a = "b", c = "d"}).to_equal {"five", 2, 1} - - it respects __len metamethod: - expect (f (__index)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} - - it works for an empty list: - expect (f {}).to_equal {} - - - describe monkey_patch: - before: io_mt = {} diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index ac90975..fc2afa7 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -4,10 +4,6 @@ before: StrBuf = require (this_module).prototype b = StrBuf {"foo", "bar"} - setdebug { deprecate = false } - - deprecate_on = bind (deprecation, {"nil", this_module}) - deprecate_off = bind (deprecation, {false, this_module}) specify std.strbuf: - describe require: @@ -54,29 +50,8 @@ specify std.strbuf: - describe tostring: - - context as a module function: - - it writes a deprecation warning: - expect (deprecate_on ("tostring", "P {'foo', 'bar'}")). - to_contain_error "was deprecated" - expect (deprecate_off ("tostring", "P {'foo', 'bar'}")). - not_to_contain_error "was deprecated" - - - it returns buffered string: - expect (StrBuf.tostring (b)).to_be "foobar" - - - context as an object method: - - it writes a deprecation warning: - expect (deprecate_on ("tostring", "", "{'foo', 'bar'}")). - to_contain_error "was deprecated" - expect (deprecate_off ("tostring", "", "{'foo', 'bar'}")). - not_to_contain_error "was deprecated" - - - it returns buffered string: - expect (b:tostring ()).to_be "foobar" - - - context as a metamethod: - - it returns buffered string: - expect (tostring (b)).to_be "foobar" + - it returns buffered string: + expect (tostring (b)).to_be "foobar" - describe concat: diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 49300fd..8a8d387 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -9,12 +9,6 @@ before: "numbertosi", "ordinal_suffix", "pad", "pickle", "prettytostring", "render", "rtrim", "split", "tfind", "trim", "wrap" } - deprecations = {} - - setdebug { deprecate = false } - - deprecate_on = bind (deprecation, {"nil", this_module}) - deprecate_off = bind (deprecation, {false, this_module}) M = require (this_module) getmetatable ("").__concat = M.__concat @@ -33,12 +27,8 @@ specify std.string: expect (show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core string table: - apis = require "std.base".base.copy (extend_base) - for _, v in ipairs (deprecations) do - apis[#apis + 1] = v - end expect (show_apis {from=base_module, not_in=this_module}). - to_contain.a_permutation_of (apis) + to_contain.a_permutation_of (extend_base) - context via the std module: - it does not touch the global table: @@ -449,71 +439,6 @@ specify std.string: to_be '{\n["?"] = 1,\n["[]"] = 1,\n_ = 0,\n["a-key"] = 1,\nword = 0,\n}' -- describe render: - - before: - r = function (x) - return M.render (x, { - sort = function (keys) - table.sort (keys, - function (a, b) return tostring (a) < tostring (b) end) - return keys - end, - }) - end - t = {1, {{2, 3}, 4, {5}}} - - f = M.render - - - it writes an argument passing deprecation warning: - around = "function () return '|' end" - inside = "function () return '_' end" - between = "function () return ',' end" - argstr = table.concat ({around, around, inside, inside, between}, ",") - - expect (deprecate_on ("render", argstr)). - to_contain_error "was deprecated" - expect (deprecate_off ("render", argstr)). - not_to_contain_error "was deprecated" - - - context with bad arguments: - badargs.diagnose (f, "std.string.render (?any, ?table|func, ?func, ?func, ?func, ?func, ?table)") - - - it converts a primitive to a representative string: - expect (r (nil)).to_be "nil" - expect (r (false)).to_be "false" - expect (r (42)).to_be "42" - expect (r "string").to_be "string" - - it converts a sequence to a representative string: - expect (r {false, "table", 42}).to_be "{1=false,2=table,3=42}" - - it converts a nested sequence to a representative string: - expect (r (t)). - to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}" - - it converts a hash to a representative string: - expect (r {[false]=true, hash="table", answer=42}). - to_be "{answer=42,false=true,hash=table}" - - it converts a recursive table to a representative string: - t[1] = t - expect (r (t)). - to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") - - it supports the legacy api: - term = function (s) return function () return s end end - pair = function (_, _, _, i, v) return i .. "=" .. v end - sep = function (_, i, _, j) return i and j and "," or "" end - r = function (x) - return f (x, term "{", term "}", tostring, pair, sep) - end - expect (r (nil)).to_be "nil" - expect (r (false)).to_be "false" - expect (r (42)).to_be "42" - expect (r "string").to_be "string" - expect (r {false, "table", 42}).to_be "{1=false,2=table,3=42}" - expect (r (t)). - to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}" - t[1] = t - expect (r (t)). - to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}") - - - describe rtrim: - before: subject = " \t\r\n a short string \t\r\n " diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index b31b8e2..edb9535 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -8,12 +8,6 @@ before: | "merge", "merge_select", "monkey_patch", "new", "pack", "project", "remove", "size", "sort", "unpack", "values" } - deprecations = { "flatten", "len", "okeys", "shape" } - - setdebug { deprecate = false } - - deprecate_on = bind (deprecation, {"nil", this_module}) - deprecate_off = bind (deprecation, {false, this_module}) M = require (this_module) @@ -34,9 +28,6 @@ specify std.table: apis[#apis + 1] = v end end - for _, v in ipairs (deprecations) do - apis[#apis + 1] = v - end expect (show_apis {from=base_module, not_in=this_module}). to_contain.a_permutation_of (apis) @@ -176,26 +167,6 @@ specify std.table: expect (f (t)).to_equal (l) -- describe flatten: - - before: - t = {{{"one"}}, "two", {{"three"}, "four"}} - - f = M.flatten - - - it writes a deprecation warning: - expect (deprecate_on ("flatten", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("flatten", "{}")). - not_to_contain_error "was deprecated" - - - it returns a table: - expect (type (f (t))).to_be "table" - - it works for an empty table: - expect (f {}).to_equal {} - - it flattens a nested table: - expect (f (t)).to_equal {"one", "two", "three", "four"} - - - describe insert: - before: f, badarg = init (M, this_module, "insert") @@ -277,26 +248,6 @@ specify std.table: expect (f (subject)).not_to_equal (subject) -- describe len: - - before: - f = M.len - - - it writes a deprecation warning: - expect (deprecate_on ("len", "{}")).to_contain_error "was deprecated" - expect (deprecate_off ("len", "{}")).not_to_contain_error "was deprecated" - - - it returns the length 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 elements after a hole: - expect (f {1, 2, [5]=3}).to_be (2) - - it respects __len metamethod: - t = setmetatable ({1, 2, [5]=3}, {__len = function () return 42 end}) - expect (f (t)).to_be (42) - - - describe maxn: - before: f = M.maxn @@ -491,25 +442,6 @@ specify std.table: expect (t[1]).to_be (default) -- describe okeys: - - before: - subject = { "v1", k1 = 1, "v2", k2 = 2, "v3", k3 = 3, [10]="v10" } - - f = M.okeys - - - it writes a deprecation warning: - expect (deprecate_on ("okeys", "{}")). - to_contain_error "was deprecated" - expect (deprecate_off ("okeys", "{}")). - not_to_contain_error "was deprecated" - - - it returns an empty list when subject is empty: - expect (f {}).to_equal {} - - it makes an ordered list of table keys: - expect (f (subject)). - to_equal {1, 2, 3, 10, "k1", "k2", "k3"} - - - describe pack: - before: unpack = unpack or table.unpack @@ -589,39 +521,6 @@ specify std.table: expect (t).to_equal {1, 2, 5, 42} -- describe shape: - - before: - l = {1, 2, 3, 4, 5, 6} - - f = M.shape - - - it writes a deprecation warning: - expect (deprecate_on ("shape", "{}, {}")). - to_contain_error "was deprecated" - expect (deprecate_off ("shape", "{}, {}")). - not_to_contain_error "was deprecated" - - - it returns a table: - expect (objtype (f ({2, 3}, l))).to_be "table" - - it works for an empty table: - expect (f ({0}, {})).to_equal ({}) - - it returns the result in a new table: - expect (f ({2, 3}, l)).not_to_be (l) - - it does not perturb the argument table: - f ({2, 3}, l) - expect (l).to_equal {1, 2, 3, 4, 5, 6} - - it reshapes a table according to given dimensions: - expect (f ({2, 3}, l)). - to_equal ({{1, 2, 3}, {4, 5, 6}}) - expect (f ({3, 2}, l)). - to_equal ({{1, 2}, {3, 4}, {5, 6}}) - - it treats 0-valued dimensions as an indefinite number: - expect (f ({2, 0}, l)). - to_equal ({{1, 2, 3}, {4, 5, 6}}) - expect (f ({0, 2}, l)). - to_equal ({{1, 2}, {3, 4}, {5, 6}}) - - - describe size: - before: | -- - 1 - --------- 2 ---------- -- 3 -- From 252adca86c9cfe97755fe841d881a67391788337 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 30 Jan 2016 20:01:11 +0000 Subject: [PATCH 669/703] maturity: split out into its own module (at some point!) * lib/std/maturity.lua: Delete. * local.mk (dist_luastd_DATA): Remove lib/std/maturity.lua. * build-aux/config.ld.in (file): Likewise. * lib/std/object.lua, lib/std/string.lua: Remove unused import of removed maturity module. * specs/maturity_spec.yaml: Delete. * specs/specs.mk (specl_SPECS): Remove specs/maturity_spec.yaml. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 11 +--- build-aux/config.ld.in | 6 -- lib/std/maturity.lua | 124 --------------------------------------- lib/std/object.lua | 1 - lib/std/string.lua | 18 +----- local.mk | 8 +-- specs/maturity_spec.yaml | 105 --------------------------------- specs/specs.mk | 1 - 8 files changed, 6 insertions(+), 268 deletions(-) delete mode 100644 lib/std/maturity.lua delete mode 100644 specs/maturity_spec.yaml diff --git a/NEWS.md b/NEWS.md index 255a122..4a1b5a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -136,9 +136,6 @@ - Passing the result of `functional.lambda` to `tostring` returns the original lambda string. - - New `std.maturity` module now contains the `DEPRECATED` and - `DEPRECATIONMSG` functions previously found in `std.debug`. - - We used to have an object module method, `std.object.type`, which often got imported using: @@ -217,11 +214,9 @@ 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 moved to - a new module `std.maturity`. Deprecation DEPRECATED with multi-level - deprecation warnings was more confusing than simply moving the - functions into their own module, so there is no deprecation warning - to prompt you to update call-sites. + - `std.debug.DEPRECATED` and `std.debug.DEPRECATIONMSG` have been + removed. At some point these will resurface in a new standalone + package. - Deprecated multi-argument `functional.bind` has been removed. diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index 8fea8fc..c08d357 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -52,26 +52,20 @@ file = { -- Functional Style "../lib/std/functional.lua", "../lib/std/operator.lua", - - -- Other Modules - "../lib/std/maturity.lua", } new_type ("corefunction", "Core_Functions", true) new_type ("corelibrary", "Core_Libraries", true) -new_type ("coremodule", "Core_Modules", true) new_type ("prototype", "Object_System", true) new_type ("functional", "Functional_Style", true) function postprocess_html(s) s = s:gsub("

    %s*Corefunction (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Corelibrary (.-)

    ", '

    Module %1

    ') - s = s:gsub("

    %s*Coremodule (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Prototype (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Functional (.-)

    ", '

    Module %1

    ') s = s:gsub("

    Core_Functions

    ", '

    Core Functions

    ') s = s:gsub("

    Core_Libraries

    ", '

    Core Libraries

    ') - s = s:gsub("

    Core_Modules

    ", '

    Core Modules

    ') s = s:gsub("

    Object_System

    ", '

    Object System

    ') s = s:gsub("

    Functional_Style

    ", '

    Functional Style

    ') return s diff --git a/lib/std/maturity.lua b/lib/std/maturity.lua deleted file mode 100644 index 4baa8ef..0000000 --- a/lib/std/maturity.lua +++ /dev/null @@ -1,124 +0,0 @@ ---[[-- - API Maturity. - - Rather than suddenly changing or removing APIs between releases of a - library use these functions to support deprecated calls for a time - first, while issuing warnings to the caller. - - The verbosity of APIs deprecated with these functions is controlled by - the global `_DEBUG` variable, which must be set before any `stdlib` - modules are loaded. This declaration will disable deprecation, so - that deprecated APIs will behave normally: - - _DEBUG = { deprecate = false } - - Alternatively, without affecting the global environment, the following - style causes deprecated APIs to be undefined so that you can easily - check whether your code is still using deprecated calls: - - local init = require "std.debug_init" - init._DEBUG.deprecate = true - - Not setting `_DEBUG.deprecate` will warn on every call to deprecated - APIs. - - @coremodule std.maturity -]] - - -local _ENV = _ENV -local error = error -local pcall = pcall -local select = select -local tostring = tostring - -local io_stderr = io.stderr -local string_format = string.format -local table_unpack = table.unpack or unpack - - -local _ = { - debug_init = require "std.debug_init", -} - -local _DEBUG = _.debug_init._DEBUG - - -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end - -_ = nil - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - -local function DEPRECATIONMSG (version, name, extramsg, level) - if level == nil then level, extramsg = extramsg, nil end - extramsg = extramsg or "and will be removed entirely in a future release" - - local _, where = pcall (function () error ("", level + 3) end) - if _DEBUG.deprecate == nil then - return (where .. string_format ("%s was deprecated in release %s, %s.\n", - name, tostring (version), extramsg)) - end - - return "" -end - - -local function result_pack (...) - return {n = select ("#", ...), ...} -end - - -local function DEPRECATED (version, name, extramsg, fn) - if fn == nil then fn, extramsg = extramsg, nil end - - if not _DEBUG.deprecate then - return function (...) - io_stderr:write (DEPRECATIONMSG (version, name, extramsg, 2)) - - -- `return fn (...)` is subject to tail call elimination, which - -- would lose a stack frame and change the `level` argument - -- required for frame counting functions, so we do this instead: - local r = result_pack (fn (...)) - return table_unpack (r, 1, r.n) - end - end -end - - -return { - -- Provide a deprecated function definition according to _DEBUG.deprecate. - -- You can check whether your covered code uses deprecated functions by - -- setting `_DEBUG.deprecate` to `true` before loading any stdlib modules, - -- or silence deprecation warnings by setting `_DEBUG.deprecate = false`. - -- @function DEPRECATED - -- @string version first deprecation release version - -- @string name function name for automatic warning message - -- @string[opt] extramsg additional warning text - -- @func fn deprecated function - -- @return a function to show the warning on first call, and hand off to *fn* - -- @usage - -- M.op = DEPRECATED ("41", "'std.functional.op'", std.operator) - DEPRECATED = DEPRECATED, - - -- Format a deprecation warning message. - -- @function DEPRECATIONMSG - -- @string version first deprecation release version - -- @string name function name for automatic warning message - -- @string[opt] extramsg additional warning text - -- @int level call stack level to blame for the error - -- @treturn string deprecation warning message, or empty string - -- @usage - -- io.stderr:write (DEPRECATIONMSG ("42", "multi-argument 'module.fname'", 2)) - DEPRECATIONMSG = DEPRECATIONMSG, -} diff --git a/lib/std/object.lua b/lib/std/object.lua index 7b11740..6536f6f 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -29,7 +29,6 @@ local getmetatable = getmetatable local _ = { debug_init = require "std.debug_init", container = require "std.container", - maturity = require "std.maturity", std = require "std.base", } diff --git a/lib/std/string.lua b/lib/std/string.lua index fe69d41..e7194ae 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -29,7 +29,6 @@ local deprecated = nil local _ = { debug_init = require "std.debug_init", - maturity = require "std.maturity", std = require "std.base", strbuf = require "std.strbuf", } @@ -38,7 +37,6 @@ local StrBuf = _.strbuf.prototype local _DEBUG = _.debug_init._DEBUG local _tostring = _.std.tostring -local DEPRECATIONMSG = _.maturity.DEPRECATIONMSG local copy = _.std.base.copy local escape_pattern = _.std.string.escape_pattern local len = _.std.operator.len @@ -452,20 +450,8 @@ M = { -- end, -- }) -- end - render = X ("render (?any, ?table|func, ?func, ?func, ?func, ?func, ?table)", - function (x, opencb, closecb, elemcb, paircb, sepcb, roots) - if type (opencb) == "function" then - io_stderr:write (DEPRECATIONMSG ("41.3", - "multiple function arguments to 'std.string.render'", - "pass a table of named functions as the second parameter instead", 2)) - opencb = { - open = opencb, close = closecb, elem = elemcb, sep = sepcb, - pair = function (x, kp, vp, k, v, kstr, vstr) - return paircb (x, k, v, kstr, vstr) - end, - } - end - return render (x, opencb, roots) + render = X ("render (?any, ?table)", function (x, rendercbs, roots) + return render (x, rendercbs, roots) end ), diff --git a/local.mk b/local.mk index 1a9d9df..8c2e32f 100644 --- a/local.mk +++ b/local.mk @@ -72,7 +72,6 @@ dist_luastd_DATA = \ lib/std/init.lua \ lib/std/list.lua \ lib/std/math.lua \ - lib/std/maturity.lua \ lib/std/object.lua \ lib/std/operator.lua \ lib/std/package.lua \ @@ -125,7 +124,6 @@ EXTRA_DIST += \ doccorefunctions = $(srcdir)/doc/core_functions/std doccorelibraries = $(srcdir)/doc/core_libraries/std docfunctional = $(srcdir)/doc/functional_style/std -docmodules = $(srcdir)/doc/core_modules/std docobjects = $(srcdir)/doc/object_system/std dist_doc_DATA += \ @@ -150,10 +148,6 @@ dist_docfunctional_DATA += \ $(docfunctional).operator.html \ $(NOTHING_ELSE) -dist_docmodules_DATA += \ - $(docmodules).maturity.html \ - $(NOTHING_ELSE) - dist_docobjects_DATA += \ $(docobjects).container.html \ $(docobjects).list.html \ @@ -169,7 +163,7 @@ dist_docobjects_DATA += \ ## of the doc directory as a sentinel file. $(dist_doc_DATA) $(dist_doccorefunctions_DATA): $(srcdir)/doc $(dist_doccorelibraries_DATA) $(dist_docfunctional_DATA): $(srcdir)/doc -$(dist_docmodules_DATA) $(dist_docobjects_DATA): $(srcdir)/doc +$(dist_docobjects_DATA): $(srcdir)/doc $(srcdir)/doc: $(dist_lua_DATA) $(dist_luastd_DATA) test -d $@ || mkdir $@ diff --git a/specs/maturity_spec.yaml b/specs/maturity_spec.yaml deleted file mode 100644 index 0fc8a9a..0000000 --- a/specs/maturity_spec.yaml +++ /dev/null @@ -1,105 +0,0 @@ -before: - this_module = "std.maturity" - - M = require (this_module) - - -specify std.maturity: -- describe DEPRECATED: - - before: | - function runscript (body, name, args) - return luaproc ( - "require '" .. this_module .. "'.DEPRECATED ('0', '" .. - (name or "runscript") .. "', function (...)" .. - " " .. body .. - " end) " .. - "('" .. table.concat (args or {}, "', '") .. "')" - ) - end - - f, badarg = init (M, this_module, "DEPRECATED") - - - it returns a function: - expect (type (f ("0", "deprecated", nop))).to_be "function" - expect (f ("0", "deprecated", nop)).not_to_be (nop) - - context with deprecated function: - - it executes the deprecated function: - expect (runscript 'error "oh noes!"').to_contain_error "oh noes!" - - it passes arguments to the deprecated function: - expect (runscript ("print (table.concat ({...}, ', '))", nil, - {"foo", "bar", "baz"})).to_output "foo, bar, baz\n" - - it returns deprecated function results: | - script = [[ - DEPRECATED = require "]] .. this_module .. [[".DEPRECATED - fn = DEPRECATED ("0", "fn", function () return "foo", "bar", "baz" end) - print (fn ()) - ]] - expect (luaproc (script)).to_output "foo\tbar\tbaz\n" - - it writes a warning to stderr: - expect (runscript 'error "oh noes!"'). - to_match_error "deprecated.*, and will be removed" - - it writes the version string to stderr: - expect (runscript 'error "oh noes!"'). - to_contain_error "in release 0" - - it writes the call location to stderr: | - expect (runscript 'error "oh noes!"'). - to_match_error "^%S+:1: " - - context with _DEBUG: - - before: | - script = [[ - DEPRECATED = require "]] .. this_module .. [[".DEPRECATED - fn = DEPRECATED ("0", "fn", function () io.stderr:write "oh noes!\n" end) - fn () -- line 3 - fn () -- line 4 - ]] - - it warns every call by default: - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" - - it does not warn at all with _DEBUG set to false: - script = "_DEBUG = false " .. script - expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - - it does not define the function with _DEBUG set to true: | - script = "_DEBUG = true " .. script - expect (luaproc (script)).to_contain_error.any_of { - ":3: attempt to call global 'fn'", - ":3: attempt to call a nil value (global 'fn')", - } - - it warns on every call with _DEBUG.deprecate unset: - script = "_DEBUG = {} " .. script - expect (luaproc (script)).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (script)).to_match_error "\n%S+:4:.*deprecated" - - it does not warn at all with _DEBUG.deprecate set to false: - script = "_DEBUG = { deprecate = false } " .. script - expect (luaproc (script)).not_to_match_error "%d:.*deprecated" - - it warns on every call with _DEBUG.deprecate set to true: | - script = "_DEBUG = { deprecate = true } " .. script - expect (luaproc (script)).to_contain_error.any_of { - ":3: attempt to call global 'fn'", - ":3: attempt to call a nil value (global 'fn')", - } - - -- describe DEPRECATIONMSG: - - before: | - function mkscript (lvl) - return [[ - DEPRECATIONMSG = require "]] .. this_module .. [[".DEPRECATIONMSG - function fn () - io.stderr:write (DEPRECATIONMSG ("42", "fname", "extra", ]] .. lvl .. [[)) - end - fn () -- line 5 - fn () -- line 6 - ]] - end - - f = M.DEPRECATIONMSG - - - it contains deprecating the release version: - expect (luaproc (mkscript (2))).to_match_error "42" - - it contains the deprecation function name: - expect (luaproc (mkscript (2))).to_match_error "fname" - - it appends an optional extra message: - expect (luaproc (mkscript (2))).to_match_error ", extra." - - it blames the given stack level: - expect (luaproc (mkscript (1))).to_match_error "^%S+:3:.*deprecated" - expect (luaproc (mkscript (2))).to_match_error "^%S+:5:.*deprecated" diff --git a/specs/specs.mk b/specs/specs.mk index b84aaeb..7cdb7b7 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -17,7 +17,6 @@ specl_SPECS = \ $(srcdir)/specs/io_spec.yaml \ $(srcdir)/specs/list_spec.yaml \ $(srcdir)/specs/math_spec.yaml \ - $(srcdir)/specs/maturity_spec.yaml \ $(srcdir)/specs/object_spec.yaml \ $(srcdir)/specs/operator_spec.yaml \ $(srcdir)/specs/package_spec.yaml \ From 76d7b8e153da5880ff26709066459298b63ad4a3 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 12:34:40 +0000 Subject: [PATCH 670/703] functional: split out into its own module. * lib/std/functional.lua, lib/std/operator.lua, lib/std/tuple.lua: Delete. * local.mk (dist_luastd_DATA): Adjust accordingly. * build-aux/config.ld.in (file): Likewise. * lib/std/init.lua (barrel): Remove functional items. * lib/std/base.lua (mnemonic_vtable, mnemonic): Remove unused code. (callable, reduce): Move from here... * lib/std/tree.lua (callable, reduce): ...to here. * lib/std/base.lua (toqstring): Move from here... * lib/std/string.lua (toqstring): ...to here. * lib/std/table.lua (collect): Remove unused import. * specs/functional_spec.yaml, specs/operator_spec.yaml, specs/tuple_spec.yaml: Delete. * specs/debug_spec.yaml: Add a map implementation for examples. * specs/std_spec.yaml: Remove functional references. * specs/specs.mk (specl_SPECS): Adjust accordingly. * rockspec.conf: Remove functional references. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 90 +--- build-aux/config.ld.in | 13 +- lib/std/base.lua | 50 --- lib/std/functional.lua | 830 ------------------------------------- lib/std/init.lua | 5 - lib/std/operator.lua | 216 ---------- lib/std/string.lua | 14 +- lib/std/table.lua | 1 - lib/std/tree.lua | 48 ++- lib/std/tuple.lua | 199 --------- local.mk | 15 +- rockspec.conf | 4 +- specs/debug_spec.yaml | 37 +- specs/functional_spec.yaml | 748 --------------------------------- specs/operator_spec.yaml | 305 -------------- specs/specs.mk | 3 - specs/std_spec.yaml | 9 - specs/tuple_spec.yaml | 187 --------- 18 files changed, 94 insertions(+), 2680 deletions(-) delete mode 100644 lib/std/functional.lua delete mode 100644 lib/std/operator.lua delete mode 100644 lib/std/tuple.lua delete mode 100644 specs/functional_spec.yaml delete mode 100644 specs/operator_spec.yaml delete mode 100644 specs/tuple_spec.yaml diff --git a/NEWS.md b/NEWS.md index 4a1b5a3..a554741 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,10 +11,10 @@ are always welcome! - With this release, stdlib is much more focused, and non-core modules - `std.optparse` and `std.strict` have been moved into their own packages + `std.functional`, `std.maturity`, `std.operator`, `std.optparse`, + `std.strict` and `std.tuple` have been moved into their own packages and release cycle. You can still install them separately from their - [own][optparse] [projects][strict] - or using Luarocks: + own projects or using Luarocks: ```bash luarocks install optparse @@ -81,61 +81,8 @@ - All of stdlib's object prototypes now provide a `__pickle` metamethod, which makes them picklable with `std.string.pickle` too! - - `std.functional.memoize` uses a fast stable render based serialization - call by default now, when the `mnemonic` parameter is not given. - - - `std.operator.eqv` now uses render to determine equivalence between - tables, which means it works correctly for table keys too. - - - New `std.tuple` object, for managing interned immutable nil-preserving - tuples: - - ```lua - local Tuple = require "std.tuple" {} - local t3 = Tuple (nil, false, nil) - local t3_ = Tuple (nil, false, nil) - assert (t3 == t3_) - - local len = require "std.operator".len - assert (len (t3) == 3) - - local t = {} - for i = 1, len (t3) do t = t3[i] end - assert (len (t) == 3) - - local a, b, c = require "std.table".unpack (t3) - assert (a == nil and b == false and c == nil) - ``` - - - New `functional.any` returns a function that calls each of the - passed functions with the same arguments, stopping and returning the - result from the first of those calls that does not equal `nil`. - - - New `functional.product` returns a list of combinations made by - taking one element from each of the argument lists. See LDocs for - an example. - - - New `operator.eqv` is similar to `operator.eq`, except that it succeeds - when recursive table contents are equivalent. - - - New `operator.len` replaces `table.len`. `operator.len` is always - deterministic; counting only numerically indexed elements immediately up - to the first `nil` valued element (PUC-Rio Lua does not guarantee this - with its `#` operator): - - ```lua - local t1 = {1, 2, [5]=3} - local t2 = {1, 2, nil, nil, 3} - print (eqv(t1, t2)) --> true - print (len(t1) == len(t2)) --> true - print (#t1 == #t2) --> LuaJIT: true, PUC-Rio: false - ``` - - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. - - Passing the result of `functional.lambda` to `tostring` returns the - original lambda string. - - We used to have an object module method, `std.object.type`, which often got imported using: @@ -150,19 +97,6 @@ orthogonality with core Lua, we're going back to using `std.object.type`, because that just makes more sense. Sorry! - - `std.ireverse` has been replaced by `std.functional.ireverse` because - of the functional style of a non-destructive sequence reversing - operation. - - - `std.table.flatten` and `std.table.shape` have been replaced by - favour of `std.functional.flatten` and `std.functional.shape` - because these functions are far more useful in conjunction with a - functional programming style than with regular tables in imperative - code. - - - `std.table.len` has moved to `std.operator.len`, because it is not just - for tables! - - `std.table.okeys` has been removed for lack of utility. If you still need it, use this instead: @@ -184,18 +118,6 @@ correctly, rather than `nil` as in previous releases. It's also considerably faster now that it doesn't use `pcall` any more. - - `std.functional.any`, `std.functional.bind` and - `std.functional.compose` return functions that propagate trailing - `nil` arguments correctly. - - - `std.functional.memoize` now considers trailing nil arguments when - looking up a memoized value for those particular arguments, and - propagates `nil` return values from `mnemonic` functions correctly. - - - `std.functional.filter`, `std.functional.map` and - `std.functional.reduce` now pass trailing nil arguments to their - iterator function correctly. - - You can now derive other types from `std.set` by passing a `_type` field in the init argument, just like the other table argument objects. @@ -205,7 +127,8 @@ ### Incompatible changes - - `std.optparse` and `std.strict` have been moved to their own packages, + - `std.functional`, `std.maturity`, `std.operator`, `std.optparse`, + `std.strict` and `std.tuple` have been moved to their own packages, and are no longer shipped as part of stdlib. - `std.debug.argerror`, `std.debug.argcheck`, `std.debug.argscheck`, @@ -260,9 +183,6 @@ into the module `prototype` field, and add the module functions to the parent table returned when the module is required. - - `functional.lambda` no longer returns a bare function, but a functable - that can be called and stringified. - - 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`. diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index c08d357..8c76ef2 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -15,10 +15,7 @@ LuaJIT), 5.2 and 5.3 written in pure Lua, comprising: 3. A straight forward prototype-based object system, and a selection of useful objects built on it: @{std.container}, @{std.object}, - @{std.list}, @{std.set}, @{std.strbuf}, @{std.tree} and @{std.tuple}; - -4. A foundation for programming in a functional style: @{std.functional} - and @{std.operator}; + @{std.list}, @{std.set}, @{std.strbuf} and @{std.tree}. ## LICENSE @@ -47,27 +44,19 @@ file = { "../lib/std/set.lua", "../lib/std/strbuf.lua", "../lib/std/tree.lua", - "../lib/std/tuple.lua", - - -- Functional Style - "../lib/std/functional.lua", - "../lib/std/operator.lua", } new_type ("corefunction", "Core_Functions", true) new_type ("corelibrary", "Core_Libraries", true) new_type ("prototype", "Object_System", true) -new_type ("functional", "Functional_Style", true) function postprocess_html(s) s = s:gsub("

    %s*Corefunction (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Corelibrary (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Prototype (.-)

    ", '

    Module %1

    ') - s = s:gsub("

    %s*Functional (.-)

    ", '

    Module %1

    ') s = s:gsub("

    Core_Functions

    ", '

    Core Functions

    ') s = s:gsub("

    Core_Libraries

    ", '

    Core Libraries

    ') s = s:gsub("

    Object_System

    ", '

    Object System

    ') - s = s:gsub("

    Functional_Style

    ", '

    Functional Style

    ') return s end diff --git a/lib/std/base.lua b/lib/std/base.lua index cc4537c..62c3edd 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -319,27 +319,6 @@ local pack = table_pack or function (...) end -local function reduce (fn, d, ifn, ...) - local argt - if not callable (ifn) then - ifn, argt = pairs, pack (ifn, ...) - else - argt = pack (...) - end - - local nextfn, state, k = ifn (table_unpack (argt, 1, argt.n)) - local t = pack (nextfn (state, k)) -- table of iteration 1 - - local r = d -- initialise accumulator - while t[1] ~= nil do -- until iterator returns nil - k = t[1] - r = fn (r, table_unpack (t, 1, t.n)) -- pass all iterator results to fn - t = pack (nextfn (state, k)) -- maintain loop invariant - end - return r -end - - local fallbacks = { __index = { open = function (x) return "{" end, @@ -402,34 +381,12 @@ local function render (x, fns, roots) end -local function toqstring (x) - if type (x) ~= "string" then return tostring (x) end - return string_format ("%q", x) -end - - local function sortkeys (t) table_sort (t, keysort) return t end -local mnemonic_vtable = { - elem = toqstring, - sort = sortkeys, -} - - -local function mnemonic (...) - local seq, n = {...}, select ("#", ...) - local buf = {} - for i = 1, n do - buf[i] = render (seq[i], mnemonic_vtable) - end - return table_concat (buf, ",") -end - - local picklable = { boolean = true, ["nil"] = true, number = true, string = true, } @@ -613,7 +570,6 @@ return { copy = copy, last = last, merge = merge, - mnemonic = mnemonic, sortkeys = sortkeys, toqstring = toqstring, }, @@ -624,12 +580,6 @@ return { setfenv = _setfenv, }, - functional = { - callable = callable, - nop = function () end, - reduce = reduce, - }, - io = { catfile = catfile, }, diff --git a/lib/std/functional.lua b/lib/std/functional.lua deleted file mode 100644 index 4d4141c..0000000 --- a/lib/std/functional.lua +++ /dev/null @@ -1,830 +0,0 @@ ---[[-- - Functional programming. - - A selection of higher-order functions to enable a functional style of - programming in Lua. - - @functional std.functional -]] - - -local _ENV = _ENV -local loadstring = loadstring or load -local next = next -local pcall = pcall -local select = select -local setmetatable = setmetatable -local type = type - -local math_ceil = math.ceil -local table_remove = table.remove -local table_unpack = table.unpack or unpack - - -local _ = { - debug_init = require "std.debug_init", - std = require "std.base", -} - -local _DEBUG = _.debug_init._DEBUG -local _ipairs = _.std.ipairs -local _pairs = _.std.pairs -local callable = _.std.functional.callable -local ielems = _.std.ielems -local len = _.std.operator.len -local mnemonic = _.std.base.mnemonic -local nop = _.std.functional.nop -local pack = _.std.table.pack -local reduce = _.std.functional.reduce -local leaves = _.std.tree.leaves - - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end - - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end - -_ = nil - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - -local function any (...) - local fns = pack (...) - - return function (...) - local argt = {n = 0} - for i = 1, fns.n do - argt = pack (fns[i] (...)) - if argt[1] ~= nil then - return table_unpack (argt, 1, argt.n) - end - end - return table_unpack (argt, 1, argt.n) - end -end - - -local function bind (fn, bound) - return function (...) - local argt, unbound = {}, pack (...) - - -- Inline `argt = copy (bound)`... - local n = bound.n or 0 - for k, v in _pairs (bound) do - -- ...but only copy integer keys. - if type (k) == "number" and math_ceil (k) == k then - argt[k] = v - n = k > n and k or n -- Inline `n = maxn (unbound)` in same pass. - end - end - - -- Bind *unbound* parameters sequentially into *argt* gaps. - local i = 1 - for j = 1, unbound.n do - while argt[i] ~= nil do i = i + 1 end - argt[i], i = unbound[j], i + 1 - end - - -- Even if there are gaps remaining above *i*, pass at least *n* args. - if n >= i then return fn (table_unpack (argt, 1, n)) end - - -- Otherwise, we filled gaps beyond *n*, and pass that many args. - return fn (table_unpack (argt, 1, i - 1)) - end -end - - -local function case (with, branches) - local match = branches[with] or branches[1] - if callable (match) then - return match (with) - end - return match -end - - -local function collect (ifn, ...) - local r = {} - if not callable (ifn) then - -- No iterator behaves like npairs, which would collect all integer - -- key values from the first argument table: - local k, v = next (ifn) - repeat - if type (k) == "number" then r[k] = v end - k, v = next (ifn, k) - until k == nil - return r - end - - -- Or else result depends on how many return values from ifn? - local arity = 1 - for e, v in ifn (...) do - if v then arity, r = 2, {} break end - -- Build an arity-1 result table on first pass... - r[#r + 1] = e - end - - if arity == 2 then - -- ...oops, it was arity-2 all along, start again! - for k, v in ifn (...) do - r[k] = v - end - end - - return r -end - - -local function compose (...) - local fns = pack (...) - - return function (...) - local argt = pack (...) - for i = 1, fns.n do - argt = pack (fns[i] (table_unpack (argt, 1, argt.n))) - end - return table_unpack (argt, 1, argt.n) - end -end - - -local function cond (expr, branch, ...) - if branch == nil and select ("#", ...) == 0 then - expr, branch = true, expr - end - if expr then - if callable (branch) then - return branch (expr) - end - return branch - end - return cond (...) -end - - -local function curry (fn, n) - if n <= 1 then - return fn - else - return function (x) - return curry (bind (fn, {x}), n - 1) - end - end -end - - -local function filter (pfn, ifn, ...) - local argt, r = pack (...), {} - if not callable (ifn) then - ifn, argt = _pairs, pack (ifn, ...) - end - - local nextfn, state, k = ifn (table_unpack (argt, 1, argt.n)) - - local t = pack (nextfn (state, k)) -- table of iteration 1 - local arity = #t -- How many return values from ifn? - - if arity == 1 then - local v = t[1] - while v ~= nil do -- until iterator returns nil - if pfn (table_unpack (t, 1, t.n)) then -- pass all iterator results to p - r[#r + 1] = v - end - - t = pack (nextfn (state, v)) -- maintain loop invariant - v = t[1] - - if #t > 1 then -- unless we discover arity is not 1 after all - arity, r = #t, {} break - end - end - end - - if arity > 1 then - -- No need to start over here, because either: - -- (i) arity was never 1, and the original value of t is correct - -- (ii) arity used to be 1, but we only consumed nil values, so the - -- current t with arity > 1 is the correct next value to use - while t[1] ~= nil do - local k = t[1] - if pfn (table_unpack (t, 1, t.n)) then r[k] = t[2] end - t = pack (nextfn (state, k)) - end - end - - return r -end - - -local function flatten (t) - return collect (leaves, _ipairs, t) -end - - -local function foldl (fn, d, t) - if t == nil then - local tail = {} - for i = 2, len (d) do tail[#tail + 1] = d[i] end - d, t = d[1], tail - end - return reduce (fn, d, ielems, t) -end - - --- Be careful to reverse only the valid sequence part of a table. -local function ireverse (t) - local oob = 1 - while t[oob] ~= nil do - oob = oob + 1 - end - - local r = {} - for i = 1, oob - 1 do r[oob - i] = t[i] end - return r -end - - -local function foldr (fn, d, t) - if t == nil then - local u, last = {}, len (d) - for i = 1, last - 1 do u[#u + 1] = d[i] end - d, t = d[last], u - end - return reduce (function (x, y) return fn (y, x) end, d, ielems, ireverse (t)) -end - - -local function id (...) - return ... -end - - -local serialize = mnemonic - -local function memoize (fn, mnemonic) - mnemonic = mnemonic or serialize - - return setmetatable ({}, { - __call = function (self, ...) - local k = mnemonic (...) - local t = self[k] - if t == nil then - t = pack (fn (...)) - self[k] = t - end - return table_unpack (t, 1, t.n) - end - }) -end - - -local lambda = memoize (function (s) - local expr - - -- Support "|args|expression" format. - local args, body = s:match "^%s*|%s*([^|]*)|%s*(.+)%s*$" - if args and body then - expr = "return function (" .. args .. ") return " .. body .. " end" - end - - -- Support "expression" format. - if not expr then - body = s:match "^%s*(_.*)%s*$" or s:match "^=%s*(.+)%s*$" - if body then - expr = [[ - return function (...) - local _1,_2,_3,_4,_5,_6,_7,_8,_9 = ... - local _ = _1 - return ]] .. body .. [[ - end - ]] - end - end - - local ok, fn - if expr then - ok, fn = pcall (loadstring (expr)) - end - - -- Diagnose invalid input. - if not ok then - return nil, "invalid lambda string '" .. s .. "'" - end - - return setmetatable ({}, { - __call = function (self, ...) return fn (...) end, - __tostring = function (self) return s end, - }) -end, id) - - -local function map (mapfn, ifn, ...) - local argt, r = pack (...), {} - if not callable (ifn) or argt.n == 0 then - ifn, argt = _pairs, pack (ifn, ...) - end - - local nextfn, state, k = ifn (table_unpack (argt, 1, argt.n)) - local mapargs = pack (nextfn (state, k)) - - local arity = 1 - while mapargs[1] ~= nil do - local d, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) - if v ~= nil then - arity, r = 2, {} break - end - r[#r + 1] = d - mapargs = {nextfn (state, mapargs[1])} - end - - if arity > 1 then - -- No need to start over here, because either: - -- (i) arity was never 1, and the original value of mapargs is correct - -- (ii) arity used to be 1, but we only consumed nil values, so the - -- current mapargs with arity > 1 is the correct next value to use - while mapargs[1] ~= nil do - local k, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) - r[k] = v - mapargs = pack (nextfn (state, mapargs[1])) - end - end - return r -end - - -local function map_with (mapfn, tt) - local r = {} - for k, v in _pairs (tt) do - r[k] = mapfn (table_unpack (v, 1, len (v))) - end - return r -end - - -local function _product (x, l) - local r = {} - for v1 in ielems (x) do - for v2 in ielems (l) do - r[#r + 1] = {v1, table_unpack (v2, 1, len (v2))} - end - end - return r -end - -local function product (...) - local argt = {...} - if not next (argt) then - return argt - else - -- Accumulate a list of products, starting by popping the last - -- argument and making each member a one element list. - local d = map (lambda '={_1}', ielems, table_remove (argt)) - -- Right associatively fold in remaining argt members. - return foldr (_product, d, argt) - end -end - - -local function shape (dims, t) - t = flatten (t) - -- Check the shape and calculate the size of the zero, if any - local size = 1 - local zero - for i, v in _ipairs (dims) do - if v == 0 then - if zero then -- bad shape: two zeros - return nil - else - zero = i - end - else - size = size * v - end - end - if zero then - dims[zero] = math_ceil (len (t) / size) - end - local function fill (i, d) - if d > len (dims) then - return t[i], i + 1 - else - local r = {} - for j = 1, dims[d] do - local e - e, i = fill (i, d + 1) - r[#r + 1] = e - end - return r, i - end - end - return (fill (1, 1)) -end - - -local function zip (tt) - local r = {} - for outerk, inner in _pairs (tt) do - for k, v in _pairs (inner) do - r[k] = r[k] or {} - r[k][outerk] = v - end - end - return r -end - - -local function zip_with (fn, tt) - return map_with (fn, zip (tt)) -end - - - ---[[ ================= ]]-- ---[[ Public Interface. ]]-- ---[[ ================= ]]-- - - -local function X (decl, fn) - return argscheck ("std.functional." .. decl, fn) -end - -local M = { - --- Call a series of functions until one returns non-nil. - -- @function any - -- @func ... functions to call - -- @treturn function to call fn1 .. fnN until one returns non-nil. - -- @usage - -- old_object_type = any (std.object.type, io.type, type) - any = X ("any (func...)", any), - - --- Partially apply a function. - -- @function bind - -- @func fn function to apply partially - -- @tparam table argt table of *fn* arguments to bind - -- @return function with *argt* arguments already bound - -- @usage - -- cube = bind (std.operator.pow, {[2] = 3}) - bind = X ("bind (func, table)", bind), - - --- Identify callable types. - -- @function callable - -- @param x an object or primitive - -- @return `true` if *x* can be called, otherwise `false` - -- @usage - -- if callable (functable) then functable (args) end - callable = X ("callable (?any)", callable), - - --- A rudimentary case statement. - -- Match *with* against keys in *branches* table. - -- @function case - -- @param with expression to match - -- @tparam table branches map possible matches to functions - -- @return the value associated with a matching key, or the first non-key - -- value if no key matches. Function or functable valued matches are - -- called using *with* as the sole argument, and the result of that call - -- returned; otherwise the matching value associated with the matching - -- key is returned directly; or else `nil` if there is no match and no - -- default. - -- @see cond - -- @usage - -- return case (type (object), { - -- table = "table", - -- string = function () return "string" end, - -- function (s) error ("unhandled type: " .. s) end, - -- }) - case = X ("case (?any, #table)", case), - - --- Collect the results of an iterator. - -- @function collect - -- @func[opt=std.npairs] ifn iterator function - -- @param ... *ifn* arguments - -- @treturn table of results from running *ifn* on *args* - -- @see filter - -- @see map - -- @usage - -- --> {"a", "b", "c"} - -- collect {"a", "b", "c", x=1, y=2, z=5} - collect = X ("collect ([func], ?any...)", collect), - - --- Compose functions. - -- @function compose - -- @func ... functions to compose - -- @treturn function composition of fnN .. fn1: note that this is the - -- reverse of what you might expect, but means that code like: - -- - -- functional.compose (function (x) return f (x) end, - -- function (x) return g (x) end)) - -- - -- can be read from top to bottom. - -- @usage - -- vpairs = compose (table.invert, ipairs) - -- for v, i in vpairs {"a", "b", "c"} do process (v, i) end - compose = X ("compose (func...)", compose), - - --- A rudimentary condition-case statement. - -- If *expr* is "truthy" return *branch* if given, otherwise *expr* - -- itself. If the return value is a function or functable, then call it - -- with *expr* as the sole argument and return the result; otherwise - -- return it explicitly. If *expr* is "falsey", then recurse with the - -- first two arguments stripped. - -- @function cond - -- @param expr a Lua expression - -- @param branch a function, functable or value to use if *expr* is - -- "truthy" - -- @param ... additional arguments to retry if *expr* is "falsey" - -- @see case - -- @usage - -- -- recursively calculate the nth triangular number - -- function triangle (n) - -- return cond ( - -- n <= 0, 0, - -- n == 1, 1, - -- function () return n + triangle (n - 1) end) - -- end - cond = cond, -- any number of any type arguments! - - --- Curry a function. - -- @function curry - -- @func fn function to curry - -- @int n number of arguments - -- @treturn function curried version of *fn* - -- @usage - -- add = curry (function (x, y) return x + y end, 2) - -- incr, decr = add (1), add (-1) - curry = X ("curry (func, int)", curry), - - --- Filter an iterator with a predicate. - -- @function filter - -- @tparam predicate pfn predicate function - -- @func[opt=std.pairs] ifn iterator function - -- @param ... iterator arguments - -- @treturn table elements e for which `pfn (e)` is not "falsey". - -- @see collect - -- @see map - -- @usage - -- --> {2, 4} - -- filter (lambda '|e|e%2==0', std.elems, {1, 2, 3, 4}) - filter = X ("filter (func, [func], ?any...)", filter), - - --- Flatten a nested table into a list. - -- @function flatten - -- @tparam table t a table - -- @treturn table a list of all non-table elements of *t* - -- @usage - -- --> {1, 2, 3, 4, 5} - -- flatten {{1, {{2}, 3}, 4}, 5} - flatten = X ("flatten (table)", flatten), - - --- Fold a binary function left associatively. - -- If parameter *d* is omitted, the first element of *t* is used, - -- and *t* treated as if it had been passed without that element. - -- @function foldl - -- @func fn binary function - -- @param[opt=t[1]] d initial left-most argument - -- @tparam table t a table - -- @return result - -- @see foldr - -- @see reduce - -- @usage - -- foldl (std.operator.quot, {10000, 100, 10}) == (10000 / 100) / 10 - foldl = X ("foldl (function, [any], table)", foldl), - - --- Fold a binary function right associatively. - -- If parameter *d* is omitted, the last element of *t* is used, - -- and *t* treated as if it had been passed without that element. - -- @function foldr - -- @func fn binary function - -- @param[opt=t[1]] d initial right-most argument - -- @tparam table t a table - -- @return result - -- @see foldl - -- @see reduce - -- @usage - -- foldr (std.operator.quot, {10000, 100, 10}) == 10000 / (100 / 10) - foldr = X ("foldr (function, [any], table)", foldr), - - --- Identity function. - -- @function id - -- @param ... arguments - -- @return *arguments* - id = id, -- any number of any type arguments! - - --- Return a new sequence with element order reversed. - -- - -- Apart from the order of the elements returned, this function follows - -- the same rules as @{ipairs} for determining first and last elements. - -- @function ireverse - -- @tparam table t a table - -- @treturn table a new table with integer keyed elements in reverse - -- order with respect to *t* - -- @see std.ielems - -- @see ipairs - -- @usage - -- local rielems = std.functional.compose (std.ireverse, std.ielems) - -- --> bar - -- --> foo - -- std.functional.map (print, rielems, {"foo", "bar", [4]="baz", d=5}) - ireverse = X ("ireverse (table)", ireverse), - - --- Compile a lambda string into a Lua function. - -- - -- A valid lambda string takes one of the following forms: - -- - -- 1. `'=expression'`: equivalent to `function (...) return expression end` - -- 1. `'|args|expression'`: equivalent to `function (args) return expression end` - -- - -- The first form (starting with `'='`) automatically assigns the first - -- nine arguments to parameters `'_1'` through `'_9'` for use within the - -- expression body. The parameter `'_1'` is aliased to `'_'`, and if the - -- first non-whitespace of the whole expression is `'_'`, then the - -- leading `'='` can be omitted. - -- - -- The results are memoized, so recompiling a previously compiled - -- lambda string is extremely fast. - -- @function lambda - -- @string s a lambda string - -- @treturn functable compiled lambda string, can be called like a function - -- @usage - -- -- The following are equivalent: - -- lambda '= _1 < _2' - -- lambda '|a,b| a {1, 4, 9, 16} - -- map (lambda '=_1*_1', std.ielems, {1, 2, 3, 4}) - map = X ("map (func, [func], ?any...)", map), - - --- Map a function over a table of argument lists. - -- @function map_with - -- @func fn map function - -- @tparam table tt a table of *fn* argument lists - -- @treturn table new table of *fn* results - -- @see map - -- @see zip_with - -- @usage - -- --> {"123", "45"}, {a="123", b="45"} - -- conc = bind (map_with, {lambda '|...|table.concat {...}'}) - -- conc {{1, 2, 3}, {4, 5}}, conc {a={1, 2, 3, x="y"}, b={4, 5, z=6}} - map_with = X ("map_with (function, table of tables)", map_with), - - --- Memoize a function, by wrapping it in a functable. - -- - -- To ensure that memoize always returns the same results for the same - -- arguments, it passes arguments to *fn*. You can specify a more - -- sophisticated function if memoize should handle complicated argument - -- equivalencies. - -- @function memoize - -- @func fn pure function: a function with no side effects - -- @tparam[opt=std.tostring] mnemonic mnemonicfn how to remember the arguments - -- @treturn functable memoized function - -- @usage - -- local fast = memoize (function (...) --[[ slow code ]] end) - memoize = X ("memoize (func, ?func)", memoize), - - --- No operation. - -- This function ignores all arguments, and returns no values. - -- @function nop - -- @see id - -- @usage - -- if unsupported then vtable["memrmem"] = nop end - nop = nop, -- ignores all arguments - - --- Functional list product. - -- - -- Return a list of each combination possible by taking a single - -- element from each of the argument lists. - -- @function product - -- @param ... operands - -- @return result - -- @usage - -- --> {"000", "001", "010", "011", "100", "101", "110", "111"} - -- map (table.concat, ielems, product ({0,1}, {0, 1}, {0, 1})) - product = X ("product (list...)", product), - - --- Fold a binary function into an iterator. - -- @function reduce - -- @func fn reduce function - -- @param d initial first argument - -- @func[opt=std.pairs] ifn iterator function - -- @param ... iterator arguments - -- @return result - -- @see foldl - -- @see foldr - -- @usage - -- --> 2 ^ 3 ^ 4 ==> 4096 - -- reduce (std.operator.pow, 2, std.ielems, {3, 4}) - reduce = X ("reduce (func, any, [func], ?any...)", reduce), - - --- Shape a table according to a list of dimensions. - -- - -- Dimensions are given outermost first and items from the original - -- list are distributed breadth first; there may be one 0 indicating - -- an indefinite number. Hence, `{0}` is a flat list, - -- `{1}` is a singleton, `{2, 0}` is a list of - -- two lists, and `{0, 2}` is a list of pairs. - -- - -- Algorithm: turn shape into all positive numbers, calculating - -- the zero if necessary and making sure there is at most one; - -- recursively walk the shape, adding empty tables until the bottom - -- level is reached at which point add table items instead, using a - -- counter to walk the flattened original list. - -- - -- @todo Use ileaves instead of flatten (needs a while instead of a - -- for in fill function) - -- @function shape - -- @tparam table dims table of dimensions `{d1, ..., dn}` - -- @tparam table t a table of elements - -- @return reshaped list - -- @usage - -- --> {{"a", "b"}, {"c", "d"}, {"e", "f"}} - -- shape ({3, 2}, {"a", "b", "c", "d", "e", "f"}) - shape = X ("shape (table, table)", shape), - - --- Zip a table of tables. - -- Make a new table, with lists of elements at the same index in the - -- original table. This function is effectively its own inverse. - -- @function zip - -- @tparam table tt a table of tables - -- @treturn table new table with lists of elements of the same key - -- from *tt* - -- @see map - -- @see zip_with - -- @usage - -- --> {{1, 3, 5}, {2, 4}}, {a={x=1, y=3, z=5}, b={x=2, y=4}} - -- zip {{1, 2}, {3, 4}, {5}}, zip {x={a=1, b=2}, y={a=3, b=4}, z={a=5}} - zip = X ("zip (table of tables)", zip), - - --- Zip a list of tables together with a function. - -- @function zip_with - -- @tparam function fn function - -- @tparam table tt table of tables - -- @treturn table a new table of results from calls to *fn* with arguments - -- made from all elements the same key in the original tables; effectively - -- the "columns" in a simple list - -- of lists. - -- @see map_with - -- @see zip - -- @usage - -- --> {"135", "24"}, {a="1", b="25"} - -- conc = bind (zip_with, {lambda '|...|table.concat {...}'}) - -- conc {{1, 2}, {3, 4}, {5}}, conc {{a=1, b=2}, x={a=3, b=4}, {b=5}} - zip_with = X ("zip_with (function, table of tables)", zip_with), -} - - -return M - - - ---- Types --- @section Types - - ---- Signature of a @{memoize} argument normalization callback function. --- @function mnemonic --- @param ... arguments --- @treturn string stable serialized arguments --- @usage --- local mnemonic = function (name, value, props) return name end --- local intern = std.functional.memoize (mksymbol, mnemonic) - - ---- Signature of a @{filter} predicate callback function. --- @function predicate --- @param ... arguments --- @treturn boolean "truthy" if the predicate condition succeeds, --- "falsey" otherwise --- @usage --- local predicate = lambda '|k,v|type(v)=="string"' --- local strvalues = filter (predicate, std.pairs, {name="Roberto", id=12345}) diff --git a/lib/std/init.lua b/lib/std/init.lua index af6b3d7..06f875c 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -107,10 +107,6 @@ local function barrel (namespace) -- Older releases installed the following into _G by default. for _, name in pairs { - "functional.bind", "functional.collect", "functional.compose", - "functional.curry", "functional.filter", "functional.id", - "functional.map", - "io.die", "io.warn", "string.pickle", "string.prettytostring", "string.render", @@ -124,7 +120,6 @@ local function barrel (namespace) end -- Support old api names, for backwards compatibility. - namespace.fold = M.functional.fold namespace.metamethod = M.getmetamethod namespace.op = M.operator namespace.require_version = M.require diff --git a/lib/std/operator.lua b/lib/std/operator.lua deleted file mode 100644 index fb4679d..0000000 --- a/lib/std/operator.lua +++ /dev/null @@ -1,216 +0,0 @@ ---[[-- - Functional forms of Lua operators. - - @functional std.operator -]] - - -local _ENV = _ENV -local type = type - - -local _ = { - debug_init = require "std.debug_init", - std = require "std.base", -} - -local _DEBUG = _.debug_init._DEBUG -local _tostring = _.std.tostring -local len = _.std.operator.len -local serialize = _.std.base.mnemonic - - -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end - -_ = nil - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - -local function eqv (a, b) - -- If they are the same primitive value, or they share a metatable - -- with an __eq metamethod that says they are equivalent, we're done! - if a == b then return true end - - -- Unless we now have two tables, the previous line ensures a ~= b. - if type (a) ~= "table" or type (b) ~= "table" then return false end - - -- Otherwise, compare serializations of each. - return serialize (a) == serialize (b) -end - - -local M = { - --- Stringify and concatenate arguments. - -- @param a an argument - -- @param b another argument - -- @return concatenation of stringified arguments. - -- @usage - -- --> "=> 1000010010" - -- functional.foldl (concat, "=> ", {10000, 100, 10}) - concat = function (a, b) return _tostring (a) .. _tostring (b) end, - - --- Equivalent to `#` operation, but respecting `__len` even on Lua 5.1. - -- @function len - -- @tparam object|string|table x operand - -- @treturn int length of list part of *t* - -- @usage for i = 1, len (t) do process (t[i]) end - len = len, - - --- Dereference a table. - -- @tparam table t a table - -- @param k a key to lookup in *t* - -- @return value stored at *t[k]* if any, otherwise `nil` - -- @usage - -- --> 4 - -- functional.foldl (get, {1, {{2, 3, 4}, 5}}, {2, 1, 3}) - get = function (t, k) return t and t[k] or nil end, - - --- Set a table element, honoring metamethods. - -- @tparam table t a table - -- @param k a key to lookup in *t* - -- @param v a value to set for *k* - -- @treturn table *t* - -- @usage - -- -- destructive table merge: - -- --> {"one", bar="baz", two=5} - -- functional.reduce (set, {"foo", bar="baz"}, {"one", two=5}) - set = function (t, k, v) t[k]=v; return t end, - - --- Return the sum of the arguments. - -- @param a an argument - -- @param b another argument - -- @return the sum of the *a* and *b* - -- @usage - -- --> 10110 - -- functional.foldl (sum, {10000, 100, 10}) - sum = function (a, b) return a + b end, - - --- Return the difference of the arguments. - -- @param a an argument - -- @param b another argument - -- @return the difference between *a* and *b* - -- @usage - -- --> 890 - -- functional.foldl (diff, {10000, 100, 10}) - diff = function (a, b) return a - b end, - - --- Return the product of the arguments. - -- @param a an argument - -- @param b another argument - -- @return the product of *a* and *b* - -- @usage - -- --> 10000000 - -- functional.foldl (prod, {10000, 100, 10}) - prod = function (a, b) return a * b end, - - --- Return the quotient of the arguments. - -- @param a an argument - -- @param b another argument - -- @return the quotient *a* and *b* - -- @usage - -- --> 1000 - -- functional.foldr (quot, {10000, 100, 10}) - quot = function (a, b) return a / b end, - - --- Return the modulus of the arguments. - -- @param a an argument - -- @param b another argument - -- @return the modulus of *a* and *b* - -- @usage - -- --> 3 - -- functional.foldl (mod, {65536, 100, 11}) - mod = function (a, b) return a % b end, - - --- Return the exponent of the arguments. - -- @param a an argument - -- @param b another argument - -- @return the *a* to the power of *b* - -- @usage - -- --> 4096 - -- functional.foldl (pow, {2, 3, 4}) - pow = function (a, b) return a ^ b end, - - --- Return the logical conjunction of the arguments. - -- @param a an argument - -- @param b another argument - -- @return logical *a* and *b* - -- @usage - -- --> true - -- functional.foldl (conj, {true, 1, "false"}) - conj = function (a, b) return a and b end, - - --- Return the logical disjunction of the arguments. - -- @param a an argument - -- @param b another argument - -- @return logical *a* or *b* - -- @usage - -- --> true - -- functional.foldl (disj, {true, 1, false}) - disj = function (a, b) return a or b end, - - --- Return the logical negation of the arguments. - -- @param a an argument - -- @return not *a* - -- @usage - -- --> {true, false, false, false} - -- functional.bind (functional.map, {std.ielems, neg}) {false, true, 1, 0} - neg = function (a) return not a end, - - --- Recursive table equivalence. - -- @param a an argument - -- @param b another argument - -- @treturn boolean whether *a* and *b* are recursively equivalent - eqv = eqv, - - --- Return the equality of the arguments. - -- @param a an argument - -- @param b another argument - -- @return `true` if *a* is *b*, otherwise `false` - eq = function (a, b) return a == b end, - - --- Return the inequality of the arguments. - -- @param a an argument - -- @param b another argument - -- @return `false` if *a* is *b*, otherwise `true` - -- @usage - -- --> true - -- local f = require "std.functional" - -- table.empty (f.filter (f.bind (neq, {6}), std.ielems, {6, 6, 6}) - neq = function (a, b) return a ~= b end, - - --- Return whether the arguments are in ascending order. - -- @param a an argument - -- @param b another argument - -- @return `true` if *a* is less then *b*, otherwise `false` - lt = function (a, b) return a < b end, - - --- Return whether the arguments are not in descending order. - -- @param a an argument - -- @param b another argument - -- @return `true` if *a* is not greater then *b*, otherwise `false` - lte = function (a, b) return a <= b end, - - --- Return whether the arguments are in descending order. - -- @param a an argument - -- @param b another argument - -- @return `true` if *a* is greater then *b*, otherwise `false` - gt = function (a, b) return a > b end, - - --- Return whether the arguments are not in ascending order. - -- @param a an argument - -- @param b another argument - -- @return `true` if *a* is not greater then *b*, otherwise `false` - gte = function (a, b) return a >= b end, -} - -return M diff --git a/lib/std/string.lua b/lib/std/string.lua index e7194ae..01ca331 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -23,10 +23,9 @@ local io_stderr = io.stderr local math_abs = math.abs local math_floor = math.floor local table_insert = table.insert +local string_format = string.format -local deprecated = nil - local _ = { debug_init = require "std.debug_init", std = require "std.base", @@ -45,7 +44,6 @@ local pickle = _.std.string.pickle local render = _.std.string.render local sortkeys = _.std.base.sortkeys local split = _.std.string.split -local toqstring = _.std.base.toqstring -- Perform typechecking with functions exported from this module, unless @@ -235,7 +233,10 @@ local function prettytostring (x, indent, spacing) return spacing .. "}" end, - elem = toqstring, + elem = function (x) + if type (x) ~= "string" then return tostring (x) end + return string_format ("%q", x) + end, pair = function (x, _, _, k, v, kstr, vstr) local type_k = type (k) @@ -522,11 +523,6 @@ M = { } -if deprecated then - M = merge (M, deprecated.string) -end - - return merge (M, string) diff --git a/lib/std/table.lua b/lib/std/table.lua index 0e0a618..01b1ace 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -32,7 +32,6 @@ local _ = { local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local collect = _.std.functional.collect local copy = _.std.base.copy local getmetamethod = _.std.getmetamethod local invert = _.std.table.invert diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 8710c79..e36589b 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -34,12 +34,12 @@ local type = type local coroutine_yield = coroutine.yield local coroutine_wrap = coroutine.wrap local table_remove = table.remove +local table_unpack = table.unpack or unpack local _ = { container = require "std.container", debug_init = require "std.debug_init", - operator = require "std.operator", std = require "std.base", } @@ -49,12 +49,10 @@ local Module = _.std.object.Module local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs -local get = _.operator.get local ielems = _.std.ielems local last = _.std.base.last local leaves = _.std.tree.leaves local len = _.std.operator.len -local reduce = _.std.functional.reduce -- Perform typechecking with functions exported from this module, unless @@ -116,6 +114,24 @@ local function _nodes (it, tr) end +-- 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 clone (t, nometa) local r = {} if not nometa then @@ -144,6 +160,11 @@ local function clone (t, nometa) end +local function get (t, k) + return t and t[k] or nil +end + + local function merge (t, u) for ty, p, n in _nodes (_pairs, u) do if ty == "leaf" then @@ -154,6 +175,27 @@ local function merge (t, u) end +local function reduce (fn, d, ifn, ...) + local argt + if not callable (ifn) then + ifn, argt = pairs, pack (ifn, ...) + else + argt = pack (...) + end + + local nextfn, state, k = ifn (table_unpack (argt, 1, argt.n)) + local t = pack (nextfn (state, k)) -- table of iteration 1 + + local r = d -- initialise accumulator + while t[1] ~= nil do -- until iterator returns nil + k = t[1] + r = fn (r, table_unpack (t, 1, t.n)) -- pass all iterator results to fn + t = pack (nextfn (state, k)) -- maintain loop invariant + end + return r +end + + --[[ ============ ]]-- --[[ Tree Object. ]]-- diff --git a/lib/std/tuple.lua b/lib/std/tuple.lua deleted file mode 100644 index 409a3dc..0000000 --- a/lib/std/tuple.lua +++ /dev/null @@ -1,199 +0,0 @@ ---[[-- - Tuple container prototype. - - An interned, immutable, nil-preserving tuple object. - - Like Lua strings, tuples with the same elements can be quickly compared - with a straight-forward `==` comparison. The `prototype` field in the - returned module table is the empty tuple, which can be cloned to create - tuples with other contents. - - In addition to the functionality described here, Tuple containers also - have all the methods and metamethods of the @{std.container.prototype} - (except where overridden here), - - The immutability guarantees only work if you don't change the contents - of tables after adding them to a tuple. Don't do that! - - Prototype Chain - --------------- - - table - `-> Container - `-> Tuple - - @prototype std.tuple -]] - - -local _ENV = _ENV -local error = error -local getmetatable = getmetatable -local next = next -local select = select -local setmetatable = setmetatable -local tonumber = tonumber -local type = type - -local string_format = string.format -local table_concat = table.concat -local table_unpack = table.unpack or unpack - - -local _ = { - container = require "std.container", - debug_init = require "std.debug_init", - std = require "std.base", -} - -local Container = _.container.prototype -local Module = _.std.object.Module - -local _DEBUG = _.debug_init._DEBUG -local pickle = _.std.string.pickle -local toqstring = _.std.base.toqstring - - -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end - -_ = nil - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - --- Maintain a weak functable of all interned tuples. --- @function intern --- @int n number of elements in *t*, including trailing `nil`s --- @tparam table t table of elements --- @treturn table interned *n*-tuple *t* -local intern = setmetatable ({}, { - __mode = "kv", - - __call = function (self, k, t) - if self[k] == nil then - self[k] = {[t] = k} - end - return self[k] - end, -}) - - ---- Unpack tuple values between index *i* and *j*, inclusive. --- @function unpack --- @int[opt=1] i first index to unpack --- @int[opt=t.n] j last index to unpack --- @return ... values at indices *i* through *j*, inclusive --- @usage --- t = Tuple (1, 3, 2, 5) --- --> 3, 2, 5 --- tuple.unpack (t, 2) -local function unpack (t, i, j) - return table_unpack (next (t), tonumber (i) or 1, tonumber (j) or t.n) -end - - - ---[[ ================== ]]-- ---[[ Type Declarations. ]]-- ---[[ ================== ]]-- - - ---- Tuple prototype object. --- @object prototype --- @string[opt="Tuple"] _type object name --- @int n number of tuple elements --- @see std.container.prototype --- @usage --- local Tuple = require "std.tuple".prototype --- function count (...) --- argtuple = Tuple (...) --- return argtuple.n --- end --- count () --> 0 --- count (nil) --> 1 --- count (false) --> 1 --- count (false, nil, true, nil) --> 4 -local prototype = Container { - _type = "std.tuple.Tuple", - - _init = function (obj, ...) - local n = select ("#", ...) - local s, t = {}, {n = n, ...} - for i = 1, n do s[i] = toqstring (t[i]) end - return intern (table_concat (s, ", "), t) - end, - - --- Metamethods - -- @section metamethods - - __index = function (self, k) - if k == "unpack" then return unpack end - return next (self) [k] - end, - - --- Return the length of this tuple. - -- @function prototype:__len - -- @treturn int number of elements in *tup* - -- @usage - -- -- Only works on Lua 5.2 or newer: - -- #Tuple (nil, 2, nil) --> 3 - -- -- For compatibility with Lua 5.1, use @{std.operator.len} - -- len (Tuple (nil, 2, nil) - __len = function (self) - return self.n - end, - - --- Prevent mutation of *tup*. - -- @function prototype:__newindex - -- @param k tuple key - -- @param v tuple value - -- @raise cannot change immutable tuple object - __newindex = function (self, k, v) - error ("cannot change immutable tuple object", 2) - end, - - --- Return a string representation of *tup* - -- @function prototype:__tostring - -- @treturn string representation of *tup* - -- @usage - -- -- 'Tuple ("nil", nil, false)' - -- print (Tuple ("nil", nil, false)) - __tostring = function (self) - local _, argstr = next (self) - return string_format ("%s (%s)", getmetatable (self)._type, argstr) - end, - - --- Return a loadable serialization of this object, where possible. - -- @function prototype:__pickle - -- @treturn string pickled object representataion - -- @see std.string.pickle - __pickle = function (self) - local mt, vals = getmetatable (self), {} - for i = 1, self.n do - vals[i] = pickle (self[i]) - end - if type (mt._module) == "string" then - return string_format ('require "%s".prototype (%s)', - mt._module, table_concat (vals, ",")) - end - return string_format ("%s (%s)", mt._type, table_concat (vals, ",")) - end, - - -- Prototype is the 0-tuple. - [intern ("", {n = 0})] = "", -} - - -return Module { - prototype = prototype, - unpack = unpack, -} diff --git a/local.mk b/local.mk index 8c2e32f..87ac6a5 100644 --- a/local.mk +++ b/local.mk @@ -43,14 +43,12 @@ update_copyright_env = \ doccorefunctionsdir = $(docdir)/core_functions doccorelibrariesdir = $(docdir)/core_libraries -docfunctionaldir = $(docdir)/functional_style docmodulesdir = $(docdir)/modules docobjectsdir = $(docdir)/object_system dist_doc_DATA = dist_doccorefunctions_DATA = dist_doccorelibraries_DATA = -dist_docfunctional_DATA = dist_docmodules_DATA = dist_docobjects_DATA = @@ -67,20 +65,17 @@ dist_luastd_DATA = \ lib/std/base.lua \ lib/std/container.lua \ lib/std/debug.lua \ - lib/std/functional.lua \ lib/std/io.lua \ lib/std/init.lua \ lib/std/list.lua \ lib/std/math.lua \ lib/std/object.lua \ - lib/std/operator.lua \ lib/std/package.lua \ lib/std/set.lua \ lib/std/strbuf.lua \ lib/std/string.lua \ lib/std/table.lua \ lib/std/tree.lua \ - lib/std/tuple.lua \ lib/std/version.lua \ $(NOTHING_ELSE) @@ -123,7 +118,6 @@ EXTRA_DIST += \ doccorefunctions = $(srcdir)/doc/core_functions/std doccorelibraries = $(srcdir)/doc/core_libraries/std -docfunctional = $(srcdir)/doc/functional_style/std docobjects = $(srcdir)/doc/object_system/std dist_doc_DATA += \ @@ -143,11 +137,6 @@ dist_doccorelibraries_DATA += \ $(doccorelibraries).table.html \ $(NOTHING_ELSE) -dist_docfunctional_DATA += \ - $(docfunctional).functional.html \ - $(docfunctional).operator.html \ - $(NOTHING_ELSE) - dist_docobjects_DATA += \ $(docobjects).container.html \ $(docobjects).list.html \ @@ -155,15 +144,13 @@ dist_docobjects_DATA += \ $(docobjects).set.html \ $(docobjects).strbuf.html \ $(docobjects).tree.html \ - $(docobjects).tuple.html \ $(NOTHING_ELSE) ## Parallel make gets confused when one command ($(LDOC)) produces ## multiple targets (all the html files above), so use the presence ## of the doc directory as a sentinel file. $(dist_doc_DATA) $(dist_doccorefunctions_DATA): $(srcdir)/doc -$(dist_doccorelibraries_DATA) $(dist_docfunctional_DATA): $(srcdir)/doc -$(dist_docobjects_DATA): $(srcdir)/doc +$(dist_doccorelibraries_DATA) $(dist_docobjects_DATA): $(srcdir)/doc $(srcdir)/doc: $(dist_lua_DATA) $(dist_luastd_DATA) test -d $@ || mkdir $@ diff --git a/rockspec.conf b/rockspec.conf index 397ad26..13b4060 100644 --- a/rockspec.conf +++ b/rockspec.conf @@ -6,8 +6,8 @@ description: summary: General Lua Libraries detailed: stdlib is a library of modules for common programming tasks, - including list, table and functional operations, objects, - pickling, pretty-printing and command-line option parsing. + including list and table operations, objects, pickling and + pretty-printing. dependencies: - lua >= 5.1, < 5.4 diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 21758de..5588f3c 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -8,6 +8,39 @@ before: | M = require (this_module) + table_unpack = table.unpack or unpack + + function map (mapfn, ...) + local argt, r = pack (...), {} + + local nextfn, state, k = pairs (table_unpack (argt, 1, argt.n)) + local mapargs = pack (nextfn (state, k)) + + local arity = 1 + while mapargs[1] ~= nil do + local d, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) + if v ~= nil then + arity, r = 2, {} break + end + r[#r + 1] = d + mapargs = {nextfn (state, mapargs[1])} + end + + if arity > 1 then + -- No need to start over here, because either: + -- (i) arity was never 1, and the original value of mapargs is correct + -- (ii) arity used to be 1, but we only consumed nil values, so the + -- current mapargs with arity > 1 is the correct next value to use + while mapargs[1] ~= nil do + local k, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) + r[k] = v + mapargs = pack (nextfn (state, mapargs[1])) + end + end + return r + end + + specify std.debug: - context when required: - context by name: @@ -44,7 +77,7 @@ specify std.debug: require "std.debug" (%s) ]], require "std".tostring (debugp), - table.concat (require "std.functional".map (mkwrap, {...}), ", ")) + table.concat (map (mkwrap, {...}), ", ")) end - it does nothing when _DEBUG is disabled: @@ -82,7 +115,7 @@ specify std.debug: require "std.debug".say (%s) ]], require "std".tostring (debugp), - table.concat (require "std.functional".map (mkwrap, {...}), ", ")) + table.concat (map (mkwrap, {...}), ", ")) end f = M.say diff --git a/specs/functional_spec.yaml b/specs/functional_spec.yaml deleted file mode 100644 index 379e6ad..0000000 --- a/specs/functional_spec.yaml +++ /dev/null @@ -1,748 +0,0 @@ -before: - base = require "std.base" - - this_module = "std.functional" - global_table = "_G" - - exported_apis = { "any", "bind", "callable", "case", "collect", "compose", - "cond", "curry", "filter", "flatten", "foldl", "foldr", - "id", "ireverse", "lambda", "map", "map_with", "memoize", - "nop", "product", "reduce", "shape", "zip", "zip_with" } - - function elems (t) - local fn, istate, ctrl = base.pairs (t) - return function (state, _) - local v - ctrl, v = fn (state, ctrl) - if ctrl then return v end - end, istate, true - end - - function npairs (t) - local m = base.getmetamethod (t, "__len") - local i, n = 0, m and m(t) or base.table.maxn (t) - return function (t) - i = i + 1 - if i <= n then return i, t[i] end - end, - t, i - end - - function iter (...) - local l = pack (...) - local oob = l.n + 1 - return function (l, n) - n = n - 1 - if n > 0 then return n, l[n] or false end - end, l, oob - end - - M = require (this_module) - - -specify std.functional: -- 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 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 via the std module: - - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). - to_equal {} - - -- describe any: - - before: - stop = function () return true end - fail = function () return false end - - f = M.any - - - context with bad arguments: - badargs.diagnose (f, "std.functional.any (func*)") - - - it returns a single function: - expect (type (f (M.id))).to_be "function" - - it propagates arguments and returned results: - expect (f (M.id) (true)).to_be (true) - expect ({f (M.id) (1, 2, 3)}).to_equal {1, 2, 3} - - it propagates nil arguments correctly: - truthy = function (x) return x and true or false end - expect (f (truthy) (true, false, nil, nil, 0, nil)). - to_equal (true, false, false, false, true, false) - - it calls all functions until one returns non-nil: - expect (f (M.id, M.id, stop, fail) (nil)).to_be (true) - - it only looks at first returned value: - expect (f (M.id, M.id, stop, fail) (nil, "ignored")).to_be (true) - - it does not call remaining functions after non-nil return: - expect (f (M.id, fail) (true)).to_be (true) - - -- describe bind: - - before: - op = require "std.operator" - - f = M.bind - - - context with bad arguments: - badargs.diagnose (f, "std.functional.bind (function, table)") - - - it does not affect normal operation if no arguments are bound: - expect (f (math.min, {}) (2, 3, 4)).to_be (2) - - it takes the extra arguments into account: - expect (f (math.min, {1, 0}) (2, 3, 4)).to_be (0) - - it appends final call arguments: - expect (f (math.max, {2, 3}) (4, 5, 1)).to_be (5) - - it does not require all arguments in final call: - div = function (a, b) return a / b end - expect (f (div, {100}) (25)).to_be (4) - - it supports out of order extra arguments: - expect (f (op.pow, {[2] = 3}) (2)).to_be (8) - - it propagates nil arguments correctly: - r = pack (f (M.id, {[4]="d"}) (1)) - expect (r).to_equal (pack (1, nil, nil, "d")) - r = pack (f (M.id, {[4]="d"}) (nil, 2)) - expect (r).to_equal (pack (nil, 2, nil, "d")) - r = pack (f (M.id, {[2]="b", [4]="d"}) (nil, 3, 5, 6, nil)) - expect (r).to_equal (pack (nil, "b", 3, "d", 5, 6, nil)) - r = pack (f (M.id, {[2]="b", [4]="d"}) (nil, 3, nil, nil, 7)) - expect (r).to_equal (pack (nil, "b", 3, "d", nil, nil, 7)) - r = pack (f (M.id, {[2]="b", [4]="d"}) (nil, 3, nil, nil, 7, nil, nil)) - expect (r).to_equal (pack (nil, "b", 3, "d", nil, nil, 7, nil, nil)) - - -- describe callable: - - before: - f = M.callable - - - context with bad arguments: - badargs.diagnose (f, "std.functional.callable (?any)") - - - it returns the function associated with a callable: - Container = require "std.container" { __call = M.nop } - for _, v in ipairs { - true, - 42, - "str", - io.stderr, - {}, - M.nop, - setmetatable ({}, {__call = M.nop}), - Container, - } do - expect (f (v)).to_be (pcall (v, {}) and M.nop or nil) - end - - it returns 'nil' for uncallable arguments: - expect (f ()).to_be (nil) - expect (f {}).to_be (nil) - expect (f "").to_be (nil) - - -- describe case: - - before: - yes = function () return true end - no = function () return false end - default = function (s) return s end - branches = { yes = yes, no = no, default } - - f = M.case - - - context with bad arguments: | - badargs.diagnose (f, "std.functional.case (?any, #table)") - - - it matches against branch keys: - expect (f ("yes", branches)).to_be (true) - expect (f ("no", branches)).to_be (false) - - it has a default for unmatched keys: - expect (f ("none", branches)).to_be "none" - - it returns nil for unmatched keys with no default: - expect (f ("none", { yes = yes, no = no })).to_be (nil) - - it returns non-function matches: - expect (f ("t", {t = true})).to_be (true) - - it evaluates returned functions: - expect (f ("fn", {fn = function () return true end})). - to_be (true) - - it passes 'with' to function matches: - expect (f ("with", {function (s) return s end})).to_be "with" - - it evaluates returned functables: - functable = setmetatable ({}, {__call = function (t, with) return with end}) - expect (f ("functable", {functable})).to_be "functable" - - it evaluates 'with' exactly once: - s = "prince" - function acc () s = s .. "s"; return s end - expect (f (acc (), { - prince = function () return "one" end, - princes = function () return "many" end, - princess = function () return "one" end, - function () return "gibberish" end, - })).to_be "many" - - -- describe collect: - - before: - f = M.collect - - - context with bad arguments: - badargs.diagnose (f, "std.functional.collect ([func], ?any*)") - - - it collects a list of single return value iterator results: - expect (f (base.ielems, {"a", "b", "c"})).to_equal {"a", "b", "c"} - - it collects a table of key:value iterator results: - t = {"first", second="two", last=3} - expect (f (pairs, t)).to_equal (t) - - it defaults to npairs iteration: - expect (f {1, 2, [5]=5, a="b", c="d"}).to_equal {1, 2, [5]=5} - - it propagates nil arguments correctly: - expect (f {"a", nil, nil, "d", "e"}).to_equal {"a", [4]="d", [5]="e"} - expect (f (iter, "a", nil, nil, "d", "e")). - to_equal {"a", false, false, "d", "e"} - expect (f (iter, nil, nil, 3, 4, nil, nil)). - to_equal {false, false, 3, 4, false, false} - - -- describe compose: - - before: - f = M.compose - - - context with bad arguments: - badargs.diagnose (f, "std.functional.compose (func*)") - - - it composes a single function correctly: - expect (f (M.id) (1)).to_be (1) - - it propagates nil arguments correctly: - expect (pack (f (M.id) (nil, 2, nil, nil))). - to_equal (pack (nil, 2, nil, nil)) - expect (pack (f (M.id, M.id) (nil, 2, nil, nil))). - to_equal (pack (nil, 2, nil, nil)) - - it composes functions in the correct order: - expect (f (math.sin, math.cos) (1)). - to_be (math.cos (math.sin (1))) - - -- describe cond: - - before: - yes = function () return true end - no = function () return false end - default = function (s) return s end - branches = { yes = yes, no = no, default } - - f = M.cond - - - it returns nil for no arguments: - expect (f ()).to_be (nil) - - it evaluates a single function argument: - expect (f (function () return true end)).to_be (true) - - it evaluates a single functable argument: - functable = setmetatable ({}, {__call = function () return true end}) - expect (f (functable)).to_be (true) - - it returns a non-callable single argument directly: - expect (f "foo").to_be "foo" - - it evaluates a branch function if expr is truthy: - expect (f ("truthy", function (s) return s end)).to_be "truthy" - - it returns nil if the last expr is falsey: - expect (f (nil, function (s) return "falsey" end)).to_be (nil) - expect (f (false, true, false, true)).to_be (nil) - - it recurses with remaining arguments if first argument is falsey: - expect (f (nil, true, 42, M.id)).to_be (42) - expect (f (nil, true, false, false, 42, M.id)).to_be (42) - - -- describe curry: - - before: - op = require "std.operator" - - f = M.curry - - - context with bad arguments: - badargs.diagnose (f, "std.functional.curry (func, int)") - - - it returns a zero argument function uncurried: - expect (f (f, 0)).to_be (f) - - it returns a one argument function uncurried: - expect (f (f, 1)).to_be (f) - - it curries a two argument function: - expect (f (f, 2)).not_to_be (f) - - it evaluates intermediate arguments one at a time: - expect (f (math.min, 3) (2) (3) (4)).to_equal (2) - - it returns a curried function that can be partially applied: - bin = f (op.pow, 2) (2) - expect (bin (2)).to_be (op.pow (2, 2)) - expect (bin (10)).to_be (op.pow (2, 10)) - - -- describe filter: - - before: - elements = {"a", "b", "c", "d", "e"} - inverse = {a=1, b=2, c=3, d=4, e=5} - - f = M.filter - - - context with bad arguments: - badargs.diagnose (f, "std.functional.filter (func, [func], any*)") - - - it works with an empty table: - expect (f (M.id, pairs, {})).to_equal {} - - it iterates through element keys: - expect (f (M.id, base.ielems, elements)).to_equal {"a", "b", "c", "d", "e"} - expect (f (M.id, elems, inverse)).to_contain.a_permutation_of {1, 2, 3, 4, 5} - - it propagates nil arguments correctly: - t = {"a", nil, nil, "d", "e"} - expect (f (M.id, npairs, t)).to_equal (t) - expect (f (M.id, iter, nil, nil, 3, 4, nil, nil)). - to_equal {false, false, 3, 4, false, false} - - it passes all iteration result values to filter predicate: - t = {} - f (function (k, v) t[k] = v end, pairs, elements) - expect (t).to_equal (elements) - - it returns a list of filtered single return value iterator results: - expect (f (function (e) return e:match "[aeiou]" end, base.ielems, elements)). - to_equal {"a", "e"} - - it returns a table of filtered key:value iterator results: - t = {"first", second=2, last="three"} - expect (f (function (k, v) return type (v) == "string" end, pairs, t)). - to_equal {"first", last="three"} - expect (f (function (k, v) return k % 2 == 0 end, ipairs, elements)). - to_equal {[2]="b", [4]="d"} - - it defaults to pairs iteration: - t = {"first", second=2, last="three"} - expect (f (function (k, v) return type (v) == "string" end, t)). - to_equal {"first", last="three"} - - -- describe flatten: - - before: - t = {{{"one"}}, "two", {{"three"}, "four"}} - - f = M.flatten - - - context with bad arguments: - badargs.diagnose (f, "std.functional.flatten (table)") - - - it returns a table: - expect (type (f (t))).to_be "table" - - it works for an empty table: - expect (f {}).to_equal {} - - it flattens a nested table: - expect (f (t)).to_equal {"one", "two", "three", "four"} - - -- describe foldl: - - before: - op = require "std.operator" - f = M.foldl - - - context with bad arguments: - badargs.diagnose (f, "std.functional.foldl (func, [any], table)") - - - it works with an empty table: - expect (f (op.sum, 10000, {})).to_be (10000) - - it folds a binary function through a table: - expect (f (op.sum, 10000, {1, 10, 100})).to_be (10111) - - it folds from left to right: - expect (f (op.pow, 2, {3, 4})).to_be ((2 ^ 3) ^ 4) - - it supports eliding init argument: - expect (f (op.pow, {2, 3, 4})).to_be ((2 ^ 3) ^ 4) - - -- describe foldr: - - before: - op = require "std.operator" - f = M.foldr - - - context with bad arguments: - badargs.diagnose (f, "std.functional.foldr (func, [any], table)") - - - it works with an empty table: - expect (f (op.sum, 1, {})).to_be (1) - - it folds a binary function through a table: - expect (f (op.sum, {10000, 100, 10, 1})).to_be (10111) - - it folds from right to left: - expect (f (op.quot, 10, {10000, 100})).to_be (10000 / (100 / 10)) - - it supports eliding init argument: - expect (f (op.quot, {10000, 100, 10})).to_be (10000 / (100 / 10)) - - -- describe id: - - before: - f = M.id - - it returns argument unchanged: - expect (f (true)).to_be (true) - expect (f {1, 1, 2, 3}).to_equal {1, 1, 2, 3} - - it returns multiple arguments unchanged: - expect ({f (1, "two", false)}).to_equal {1, "two", false} - - it propagates nil values correctly: - expect (pack (f ("a", nil, nil, "d", "e"))). - to_equal (pack ("a", nil, nil, "d", "e")) - expect (pack (f (nil, nil, 3, 4, nil, nil))). - to_equal (pack (nil, nil, 3, 4, nil, nil)) - - -- describe ireverse: - - before: | - __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, - }) - - f = M.ireverse - - - context with bad arguments: - badargs.diagnose (f, "std.functional.ireverse (table)") - - - it returns a new list: - t = {1, 2, 5} - expect (f (t)).not_to_be (t) - - it reverses the elements relative to the original list: - expect (f {1, 2, "five"}).to_equal {"five", 2, 1} - - it ignores the dictionary part of a table: - expect (f {1, 2, "five"; a = "b", c = "d"}).to_equal {"five", 2, 1} - - it respects __len metamethod: - expect (f (__index)).to_equal {"g", "n", "i", "r", "t", "s", " ", "a"} - - it works for an empty list: - expect (f {}).to_equal {} - - -- describe lambda: - - before: - f = M.lambda - - callable = function (x) - if M.callable (x) then return true end - return false - end - - - context with bad arguments: - badargs.diagnose (f, "std.functional.lambda (string)") - - examples {["it diagnoses bad lambda string"] = function () - expect (select (2, f "foo")).to_be "invalid lambda string 'foo'" - end} - examples {["it diagnoses an uncompilable expression"] = function () - expect (select (2, f "||+")).to_be "invalid lambda string '||+'" - expect (select (2, f "=")).to_be "invalid lambda string '='" - end} - - - it returns previously compiled lambdas: - fn = f "|a,b|a==b" - expect (fn).to_be (f "|a,b|a==b") - - - context with argument format: - - it returns a callable: - expect (callable (f "|x| 1+x")).to_be (true) - - it compiles to a working Lua function: - fn = f "||42" - expect (fn ()).to_be (42) - - it propagates argument values: - fn = f "|...| {...}" - expect (fn (1,2,3)).to_equal {1,2,3} - - it can be stringified: - exp = "|a,b|a==b" - expect (tostring (f (exp))).to_be (exp) - - context with expression format: - - it returns a function: - expect (callable (f "_")).to_be (true) - - it compiles to a working Lua function: - fn = f "=42" - expect (fn ()).to_be (42) - - it sets auto-argument values: - fn = f "_*_" - expect (fn (42)).to_be (1764) - - it sets numeric auto-argument values: - fn = f "_1+_2+_3" - expect (fn (1, 2, 5)).to_be (8) - - it can be stringified: - exp = "_%2==0" - expect (tostring (f (exp))).to_be (exp) - - -- describe map: - - before: - elements = {"a", "b", "c", "d", "e"} - inverse = {a=1, b=2, c=3, d=4, e=5} - - f = M.map - - - context with bad arguments: - badargs.diagnose (f, "std.functional.map (func, [func], ?any*)") - - - it works with an empty table: - expect (f (M.id, ipairs, {})).to_equal {} - - it iterates through elements: - expect (f (M.id, ipairs, elements)).to_equal (elements) - expect (f (M.id, pairs, inverse)).to_contain.a_permutation_of (elements) - - it propagates nil arguments correctly: - t = {"a", nil, nil, "d", "e"} - expect (f (M.id, npairs, t)).to_equal (t) - expect (f (M.id, iter, nil, nil, 3, 4, nil, nil)). - to_equal {false, false, 3, 4, false, false} - - it passes all iteration result values to map function: - t = {} - f (function (k, v) t[k] = v end, pairs, elements) - expect (t).to_equal (elements) - - it returns a list of mapped single return value iterator results: - expect (f (function (e) return e:match "[aeiou]" end, base.ielems, elements)). - to_equal {"a", "e"} - expect (f (function (e) return e .. "x" end, elems, elements)). - to_contain.a_permutation_of {"ax", "bx", "cx", "dx", "ex"} - - it returns a table of mapped key:value iterator results: - t = {"first", second=2, last="three"} - expect (f (function (k, v) return type (v) == "string" end, pairs, t)). - to_contain.a_permutation_of {true, false, true} - expect (f (function (k, v) return k % 2 == 0 end, ipairs, elements)). - to_equal {false, true, false, true, false} - - it supports key:value results from mapping function: - expect (f (function (k, v) return v, k end, pairs, elements)). - to_equal (inverse) - - it defaults to pairs iteration: - t = {"first", second=2, last="three"} - expect (f (function (k, v) return type (v) == "string" end, t)). - to_contain.a_permutation_of {true, false, true} - - -- describe map_with: - - before: - t = {{1, 2, 3}, {4, 5}} - fn = function (...) return select ("#", ...) end - - f = M.map_with - - - context with bad arguments: - badargs.diagnose (f, "std.functional.map_with (func, table of tables)") - - - it works for an empty table: - expect (f (fn, {})).to_equal ({}) - - it returns a table: - u = f (fn, t) - expect (type (u)).to_be "table" - - it creates a new table: - old = t - u = f (fn, t) - expect (t).to_equal (old) - expect (u).not_to_equal (old) - expect (t).to_equal {{1, 2, 3}, {4, 5}} - - it maps a function over a list of argument lists: - expect (f (fn, t)).to_equal {3, 2} - - it discards hash-part arguments: - expect (f (fn, {{1,x=2,3}, {4,5,y="z"}})).to_equal {2, 2} - - it maps a function over a table of argument lists: - expect (f (fn, {a={1,2,3}, b={4,5}})).to_equal {a=3, b=2} - - -- describe memoize: - - before: - f = M.memoize - - memfn = f (function (...) - n = select ("#", ...) - if n == 0 then return nil, "bzzt" end - return n, {...} - end) - - - context with bad arguments: - badargs.diagnose (f, "std.functional.memoize (func, ?func)") - - - it propagates multiple return values: - expect ((memfn ())).to_be (nil) - expect (select (2, memfn ())).to_be "bzzt" - - it propogates nil return values: - expect (pack (f (function (...) return ... end) (nil, nil, 3, 4, nil, nil))). - to_equal (pack (nil, nil, 3, 4, nil, nil)) - - it propagates multiple arguments: - expect ({memfn ("a", 42, false)}).to_equal {3, {"a", 42, false}} - - it propagates nil arguments: - n, t = memfn (nil) - expect (t).to_equal ({}) - expect ({memfn (nil, 2, nil, nil)}).to_equal {4, {[2]=2}} - - it returns the same results for the same arguments: - n, t = memfn (1) - n, u = memfn (1) - expect (t).to_be (u) - - it returns a different object for different arguments: - n, t = memfn (1) - n, u = memfn (1, 2) - expect (t).not_to_be (u) - - it returns the same object for table valued arguments: - n, t = memfn {1, 2, 3} - n, u = memfn {1, 2, 3} - expect (t).to_be (u) - n, t = memfn {foo = "bar", baz = "quux"} - n, u = memfn {foo = "bar", baz = "quux"} - expect (t).to_be (u) - n, t = memfn {baz = "quux", foo = "bar"} - expect (t).to_be (u) - - it returns a different object for different table arguments: - n, t = memfn {1, 2, 3} - n, u = memfn {1, 2} - expect (t).not_to_be (u) - n, u = memfn {3, 1, 2} - expect (t).not_to_be (u) - n, u = memfn {1, 2, 3, 4} - expect (t).not_to_be (u) - - it returns a different object for additional trailing nils: - n, t = memfn (1, nil) - n, u = memfn (1) - expect (t).not_to_be (u) - n, u = memfn (1, nil, nil) - expect (t).not_to_be (u) - - it accepts alternative mnemonic function: - mnemonic = function (...) return select ("#", ...) end - memfn = f (function (x) return {x} end, mnemonic) - expect (memfn "same").to_be (memfn "not same") - expect (memfn (1, 2)).to_be (memfn (false, "x")) - expect (memfn "one").not_to_be (memfn ("one", "two")) - - -- describe nop: - - before: - f = M.nop - - it accepts any number of arguments: - expect (f ()).to_be (nil) - expect (f (false)).to_be (nil) - expect (f (1, 2, 3, nil, "str", {}, f)).to_be (nil) - - it returns no values: - expect (f (1, "two", false)).to_be (nil) - - -- describe product: - - before: - f = M.product - - - context with bad arguments: - badargs.diagnose (f, "std.functional.product (list*)") - - - it works with an empty table: - expect (f {}).to_equal {} - - it returns a list of elements from a single argument: - expect (f {'a', 'b', 'c'}).to_equal {{'a'}, {'b'}, {'c'}} - - it lists combinations with one element from each argument: - expect (f ({1, 2, 3}, {4, 5, 6})).to_equal { - {1, 4}, {1, 5}, {1, 6}, - {2, 4}, {2, 5}, {2, 6}, - {3, 4}, {3, 5}, {3, 6}, - } - expect (f ({1, 2}, {3, 4}, {5, 6})).to_equal { - {1, 3, 5}, {1, 3, 6}, {1, 4, 5}, {1, 4, 6}, - {2, 3, 5}, {2, 3, 6}, {2, 4, 5}, {2, 4, 6}, - } - expect (M.map (table.concat, base.ielems, f ({0,1},{0,1},{0,1}))). - to_equal {"000", "001", "010", "011", "100", "101", "110", "111"} - - -- describe reduce: - - before: - op = require "std.operator" - - f = M.reduce - - - context with bad arguments: - badargs.diagnose (f, "std.functional.reduce (func, any, [func], ?any*)") - - - it works with an empty table: - expect (f (op.sum, 2, ipairs, {})).to_be (2) - - it calls a binary function over single return value iterator results: - expect (f (op.sum, 2, base.ielems, {3})). - to_be (2 + 3) - expect (f (op.prod, 2, base.ielems, {3, 4})). - to_be (2 * 3 * 4) - - it calls a binary function over key:value iterator results: - expect (f (op.sum, 2, base.ielems, {3})).to_be (2 + 3) - expect (f (op.prod, 2, base.ielems, {3, 4})).to_be (2 * 3 * 4) - - it propagates nil arguments correctly: - function set (t, k, v) t[k] = tostring (v) return t end - expect (f (set, {}, npairs, {1, nil, nil, "a", false})). - to_equal {"1", "nil", "nil", "a", "false"} - expect (f (set, {}, npairs, {nil, nil, "3"})). - to_equal {"nil", "nil", "3"} - expect (f (set, {}, iter, nil, nil, 3, 4, nil, nil)). - to_equal {"false", "false", "3", "4", "false", "false"} - - it reduces elements from left to right: - expect (f (op.pow, 2, base.ielems, {3, 4})).to_be ((2 ^ 3) ^ 4) - - it passes all iterator results to accumulator function: - expect (f (rawset, {}, {"one", two=5})).to_equal {"one", two=5} - - -- describe shape: - - before: - l = {1, 2, 3, 4, 5, 6} - - f = M.shape - - - context with bad arguments: - badargs.diagnose (f, "std.functional.shape (table, table)") - - - it returns a table: - expect (objtype (f ({2, 3}, l))).to_be "table" - - it works for an empty table: - expect (f ({0}, {})).to_equal ({}) - - it returns the result in a new table: - expect (f ({2, 3}, l)).not_to_be (l) - - it does not perturb the argument table: - f ({2, 3}, l) - expect (l).to_equal {1, 2, 3, 4, 5, 6} - - it reshapes a table according to given dimensions: - expect (f ({2, 3}, l)). - to_equal ({{1, 2, 3}, {4, 5, 6}}) - expect (f ({3, 2}, l)). - to_equal ({{1, 2}, {3, 4}, {5, 6}}) - - it treats 0-valued dimensions as an indefinite number: - expect (f ({2, 0}, l)). - to_equal ({{1, 2, 3}, {4, 5, 6}}) - expect (f ({0, 2}, l)). - to_equal ({{1, 2}, {3, 4}, {5, 6}}) - - -- describe zip: - - before: - tt = {{1, 2}, {3, 4}, {5, 6}} - - f = M.zip - - - context with bad arguments: - badargs.diagnose (f, "std.functional.zip (table)") - - - it works for an empty table: - expect (f {}).to_equal {} - - it is the inverse of itself: - expect (f (f (tt))).to_equal (tt) - - it transposes rows and columns: - expect (f (tt)).to_equal {{1, 3, 5}, {2, 4, 6}} - expect (f {x={a=1, b=2}, y={a=3, b=4}, z={b=5}}). - to_equal {a={x=1, y=3}, b={x=2,y=4,z=5}} - - -- describe zip_with: - - before: - tt = {{1, 2}, {3, 4}, {5}} - fn = function (...) return tonumber (table.concat {...}) end - - f = M.zip_with - - - context with bad arguments: - badargs.diagnose (f, "std.functional.zip_with (function, table of tables)") - - - it works for an empty table: - expect (f (fn, {})).to_equal {} - - it returns a table: - expect (type (f (fn, tt))).to_be "table" - - it returns the result in a new table: - expect (f (fn, tt)).not_to_be (tt) - - it does not perturb the argument list: - m = f (fn, tt) - expect (tt).to_equal {{1, 2}, {3, 4}, {5}} - - it combines column entries with a function: - expect (f (fn, tt)).to_equal {135, 24} - - it discards hash-part arguments: - expect (f (fn, {{1,2}, x={3,4}, {[2]=5}})).to_equal {1, 25} - - it combines matching key entries with a function: - expect (f (fn, {{a=1,b=2}, {a=3,b=4}, {b=5}})). - to_equal {a=13, b=245} diff --git a/specs/operator_spec.yaml b/specs/operator_spec.yaml deleted file mode 100644 index 10aac5c..0000000 --- a/specs/operator_spec.yaml +++ /dev/null @@ -1,305 +0,0 @@ -before: | - this_module = "std.operator" - global_table = "_G" - - M = require (this_module) - -specify std.operator: -- 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 {} - - - context via the std module: - - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). - to_equal {} - - -- describe concat: - - before: - f = M.concat - - - it stringifies its arguments: - expect (f (1, "")).to_be "1" - expect (f ("", 2)).to_be "2" - - it concatenates its arguments: - expect (f (1, 2)).to_be "12" - - -- describe len: - - before: - f = M.len - - - context with string argument: - - it returns the length of a string: - expect (f "").to_be (0) - expect (f "abc").to_be (3) - - - context with table argument: - - it returns the length 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 always ignores elements after the first hole: - expect (f {1, 2, nil, nil, 3}).to_be (2) - expect (f {1, 2, [5]=3}).to_be (2) - - it respects __len metamethod: - t = setmetatable ({1, 2, [5]=3}, {__len = function () return 42 end}) - expect (f (t)).to_be (42) - - - context with object argument: - - before: - Object = require "std.object" {} - subject = Object {"a", "b", "c"} - - - it returns the length of an object: - expect (f (subject)).to_be (3) - - it works with an empty object: - expect (f (Object)).to_be (0) - - it respects __len metamethod: - expect (f (Object {__len = function () return 42 end})).to_be (42) - expect (f (subject {__len = function () return 42 end})).to_be (42) - - -- describe get: - - before: - f = M.get - - - it dereferences a table: - expect (f ({}, 1)).to_be (nil) - expect (f ({"foo", "bar"}, 1)).to_be "foo" - expect (f ({foo = "bar"}, "foo")).to_be "bar" - -- describe set: - - before: - f = M.set - - - it sets a table entry: - expect (f ({}, 1, 42)).to_equal {42} - expect (f ({}, "foo", 42)).to_equal {foo=42} - - it overwrites an existing entry: - expect (f ({1, 2}, 1, 42)).to_equal {42, 2} - expect (f ({foo="bar", baz="quux"}, "foo", 42)). - to_equal {foo=42, baz="quux"} - -- describe sum: - - before: - f = M.sum - - - it returns the sum of its arguments: - expect (f (99, 2)).to_be (99 + 2) - -- describe diff: - - before: - f = M.diff - - - it returns the difference of its arguments: - expect (f (99, 2)).to_be (99 - 2) - -- describe prod: - - before: - f = M.prod - - - it returns the product of its arguments: - expect (f (99, 2)).to_be (99 * 2) - -- describe quot: - - before: - f = M.quot - - - it returns the quotient of its arguments: - expect (f (99, 2)).to_be (99 / 2) - -- describe mod: - - before: - f = M.mod - - - it returns the modulus of its arguments: - expect (f (99, 2)).to_be (99 % 2) - -- describe pow: - - before: - f = M.pow - - - it returns the power of its arguments: - expect (f (99, 2)).to_be (99 ^ 2) - -- describe conj: - - before: - f = M.conj - - - it returns the logical and of its arguments: - expect (f (false, false)).to_be (false) - expect (f (false, true)).to_be (false) - expect (f (true, false)).to_be (false) - expect (f (true, true)).to_be (true) - - it supports truthy and falsey arguments: - expect (f ()).to_be (nil) - expect (f (0)).to_be (nil) - expect (f (nil, 0)).to_be (nil) - expect (f (0, "false")).to_be ("false") - -- describe disj: - - before: - f = M.disj - - - it returns the logical or of its arguments: - expect (f (false, false)).to_be (false) - expect (f (false, true)).to_be (true) - expect (f (true, false)).to_be (true) - expect (f (true, true)).to_be (true) - - it supports truthy and falsey arguments: - expect (f ()).to_be (nil) - expect (f (0)).to_be (0) - expect (f (nil, 0)).to_be (0) - expect (f (0, "false")).to_be (0) - -- describe neg: - - before: - f = M.neg - - - it returns the logical not of its argument: - expect (f (false)).to_be (true) - expect (f (true)).to_be (false) - - it supports truthy and falsey arguments: - expect (f ()).to_be (true) - expect (f (0)).to_be (false) - -- describe eqv: - - before: | - f = M.eqv - - __eq = function (a, b) return #a == #b end - X = function (x) return setmetatable (x, {__eq = __eq}) end - - - it returns false if primitive types differ: - expect (f (nil, 1)).to_be (false) - expect (f ("1", 1)).to_be (false) - - it returns true if primitives are equal: - expect (f (nil, nil)).to_be (true) - expect (f (false, false)).to_be (true) - expect (f (10, 10)).to_be (true) - expect (f ("one", "one")).to_be (true) - - it returns true if tables are equivalent: - expect (f ({}, {})).to_be (true) - expect (f ({"one"}, {"one"})).to_be (true) - expect (f ({1,2,3,4,5}, {1,2,3,4,5})).to_be (true) - expect (f ({a=1,b=2,c=3}, {c=3,b=2,a=1})).to_be (true) - - it compares values recursively: - expect (f ({1, {{2, 3}, 4}}, {1, {{2, 3}, 4}})).to_be (true) - expect (f ({a=1, b={c={2, d=3}, 4}}, {a=1, b={c={2, d=3}, 4}})). - to_be (true) - - it compares keys recursively: - expect (f ({[{a=1}]=2}, {[{a=1}]=2})).to_be (true) - expect (f ({[{a=1}]={[{2}]="b"}}, {[{a=1}]={[{2}]="b"}})). - to_be (true) - - it returns false if table lengths differ: - expect (f ({1,2,3,4}, {1,2,3,4,5})).to_be (false) - expect (f ({1,2,3,4}, {[0]=0,1,2,3,4})).to_be (false) - expect (f ({[{a=1}]={[{2}]="b"}}, {[{a=1}]={[{2}]="b", 3}})). - to_be (false) - expect (f ({[{a=1}]={[{2}]="b"}}, {[{a=1}]={[{2}]="b"}, 3})). - to_be (false) - - it returns true if __eq metamethod matches: - expect (f (X {}, X {})).to_be (true) - expect (f (X {1}, X {2})).to_be (true) - - it returns false if _eq metamethod mismatches: - expect (f (X {}, X {1})).to_be (false) - -- describe eq: - - before: - f = M.eq - - - it returns true if the arguments are equal: - expect (f ()).to_be (true) - expect (f ("foo", "foo")).to_be (true) - - it returns false if the arguments are unequal: - expect (f (1)).to_be (false) - expect (f ("foo", "bar")).to_be (false) - -- describe neq: - - before: - f = M.neq - - - it returns false if the arguments are equal: - expect (f (1, 1)).to_be (false) - expect (f ("foo", "foo")).to_be (false) - - it returns true if the arguments are unequal: - expect (f (1)).to_be (true) - expect (f ("foo", "bar")).to_be (true) - expect (f ({}, {})).to_be (true) - -- describe lt: - - before: - f = M.lt - - - it returns true if the arguments are in ascending order: - expect (f (1, 2)).to_be (true) - expect (f ("a", "b")).to_be (true) - - it returns false if the arguments are not in ascending order: - expect (f (2, 2)).to_be (false) - expect (f (3, 2)).to_be (false) - expect (f ("b", "b")).to_be (false) - expect (f ("c", "b")).to_be (false) - - it supports __lt metamethods: - List = require "std.list" {} - expect (f (List {1, 2, 3}, List {1, 2, 3, 4})).to_be (true) - expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (false) - expect (f (List {1, 2, 4}, List {1, 2, 3})).to_be (false) - -- describe lte: - - before: - f = M.lte - - - it returns true if the arguments are not in descending order: - expect (f (1, 2)).to_be (true) - expect (f (2, 2)).to_be (true) - expect (f ("a", "b")).to_be (true) - expect (f ("b", "b")).to_be (true) - - it returns false if the arguments are in descending order: - expect (f (3, 2)).to_be (false) - expect (f ("c", "b")).to_be (false) - - it supports __lte metamethods: - List = require "std.list" {} - expect (f (List {1, 2, 3}, List {1, 2, 3, 4})).to_be (true) - expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (true) - expect (f (List {1, 2, 4}, List {1, 2, 3})).to_be (false) - -- describe gt: - - before: - f = M.gt - - - it returns true if the arguments are in descending order: - expect (f (2, 1)).to_be (true) - expect (f ("b", "a")).to_be (true) - - it returns false if the arguments are not in descending order: - expect (f (2, 2)).to_be (false) - expect (f (2, 3)).to_be (false) - expect (f ("b", "b")).to_be (false) - expect (f ("b", "c")).to_be (false) - - it supports __lt metamethods: - List = require "std.list" {} - expect (f (List {1, 2, 3, 4}, List {1, 2, 3})).to_be (true) - expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (false) - expect (f (List {1, 2, 3}, List {1, 2, 4})).to_be (false) - -- describe gte: - - before: - f = M.gte - - - it returns true if the arguments are not in ascending order: - expect (f (2, 1)).to_be (true) - expect (f (2, 2)).to_be (true) - expect (f ("b", "a")).to_be (true) - expect (f ("b", "b")).to_be (true) - - it returns false if the arguments are in ascending order: - expect (f (2, 3)).to_be (false) - expect (f ("b", "c")).to_be (false) - - it supports __lte metamethods: - List = require "std.list" {} - expect (f (List {1, 2, 3, 4}, List {1, 2, 3})).to_be (true) - expect (f (List {1, 2, 3}, List {1, 2, 3})).to_be (true) - expect (f (List {1, 2, 3}, List {1, 2, 4})).to_be (false) diff --git a/specs/specs.mk b/specs/specs.mk index 7cdb7b7..311fbb8 100644 --- a/specs/specs.mk +++ b/specs/specs.mk @@ -13,19 +13,16 @@ specl_SPECS = \ $(srcdir)/specs/container_spec.yaml \ $(srcdir)/specs/debug_spec.yaml \ - $(srcdir)/specs/functional_spec.yaml \ $(srcdir)/specs/io_spec.yaml \ $(srcdir)/specs/list_spec.yaml \ $(srcdir)/specs/math_spec.yaml \ $(srcdir)/specs/object_spec.yaml \ - $(srcdir)/specs/operator_spec.yaml \ $(srcdir)/specs/package_spec.yaml \ $(srcdir)/specs/set_spec.yaml \ $(srcdir)/specs/strbuf_spec.yaml \ $(srcdir)/specs/string_spec.yaml \ $(srcdir)/specs/table_spec.yaml \ $(srcdir)/specs/tree_spec.yaml \ - $(srcdir)/specs/tuple_spec.yaml \ $(srcdir)/specs/std_spec.yaml \ $(NOTHING_ELSE) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 754d47f..8478d7f 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -149,21 +149,12 @@ specify std: end - it scribbles backwards compatibility apis: for api, fn in pairs { - bind = M.functional.bind, - collect = M.functional.collect, - compose = M.functional.compose, - curry = M.functional.curry, die = M.io.die, - filter = M.functional.filter, - fold = M.functional.fold, - id = M.functional.id, ileaves = M.tree.ileaves, inodes = M.tree.inodes, leaves = M.tree.leaves, - map = M.functional.map, metamethod = M.getmetamethod, nodes = M.tree.nodes, - op = M.operator, pack = M.table.pack, pickle = M.string.pickle, prettytostring = M.string.prettytostring, diff --git a/specs/tuple_spec.yaml b/specs/tuple_spec.yaml deleted file mode 100644 index 629c255..0000000 --- a/specs/tuple_spec.yaml +++ /dev/null @@ -1,187 +0,0 @@ -before: - Tuple = require "std.tuple".prototype - - t0, t1, t2 = Tuple (), Tuple "one", Tuple (false, true) - - -specify std.tuple: -- describe require: - - it does not perturb the global namespace: - expect (show_apis {added_to="_G", by="std.tuple"}). - to_equal {} - - -- describe construction: - - it constructs a new tuple: - expect (t0).not_to_be (Tuple) - expect (objtype (t0)).to_be "Tuple" - - it initialises tuple with constructor parameters: - expect (objtype (t2)).to_be "Tuple" - expect (t2[1]).to_be (false) - expect (t2[2]).to_be (true) - - it understands nil valued elements: - t5 = Tuple (nil, nil, 1, nil, nil) - expect (t5[3]).to_be (1) - expect (t5[5]).to_be (nil) - - -- describe length: - - context with n field: - - it returns the number of elements: - expect (t0.n).to_be (0) - expect (t1.n).to_be (1) - expect (t2.n).to_be (2) - - it counts nil valued elements: - expect (Tuple (nil).n).to_be (1) - expect (Tuple (nil, false, nil, nil).n).to_be (4) - - - context with std.operator.len: - - before: - len = require "std.operator".len - - it returns the number of elements: - expect (len (t0)).to_be (0) - expect (len (t1)).to_be (1) - expect (len (t2)).to_be (2) - - it counts nil valued elements: - expect (len (Tuple (nil))).to_be (1) - expect (len (Tuple (nil, false, nil, nil))).to_be (4) - - -- describe indexing: - - it dereferences elements: - expect (t2[1]).to_be (false) - expect (t2[2]).to_be (true) - expect (t2.n).to_be (2) - - it returns nil-valued elements: - t3 = Tuple (nil, false, nil) - expect (t3[1]).to_be (nil) - expect (t3[2]).to_be (false) - expect (t3[3]).to_be (nil) - expect (t3.n).to_be (3) - - it returns nil for out-of-bound indices: - expect (t1[0]).to_be (nil) - expect (t1[1]).not_to_be (nil) - expect (t1[2]).to_be (nil) - expect (t1[-1]).to_be (nil) - expect (t1.foo).to_be (nil) - - -- describe interning: - - it interns all tuples: - expect (Tuple ()).to_be (Tuple ()) - expect (Tuple ("a", 2)).to_be (Tuple ("a", 2)) - - it interns nil valued elements: - expect (Tuple (nil)).to_be (Tuple (nil)) - expect (Tuple (nil, false, nil)).to_be (Tuple (nil, false, nil)) - expect (Tuple (nil)).not_to_be (Tuple (false)) - - it distinguishes nil from no elements: - expect (Tuple (nil)).not_to_be (Tuple ()) - - -- describe immutability: - - before: - fn = function (t, k, v) t[k] = v end - - - it diagnoses attempts to mutate contents: - expect (fn (t2, 1, 1)).to_raise "cannot change immutable tuple object" - - it diagnoses attempts to mutate methods: - expect (fn (t2, "unpack", nil)).to_raise "cannot change immutable tuple object" - - it diagnoses attempts to add new values: - expect (fn (t2, 0, "oob")).to_raise "cannot change immutable tuple object" - - -- describe traversing: - - before: - npairs = require "std".npairs - - pretty = function (t) - local r = {} - for i, v in npairs (t) do r[i] = tostring (v) end - return table.concat (r, ",") - end - - - it iterates over the elements: - expect (pretty (Tuple ("a", "b", "c"))).to_be "a,b,c" - - it works with 0-tuple: - expect (pretty (Tuple ())).to_be "" - - it understands nil elements: - expect (pretty (Tuple (nil))).to_be "nil" - expect (pretty (Tuple (false, nil))).to_be "false,nil" - expect (pretty (Tuple (nil, false))).to_be "nil,false" - expect (pretty (Tuple (nil, nil))).to_be "nil,nil" - - -- describe unpacking: - - context with module function: - - before: - unpack = require "std.tuple".unpack - - collect = function (t) - local r = {unpack (t)} - for i = 1, t.n do r[i] = tostring (r[i]) end - return table.concat (r, ",") - end - - - it returns all elements: - expect (collect (Tuple ("a", "b", "unpack"))).to_be "a,b,unpack" - - it works with 0-tuple: - expect (collect (Tuple ())).to_be "" - - it understands nil elements: - expect (collect (Tuple (nil))).to_be "nil" - expect (collect (Tuple (false, nil))).to_be "false,nil" - expect (collect (Tuple (nil, false))).to_be "nil,false" - expect (collect (Tuple (nil, nil))).to_be "nil,nil" - - - context with object method: - - before: - collect = function (t) - local r = {t:unpack ()} - for i = 1, t.n do r[i] = tostring (r[i]) end - return table.concat (r, ",") - end - - - it returns all elements: - expect (collect (Tuple ("a", "b", "unpack"))).to_be "a,b,unpack" - - it works with 0-tuple: - expect (collect (Tuple ())).to_be "" - - it understands nil elements: - expect (collect (Tuple (nil))).to_be "nil" - expect (collect (Tuple (false, nil))).to_be "false,nil" - expect (collect (Tuple (nil, false))).to_be "nil,false" - expect (collect (Tuple (nil, nil))).to_be "nil,nil" - - -- describe __tostring: - - it starts with the object type: - expect (tostring (Tuple ())).to_match "^Tuple " - - it contains all the elements: - elems = {"a", "b", "c"} - for _, e in ipairs (elems) do - expect (tostring (Tuple (unpack (elems)))).to_match (e) - end - - -- describe __pickle: - - before: - loadstring = loadstring or load - function unpickle (s) return loadstring ("return " .. s) () end - f = require "std.string".pickle - - things = Tuple (false, "str", 42) - - - it returns a string: - expect (type (f (things))).to_be "string" - - it propagates the '_module' metafield when '_type' is unchanged: - expect (getmetatable (Tuple (1))._module). - to_be (getmetatable (Tuple)._module) - - it requires the module path for later invocation: - expect (f (things)).to_be 'require "std.tuple".prototype (false,"str",42)' - - it roundtrips objects: - expect (unpickle (f (things))).to_equal (things) - - it converts a nested object to a representative string: - tuple = Tuple (Tuple ("?"), "!", 42) - expect (f (tuple)).to_be ('require "std.tuple".prototype ' .. - '(require "std.tuple".prototype ("?"),"!",42)') - - it roundtrips nested objects: - tuple = Tuple (Tuple ("?"), "!", 42) - expect (unpickle (f (tuple))).to_equal (tuple) From ba8532f96827aa8884ac341d36ac966ecfdf9e4f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 18:18:33 +0000 Subject: [PATCH 671/703] maint: bump version number to 42.0.0. Now that the current stdlib codebase no longer supports released Specl-14.1.4, make sure LuaRocks can tell this codebase apart from the compatible 14.1.2 release: * configure.ac (AC_INIT): Bump version number to 42.0.0. * stdlib-git-1.rockspec, .travis.yml: Regenerate. Signed-off-by: Gary V. Vaughan --- .travis.yml | 4 ++-- configure.ac | 2 +- stdlib-git-1.rockspec | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3f331b7..8de1f0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -124,8 +124,8 @@ install: # Build from rockspec, forcing uninstall of older luarocks installed # above when testing the git rockspec, both for enforcing backwards # compatibility by default, and for ease of maintenance. - - if test -f 'stdlib-41.2.0-1.rockspec'; then - sudo luarocks make 'stdlib-41.2.0-1.rockspec' LUA="$LUA"; + - if test -f 'stdlib-42.0.0-1.rockspec'; then + sudo luarocks make 'stdlib-42.0.0-1.rockspec' LUA="$LUA"; else sudo luarocks make --force 'stdlib-git-1.rockspec' LUA="$LUA"; fi diff --git a/configure.ac b/configure.ac index d642536..414cc84 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl along with this program. If not, see . dnl Initialise autoconf and automake -AC_INIT([stdlib], [41.2.0], [http://github.com/lua-stdlib/lua-stdlib/issues]) +AC_INIT([stdlib], [42.0.0], [http://github.com/lua-stdlib/lua-stdlib/issues]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index 6ec4919..7c70e16 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -1,7 +1,7 @@ package = "stdlib" version = "git-1" description = { - detailed = "stdlib is a library of modules for common programming tasks, including list, table and functional operations, objects, pickling, pretty-printing and command-line option parsing.", + detailed = "stdlib is a library of modules for common programming tasks, including list and table operations, objects, pickling and pretty-printing.", homepage = "http://lua-stdlib.github.io/lua-stdlib", license = "MIT/X11", summary = "General Lua Libraries", From ed4174ac122a22ff07b38a35b589e4bb48358594 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 18:24:56 +0000 Subject: [PATCH 672/703] travis: don't force uninstall stdlib that specl relies on! * .travis.yml (install): Don't force removal of stdlib rock that specl relies on! Signed-off-by: Gary V. Vaughan --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8de1f0a..85fff05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -127,7 +127,7 @@ install: - if test -f 'stdlib-42.0.0-1.rockspec'; then sudo luarocks make 'stdlib-42.0.0-1.rockspec' LUA="$LUA"; else - sudo luarocks make --force 'stdlib-git-1.rockspec' LUA="$LUA"; + sudo luarocks make 'stdlib-git-1.rockspec' LUA="$LUA"; fi # Clean up files created by root From 4b6968eca8d3eca409fc800e2d06977a8603e100 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 20:42:40 +0000 Subject: [PATCH 673/703] string: remove broken pickle implementation. This implementation is mostly useless. In preparation for something else much cleaner and more functional, delete the cruft for this broken implementation: * lib/std/base.lua (picklable, pickle_vtable, pickle): Remove broken implementation. * lib/std/string.lua (pickle): Remove re-export of std.base.pickle. * lib/std/container.lua (__pickle): Remove. * lib/std/list.lua, lib/std/object.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/tree.lua (__pickle): Likewise. * specs/container_spec.yaml, specs/list_spec.yaml, specs/object_spec.yaml, specs/set_spec.yaml, specs/std_spec.yaml, specs/strbuf_spec.yaml, specs/string_spec.yaml, specs/tree_spec.yaml: Adjust accordingly. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 10 ------ lib/std/base.lua | 52 ------------------------------ lib/std/container.lua | 58 ++------------------------------- lib/std/list.lua | 2 +- lib/std/object.lua | 2 +- lib/std/set.lua | 28 +--------------- lib/std/strbuf.lua | 2 +- lib/std/string.lua | 14 -------- lib/std/tree.lua | 2 +- specs/container_spec.yaml | 67 ++------------------------------------- specs/list_spec.yaml | 47 --------------------------- specs/object_spec.yaml | 47 --------------------------- specs/set_spec.yaml | 47 --------------------------- specs/std_spec.yaml | 3 +- specs/strbuf_spec.yaml | 47 --------------------------- specs/string_spec.yaml | 57 +-------------------------------- specs/tree_spec.yaml | 47 --------------------------- 17 files changed, 12 insertions(+), 520 deletions(-) diff --git a/NEWS.md b/NEWS.md index a554741..f949f92 100644 --- a/NEWS.md +++ b/NEWS.md @@ -73,14 +73,6 @@ more legible deeply nested table output, identically to previous releases. - - `std.string.pickle` uses the more powerful features of the improved - render function to return a `std.eval`able string that recreates an - equivalent object to the original argument more accurately than - before. - - - All of stdlib's object prototypes now provide a `__pickle` metamethod, - which makes them picklable with `std.string.pickle` too! - - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. - We used to have an object module method, `std.object.type`, which @@ -110,8 +102,6 @@ ### Bug fixes - - `std.string.pickle` works with nested tables, and mutable keys. - - `std.string.wrap` doesn't throw a StrBuf deprecation warning any more. - `std.getmetamethod` now returns functable valued metamethods diff --git a/lib/std/base.lua b/lib/std/base.lua index 62c3edd..03cafc4 100644 --- a/lib/std/base.lua +++ b/lib/std/base.lua @@ -387,57 +387,6 @@ local function sortkeys (t) end -local picklable = { - boolean = true, ["nil"] = true, number = true, string = true, -} - -local pickle_vtable = { - term = function (x) - local type_x = type (x) - if picklable[type_x] or getmetamethod (x, "__pickle") then - return true - elseif type (x) ~= "table" then - -- don't know what to do with this :( - error ("cannot pickle " .. tostring (x)) - end - end, - - elem = function (x) - -- math - if x ~= x then - return "0/0" - elseif x == math_huge then - return "math.huge" - elseif x == -math_huge then - return "-math.huge" - elseif x == nil then - return "nil" - end - - -- common types - local type_x = type (x) - if type_x == "string" then - return string_format ("%q", x) - elseif type_x == "number" or type_x == "boolean" then - return tostring (x) - end - - -- pickling metamethod - local __pickle = getmetamethod (x, "__pickle") - if __pickle then return __pickle (x) end - end, - - pair = function (x, kp, vp, k, v, kstr, vstr) - return "[" .. kstr .. "]=" .. vstr - end, -} - - -local function pickle (x) - return render (x, pickle_vtable) -end - - local function ripairs (t) local oob = 1 while t[oob] ~= nil do @@ -603,7 +552,6 @@ return { string = { escape_pattern = escape_pattern, - pickle = pickle, render = render, split = split, }, diff --git a/lib/std/container.lua b/lib/std/container.lua index be52202..a8b479d 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -35,8 +35,6 @@ local select = select local setmetatable = setmetatable local type = type -local string_find = string.find -local string_sub = string.sub local table_concat = table.concat @@ -50,7 +48,6 @@ local Module = _.std.object.Module local _DEBUG = _.debug_init._DEBUG local copy = _.std.base.copy local mapfields = _.std.object.mapfields -local pickle = _.std.string.pickle local render = _.std.string.render local sortkeys = _.std.base.sortkeys @@ -156,8 +153,7 @@ local tostring_vtable = { -- local g = Graph { "node1", "node2" } -- assert (nodes (g) == 2) local prototype = { - _module = "std.container", -- for pickle() - _type = "Container", -- for tostring() and type() + _type = "Container", --- Metamethods -- @section metamethods @@ -216,16 +212,6 @@ local prototype = { -- If a metatable was set, then merge our fields and use it. if next (getmetatable (obj) or {}) then - local new_mt = getmetatable (obj) - local new_type = new_mt._type or "" - local i = string_find ("." .. new_type, "%.[^%.]*$") - if i > 1 then - -- expand long-form type. - new_mt._type = string_sub (new_type, i) - new_mt._module = string_sub (new_type, 1, i -2) - end - - -- Merge fields. obj_mt = instantiate (mt, getmetatable (obj)) -- Merge object methods. @@ -234,11 +220,6 @@ local prototype = { then obj_mt.__index = instantiate (mt.__index, obj_mt.__index) end - - -- Invalidate obsoleted _module field - if new_mt._type ~= nil and new_mt._module == nil then - obj_mt._module = nil - end end return setmetatable (obj, obj_mt) @@ -262,42 +243,7 @@ local prototype = { return table_concat { -- Pass a shallow copy to render to avoid triggering __tostring -- again and blowing the stack. - getmetatable (self)._type, - " ", - render (copy (self), tostring_vtable), - } - end, - - - --- Return a loadable serialization of this object, where possible. - -- - -- If the object contains an unpicklable element (e.g. a userdata with - -- no `__pickle` metamethod) then neither is the entire container - -- picklable, and an error will be raised. - -- - -- Assuming the object metatable carries a correct `_module` field, - -- (either set manually when the prototype was created, or else because - -- the long form `_type` field was provided) that module path will be - -- required when the pickled object is evaluated. Otherwise, the bare - -- `_type` string is used and you will be responsible for setting that - -- to the correct object prototype before evaluating a pickled object. - -- @function prototype:__pickle - -- @treturn string pickled object representation - -- @see std.string.pickle - __pickle = function (self) - local mt = getmetatable (self) - if type (mt._module) == "string" then - -- object with _module set - return table_concat { - 'require "', - mt._module, - '".prototype ', - pickle (copy (self)), - } - end - -- rely on caller preloading `local ObjectName = require "obj".prototype` - return table_concat { - mt._type, " ", pickle (copy (self)), + getmetatable (self)._type, " ", render (copy (self), tostring_vtable), } end, } diff --git a/lib/std/list.lua b/lib/std/list.lua index 424cabd..1dc84fd 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -208,7 +208,7 @@ local methods = { -- local List = require "std.list".prototype -- assert (std.type (List) == "List") List = Object { - _type = "std.list.List", + _type = "List", --- Metamethods -- @section metamethods diff --git a/lib/std/object.lua b/lib/std/object.lua index 6536f6f..fde76c9 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -138,7 +138,7 @@ local methods = { -- command = pipeline[pid], -- manual assignment -- } local prototype = Container { - _type = "std.object.Object", + _type = "Object", --- Metamethods -- @section metamethods diff --git a/lib/std/set.lua b/lib/std/set.lua index 121c31a..3c48211 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -194,7 +194,7 @@ end -- local Set = require "std.set".prototype -- assert (std.type (Set) == "Set") prototype = Container { - _type = "std.set.Set", + _type = "Set", --- Set object initialisation. -- @@ -288,32 +288,6 @@ prototype = Container { table_sort (keys) return getmetatable (self)._type .. " {" .. table_concat (keys, ", ") .. "}" end, - - --- Return a loadable serialization of this object, where possible. - -- @function prototype:__pickle - -- @treturn string pickled object representation - -- @see std.string.pickle - __pickle = function (self) - local mt, keys = getmetatable (self), {} - for k in _pairs (self) do - keys[#keys + 1] = pickle (k) - end - table_sort (keys) - if type (mt._module) == "string" then - -- object with _module set - return table_concat { - 'require "', - mt._module, - '".prototype {', - table_concat (keys, ","), - "}", - } - end - -- rely on caller preloading `local ObjName = require "module".prototype` - return table_concat { - mt._type, " {", table_concat (keys, ","), "}" - } - end, } diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 91fad0b..99be646 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -133,7 +133,7 @@ local methods = { return Module { prototype = Object { - _type = "std.strbuf.StrBuf", + _type = "StrBuf", --- Metamethods -- @section metamethods diff --git a/lib/std/string.lua b/lib/std/string.lua index 01ca331..4a9dfdc 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -40,7 +40,6 @@ local copy = _.std.base.copy local escape_pattern = _.std.string.escape_pattern local len = _.std.operator.len local merge = _.std.base.merge -local pickle = _.std.string.pickle local render = _.std.string.render local sortkeys = _.std.base.sortkeys local split = _.std.string.split @@ -413,19 +412,6 @@ M = { -- @usage print (pad (trim (outputstr, 78)) .. "\n") pad = X ("pad (string, int, ?string)", pad), - --- Convert a value to a string. - -- The string can be passed to `std.eval` to retrieve the value. - -- Only primitives for which `tostring` returns an evalable result, - -- and objects with a `__pickle` metamethod are picklable. - -- @function pickle - -- @param x object to pickle - -- @treturn string reversible string rendering of *x* - -- @see std.eval - -- @usage - -- freeze = std.functional.memoize (pickle) - -- thaw = function (x) return std.eval (x) end - pickle = X ("pickle (?any)", pickle), - --- Pretty-print a table, or other object. -- @function prettytostring -- @param x object to convert to string diff --git a/lib/std/tree.lua b/lib/std/tree.lua index e36589b..1930284 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -234,7 +234,7 @@ end -- io.write (leaf .. "\t") -- end prototype = Container { - _type = "std.tree.Tree", + _type = "Tree", --- Metamethods -- @section metamethods diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 61590c8..7e76ef8 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -53,22 +53,15 @@ specify std.container: things = Container {foo="bar", _baz="quux"} expect (getmetatable (things)).not_to_be (getmetatable (Container)) expect (getmetatable (things)._baz).to_be "quux" - - it propagates '_type' and '_module' fields: + - it propagates '_type' field: things = Container {1} u, v = things {"u"}, things {"v"} expect (objtype (u)).to_be "Container" - expect (getmetatable (u)._module).to_be "std.container" expect (objtype (v)).to_be (objtype (Container)) - expect (getmetatable (v)._module). - to_be (getmetatable (Container)._module) - - it sets '_type' and '_module' from long prototype '_type' field: - things = Container {_type = "specs.example.Things"} - expect (objtype (things)).to_be "Things" - expect (getmetatable (things)._module).to_be "specs.example" - context with module functions: - before: Bag = require "std.base".object.Module { - prototype = Container { _type = "Bag", _module = "specs.example" }, + prototype = Container { _type = "Bag" }, count = function (bag) local n = 0 for _, m in pairs (bag) do n = n + m end @@ -90,18 +83,11 @@ specify std.container: - it does allow elements named after module functions: things = Bag { count = 1337 } expect (Bag.count (things)).to_be (1337) - - it propagates '_type' and '_module' fields: + - it propagates '_type' field: things = Bag { bananas=0 } u, v = things { bananas=1 }, things { coconuts=0 } expect (objtype (u)).to_be "Bag" - expect (getmetatable (u)._module).to_be "specs.example" expect (objtype (v)).to_be (objtype (Bag.prototype)) - expect (getmetatable (v)._module). - to_be (getmetatable (Bag.prototype)._module) - - it sets '_type' and '_module' from long prototype '_type' field: - things = Bag {_type = "specs.example.Things"} - expect (objtype (things)).to_be "Things" - expect (getmetatable (things)._module).to_be "specs.example" - describe field access: @@ -143,50 +129,3 @@ specify std.container: not_to_contain ";" expect (tostring (things {one = true, two = true, three = true})). to_contain ";" - - -- describe __pickle: - - before: - loadstring = loadstring or load - function unpickle (s) return loadstring ("return " .. s) () end - f = require "std.string".pickle - - Derived = Container {_type = "Derived"} - things = Derived {one="two", "three"} - - - it returns a string: - expect (type (f (things))).to_be "string" - - it resets '_module' metafield when '_type' changes: - expect (getmetatable (things)._module).to_be (nil) - - it supports setting '_module' and '_type' metafields: - Everything = Derived { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - Everything = Container { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - - it does not have a period in '_type': - Period = Derived { _type = "specs.example.Period" } - expect (string.find (type (Period), "%.")).to_be (nil) - - it propagates the '_module' metafield when '_type' is unchanged: - expect (getmetatable (Container {1})._module). - to_be (getmetatable (Container)._module) - - context with '_module' metafield: - - it requires the module path for later invocation: - expect (f (Container)).to_be 'require "std.container".prototype {}' - - it roundtrips objects: - expect (unpickle (f (Container))).to_equal (Container) - - context with '_type' metafield only: - - it uses the '_type' value for later invocation: - expect (f (things)).to_be 'Derived {[1]="three",["one"]="two"}' - - it roundtrips objects: - compat = require "specl.compat" - compat.setfenv (unpickle, compat.getfenv (1)) - expect (unpickle (f (things))).to_equal (things) - - it converts a nested object to a representative string: - container = Container {Derived {"?"}, ["!"]=42} - expect (f (container)). - to_be 'require "std.container".prototype {[1]=Derived {[1]="?"},["!"]=42}' - - it roundtrips nested objects: - container = Container {Derived {"?"}, ["!"]=42} - expect (unpickle (f (container))).to_equal (container) diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml index d7535dd..6a2c819 100644 --- a/specs/list_spec.yaml +++ b/specs/list_spec.yaml @@ -390,50 +390,3 @@ specify std.list: expect (f (List {})).to_equal (List {}) - it returns an empty List when passed a List with one element: expect (f (List {1})).to_equal (List {}) - - -- describe __pickle: - - before: - loadstring = loadstring or load - function unpickle (s) return loadstring ("return " .. s) () end - f = require "std.string".pickle - - Derived = List {_type = "Derived"} - things = Derived {false, "str", 42} - - - it returns a string: - expect (type (f (things))).to_be "string" - - it resets '_module' metafield when '_type' changes: - expect (getmetatable (things)._module).to_be (nil) - - it supports setting '_module' and '_type' metafields: - Everything = Derived { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - Everything = List { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - - it does not have a period in '_type': - Period = Derived { _type = "specs.example.Period" } - expect (string.find (type (Period), "%.")).to_be (nil) - - it propagates the '_module' metafield when '_type' is unchanged: - expect (getmetatable (List {1})._module). - to_be (getmetatable (List)._module) - - context with '_module' metafield: - - it requires the module path for later invocation: - expect (f (List)).to_be 'require "std.list".prototype {}' - - it roundtrips objects: - expect (unpickle (f (List))).to_equal (List) - - context with '_type' metafield only: - - it uses the '_type' value for later invocation: - expect (f (things)).to_be 'Derived {[1]=false,[2]="str",[3]=42}' - - it roundtrips objects: - compat = require "specl.compat" - compat.setfenv (unpickle, compat.getfenv (1)) - expect (unpickle (f (things))).to_equal (things) - - it converts a nested object to a representative string: - buf = List {Derived {"?"}, ["!"]=42} - expect (f (buf)). - to_be 'require "std.list".prototype {[1]=Derived {[1]="?"},["!"]=42}' - - it roundtrips nested objects: - buf = List {Derived {"?"}, ["!"]=42} - expect (unpickle (f (buf))).to_equal (buf) diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml index 3d301f2..c6344a4 100644 --- a/specs/object_spec.yaml +++ b/specs/object_spec.yaml @@ -293,50 +293,3 @@ specify std.object: not_to_contain ";" expect (tostring (o {one = true, two = true, three = true})). to_contain ";" - - -- describe __pickle: - - before: - loadstring = loadstring or load - function unpickle (s) return loadstring ("return " .. s) () end - f = require "std.string".pickle - - Derived = Object {_type = "Derived"} - things = Derived {one="two", "three"} - - - it returns a string: - expect (type (f (things))).to_be "string" - - it resets '_module' metafield when '_type' changes: - expect (getmetatable (things)._module).to_be (nil) - - it supports setting '_module' and '_type' metafields: - Everything = Derived { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - Everything = Object { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - - it does not have a period in '_type': - Period = Derived { _type = "specs.example.Period" } - expect (string.find (type (Period), "%.")).to_be (nil) - - it propagates the '_module' metafield when '_type' is unchanged: - expect (getmetatable (Object {1})._module). - to_be (getmetatable (Object)._module) - - context with '_module' metafield: - - it requires the module path for later invocation: - expect (f (Object)).to_be 'require "std.object".prototype {}' - - it roundtrips objects: - expect (unpickle (f (Object))).to_equal (Object) - - context with '_type' metafield only: - - it uses the '_type' value for later invocation: - expect (f (things)).to_be 'Derived {[1]="three",["one"]="two"}' - - it roundtrips objects: - compat = require "specl.compat" - compat.setfenv (unpickle, compat.getfenv (1)) - expect (unpickle (f (things))).to_equal (things) - - it converts a nested object to a representative string: - object = Object {Derived {"?"}, ["!"]=42} - expect (f (object)). - to_be 'require "std.object".prototype {[1]=Derived {[1]="?"},["!"]=42}' - - it roundtrips nested objects: - object = Object {Derived {"?"}, ["!"]=42} - expect (unpickle (f (object))).to_equal (object) diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml index 9e298b7..48f981f 100644 --- a/specs/set_spec.yaml +++ b/specs/set_spec.yaml @@ -305,50 +305,3 @@ specify std.set: expect (tostring (s)).to_contain "Set" - it contains the ordered set elements: expect (tostring (s)).to_contain "bar, baz, foo" - - -- describe __pickle: - - before: - loadstring = loadstring or load - function unpickle (s) return loadstring ("return " .. s) () end - f = require "std.string".pickle - - Derived = Set {_type = "Derived"} - things = Derived {false, "str", 42} - - - it returns a string: - expect (type (f (things))).to_be "string" - - it resets '_module' metafield when '_type' changes: - expect (getmetatable (things)._module).to_be (nil) - - it supports setting '_module' and '_type' metafields: - Everything = Derived { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - Everything = Set { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - - it does not have a period in '_type': - Period = Derived { _type = "specs.example.Period" } - expect (string.find (type (Period), "%.")).to_be (nil) - - it propagates the '_module' metafield when '_type' is unchanged: - expect (getmetatable (Set {1})._module). - to_be (getmetatable (Set)._module) - - context with '_module' metafield: - - it requires the module path for later invocation: - expect (f (Set {1})).to_be 'require "std.set".prototype {1}' - - it roundtrips objects: - expect (unpickle (f (Set))).to_equal (Set) - - context with '_type' metafield only: - - it uses the '_type' value for later invocation: - expect (f (things)).to_be 'Derived {"str",42,false}' - - it roundtrips objects: - compat = require "specl.compat" - compat.setfenv (unpickle, compat.getfenv (1)) - expect (unpickle (f (things))).to_equal (things) - - it converts a nested object to a representative string: - set = Set {Derived {"?"}, "!"} - expect (f (set)). - to_be 'require "std.set".prototype {"!",Derived {"?"}}' - - it roundtrips nested objects: - set = Set {Derived {"?"}, "!"} - expect (unpickle (f (set))).to_equal (set) diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 8478d7f..cfcd389 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -133,7 +133,7 @@ specify std: for _, api in ipairs { "__concat", "__index", "caps", "chomp", "escape_pattern", "escape_shell", "finds", "format", "ltrim", - "monkey_patch", "numbertosi", "ordinal_suffix", "pad", "pickle", + "monkey_patch", "numbertosi", "ordinal_suffix", "pad", "prettytostring", "render", "rtrim", "split", "tfind", "trim", "wrap" } do @@ -156,7 +156,6 @@ specify std: metamethod = M.getmetamethod, nodes = M.tree.nodes, pack = M.table.pack, - pickle = M.string.pickle, prettytostring = M.string.prettytostring, render = M.string.render, require_version = M.require, diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml index fc2afa7..6c07d12 100644 --- a/specs/strbuf_spec.yaml +++ b/specs/strbuf_spec.yaml @@ -98,50 +98,3 @@ specify std.strbuf: a = StrBuf {1} b = a {} .. 2 expect (tostring (a)).to_be "1" - - -- describe __pickle: - - before: - loadstring = loadstring or load - function unpickle (s) return loadstring ("return " .. s) () end - f = require "std.string".pickle - - Derived = StrBuf {_type = "Derived"} - things = Derived {false, "str", 42} - - - it returns a string: - expect (type (f (things))).to_be "string" - - it resets '_module' metafield when '_type' changes: - expect (getmetatable (things)._module).to_be (nil) - - it supports setting '_module' and '_type' metafields: - Everything = Derived { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - Everything = StrBuf { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - - it does not have a period in '_type': - Period = Derived { _type = "specs.example.Period" } - expect (string.find (type (Period), "%.")).to_be (nil) - - it propagates the '_module' metafield when '_type' is unchanged: - expect (getmetatable (StrBuf {1})._module). - to_be (getmetatable (StrBuf)._module) - - context with '_module' metafield: - - it requires the module path for later invocation: - expect (f (StrBuf)).to_be 'require "std.strbuf".prototype {}' - - it roundtrips objects: - expect (unpickle (f (StrBuf))).to_equal (StrBuf) - - context with '_type' metafield only: - - it uses the '_type' value for later invocation: - expect (f (things)).to_be 'Derived {[1]=false,[2]="str",[3]=42}' - - it roundtrips objects: - compat = require "specl.compat" - compat.setfenv (unpickle, compat.getfenv (1)) - expect (unpickle (f (things))).to_equal (things) - - it converts a nested object to a representative string: - buf = StrBuf {Derived {"?"}, ["!"]=42} - expect (f (buf)). - to_be 'require "std.strbuf".prototype {[1]=Derived {[1]="?"},["!"]=42}' - - it roundtrips nested objects: - buf = StrBuf {Derived {"?"}, ["!"]=42} - expect (unpickle (f (buf))).to_equal (buf) diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 8a8d387..38ede91 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -6,7 +6,7 @@ before: extend_base = { "__concat", "__index", "caps", "chomp", "escape_pattern", "escape_shell", "finds", "format", "ltrim", "monkey_patch", - "numbertosi", "ordinal_suffix", "pad", "pickle", + "numbertosi", "ordinal_suffix", "pad", "prettytostring", "render", "rtrim", "split", "tfind", "trim", "wrap" } @@ -344,61 +344,6 @@ specify std.string: expect (subject).to_be (original) -- describe pickle: - - before: - loadstring = loadstring or load - function unpickle (s) return loadstring ("return " .. s) () end - seq = {1, {{2, 3}, 4, {5}}} - hash = {foo={[{5,42}]={bar=0}}} - f = M.pickle - - - context with bad arguments: - badargs.diagnose (f, "std.string.pickle (?any)") - - - it diagnoses unpicklable arguments: - expect (f (function () end)).to_error "cannot pickle function" - - it converts a primitive to a representative string: - expect (f (nil)).to_be "nil" - expect (f (false)).to_be "false" - expect (f (42)).to_be "42" - expect (f "string").to_be '"string"' - - it converts infinities to a loadable string: - expect (unpickle (f (math.huge))).to_be (math.huge) - expect (unpickle (f (-math.huge))).to_be (-math.huge) - - it converts nan values to a loadable string: - expect (f (0/0)).to_be "0/0" - nan = unpickle (f (0/0)) - expect (type (nan)).to_be "number" - expect (nan).not_to_equal (nan) - - it round trips primitive values: - expect (unpickle (f (nil))).to_be (nil) - expect (unpickle (f (false))).to_be (false) - expect (unpickle (f (42))).to_be (42) - expect (unpickle (f "string")).to_be "string" - - it converts a sequence to a representative string: - expect (f {"sequence", 42}).to_be '{[1]="sequence",[2]=42}' - - it roundtrips sequences: - expect (unpickle (f {"sequence", 42})).to_equal {"sequence", 42} - - it converts a nested sequence to a representative string: - expect (f (seq)). - to_be "{[1]=1,[2]={[1]={[1]=2,[2]=3},[2]=4,[3]={[1]=5}}}" - - it roundtrips a nested sequence: - expect (unpickle (f (seq))).to_equal (seq) - - it converts a hash to a representative string: - expect (f {hash=42}).to_be '{["hash"]=42}' - - it roundtrips a hash: - expect (unpickle (f {hash=42})).to_equal {hash=42} - # there's no guarantee of ordering... - - it roundtrips a mixed table: - t = {"one", foo=3, 2} - expect (unpickle (f (t))).to_equal (t) - - it converts a nested hash to a representative string: - expect (f (hash)). - to_be '{["foo"]={[{[1]=5,[2]=42}]={["bar"]=0}}}' - - it roundtrips a nested hash: - expect (unpickle (f (hash))).to_equal (hash) - - - describe prettytostring: - before: f = M.prettytostring diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml index e7ed66c..71e833f 100644 --- a/specs/tree_spec.yaml +++ b/specs/tree_spec.yaml @@ -420,50 +420,3 @@ specify std.tree: quux = "quux"} expect (tostring (tr)). to_contain 'fnord=Tree {branch=Tree {bar=bar, baz=baz}}, foo=foo, quux=quux' - - -- describe __pickle: - - before: - loadstring = loadstring or load - function unpickle (s) return loadstring ("return " .. s) () end - f = require "std.string".pickle - - Derived = Tree {_type = "Derived"} - things = Derived {false, "str", 42} - - - it returns a string: - expect (type (f (things))).to_be "string" - - it resets '_module' metafield when '_type' changes: - expect (getmetatable (things)._module).to_be (nil) - - it supports setting '_module' and '_type' metafields: - Everything = Derived { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - Everything = Tree { _type = "Everything", _module = "nearly" } - expect (objtype (Everything)).to_be "Everything" - expect (getmetatable (Everything)._module).to_be "nearly" - - it does not have a period in '_type': - Period = Derived { _type = "specs.example.Period" } - expect (string.find (type (Period), "%.")).to_be (nil) - - it propagates the '_module' metafield when '_type' is unchanged: - expect (getmetatable (Tree {1})._module). - to_be (getmetatable (Tree)._module) - - context with '_module' metafield: - - it requires the module path for later invocation: - expect (f (Tree)).to_be 'require "std.tree".prototype {}' - - it roundtrips objects: - expect (unpickle (f (Tree))).to_equal (Tree) - - context with '_type' metafield only: - - it uses the '_type' value for later invocation: - expect (f (things)).to_be 'Derived {[1]=false,[2]="str",[3]=42}' - - it roundtrips objects: - compat = require "specl.compat" - compat.setfenv (unpickle, compat.getfenv (1)) - expect (unpickle (f (things))).to_equal (things) - - it converts a nested object to a representative string: - buf = Tree {Derived {"?"}, ["!"]=42} - expect (f (buf)). - to_be 'require "std.tree".prototype {[1]=Derived {[1]="?"},["!"]=42}' - - it roundtrips nested objects: - buf = Tree {Derived {"?"}, ["!"]=42} - expect (unpickle (f (buf))).to_equal (buf) From fcea7adaf4844b2d877d3f659d28b28111d5ddf5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 21:04:16 +0000 Subject: [PATCH 674/703] maint: remove monkey_patch functions. * lib/std/init.lua (barrel, monkey_patch): Remove. * lib/std/io.lua, lib/std/math.lua, lib/std/string.lua, lib/std/table.lua (monkey_patch): Remove. * specs/io_spec.yaml, specs/math_spec.yaml, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Remove examples accordingly. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 13 ++-- lib/std/init.lua | 85 ++------------------------ lib/std/io.lua | 34 +---------- lib/std/math.lua | 19 ------ lib/std/string.lua | 28 +-------- lib/std/table.lua | 23 +------ specs/io_spec.yaml | 35 +---------- specs/math_spec.yaml | 22 +------ specs/std_spec.yaml | 132 +---------------------------------------- specs/string_spec.yaml | 27 +-------- specs/table_spec.yaml | 22 +------ 11 files changed, 20 insertions(+), 420 deletions(-) diff --git a/NEWS.md b/NEWS.md index f949f92..03df22d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -121,6 +121,11 @@ `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` @@ -131,19 +136,13 @@ removed. At some point these will resurface in a new standalone package. - - Deprecated multi-argument `functional.bind` has been removed. - - Deprecated methods `list:depair`, `list:elems`, `list:enpair`, `list:filter`, `list:flatten`, `list:foldl`, `list:foldr`, `list:index_key`, `list:index_value`, `list:map`, `list:map_with`, `list:project`, `list:relems`, `list:reverse`, `list:shape`, `list:transpose` and `list:zip_with` have been removed. - - Deprecated functions `functional.eval`, `functional.fold`, - `functional.op["[]"]`, `functional.op["+"]`, `functional.op["-"]`, - `functional.op["*"]`, `functional.op["/"], `functional.op["and"]', - `functional.op["or"]`, `functional.op["not"]`, `functional.op["=="]`, - `functional.op["~="]`, `list.depair`, `list.elems`, `list.enpair`, + - Deprecated functions `list.depair`, `list.elems`, `list.enpair`, `list.filter`, `list.flatten`, `list.foldl`, `list.foldr`, `list.index_key`, `list.index_value`, `list.map`, `list.map_with`, `list.project`, `list.relems`, `list.reverse`, `list.shape`, diff --git a/lib/std/init.lua b/lib/std/init.lua index 06f875c..19aad44 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -1,16 +1,10 @@ --[[-- Enhanced Lua core functions, and others. - After requiring this module, simply referencing symbols in the - submodule hierarchy will load the necessary modules on demand. - - By default there are no changes to any global symbols, or monkey - patching of core module tables and metatables. However, sometimes it's - still convenient to do that: For example, when using stdlib from the - REPL, or in a prototype where you want to throw caution to the wind and - compatibility with other modules be damned. In that case, you can give - stdlib permission to scribble all over your namespaces by using the - various `monkey_patch` calls in the library. + 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 @@ -21,7 +15,6 @@ local _ENV = _ENV -local _G = _G local error = error local ipairs = ipairs local pairs = pairs @@ -87,13 +80,7 @@ _ = nil --[[ =============== ]]-- -local M, monkeys - - -local function monkey_patch (namespace) - copy (namespace or _G, monkeys) - return M -end +local M local function _assert (expect, fmt, arg1, ...) @@ -102,37 +89,6 @@ local function _assert (expect, fmt, arg1, ...) end -local function barrel (namespace) - namespace = namespace or _G - - -- Older releases installed the following into _G by default. - for _, name in pairs { - "io.die", "io.warn", - - "string.pickle", "string.prettytostring", "string.render", - - "table.pack", - - "tree.ileaves", "tree.inodes", "tree.leaves", "tree.nodes", - } do - local module, method = name:match "^(.*)%.(.-)$" - namespace[method] = M[module][method] - end - - -- Support old api names, for backwards compatibility. - namespace.metamethod = M.getmetamethod - namespace.op = M.operator - namespace.require_version = M.require - - require "std.io".monkey_patch (namespace) - require "std.math".monkey_patch (namespace) - require "std.string".monkey_patch (namespace) - require "std.table".monkey_patch (namespace) - - return monkey_patch (namespace) -end - - local function elems (t) -- capture _pairs iterator initial state local fn, istate, ctrl = _pairs (t) @@ -255,29 +211,6 @@ M = { --- Module Functions -- @section modulefuncs - --- A [barrel of monkey_patches](http://dictionary.reference.com/browse/barrel+of+monkeys). - -- - -- Apply **all** of stdlib's `monkey_patch` functions to *namespace*. - -- - -- Additionally, for backwards compatibility only, write an historical - -- selection of stdlib submodule functions into the given namespace too - -- (at least until the next major release). - -- @function barrel - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table module table - -- @usage local std = require "std".barrel () - barrel = X ("barrel (?table)", barrel), - - --- Overwrite core methods and metamethods with `std` enhanced versions. - -- - -- Write all functions from this module, except `std.barrel` and - -- `std.monkey_patch`, into *namespace*. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage local std = require "std".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), - --- Enhance core `require` to assert version number compatibility. -- By default match against the last substring of (dot-delimited) -- digits in the module version string. @@ -422,14 +355,6 @@ M = { } -monkeys = copy ({}, M) - --- Don't monkey_patch these apis into _G! -for _, api in ipairs {"barrel", "monkey_patch", "version"} do - monkeys[api] = nil -end - - --- Metamethods -- @section Metamethods diff --git a/lib/std/io.lua b/lib/std/io.lua index d6c7180..8cfc16a 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -43,7 +43,6 @@ local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _tostring = _.std.tostring local catfile = _.std.io.catfile -local copy = _.std.base.copy local dirsep = _.std.package.dirsep local leaves = _.std.tree.leaves local len = _.std.operator.len @@ -85,7 +84,7 @@ _ = nil --[[ =============== ]]-- -local M, monkeys +local M local function input_handle (h) @@ -134,21 +133,6 @@ local function writelines (h, ...) end -local function monkey_patch (namespace) - namespace = namespace or _G - namespace.io = copy (namespace.io or {}, monkeys) - - if namespace.io.stdin then - local mt = getmetatable (namespace.io.stdin) or {} - mt.readlines = M.readlines - mt.writelines = M.writelines - setmetatable (namespace.io.stdin, mt) - end - - return M -end - - local function process_files (fn) -- N.B. "arg" below refers to the global array of command-line args if len (arg) == 0 then @@ -284,19 +268,6 @@ M = { function (path) return split (path, dirsep) end), - --- Module Functions - -- @section modulefuncs - - --- Overwrite core `io` methods with `std` enhanced versions. - -- - -- Also adds @{readlines} and @{writelines} metamethods to core file objects. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the `std.io` module table - -- @usage local io = require "std.io".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), - - --- IO Functions -- @section iofuncs @@ -351,9 +322,6 @@ M = { } -monkeys = copy ({}, M) -- before deprecations and core merge - - return merge (M, io) diff --git a/lib/std/math.lua b/lib/std/math.lua index 2e218e7..673bb20 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -22,7 +22,6 @@ local _ = { } local _DEBUG = _.debug_init._DEBUG -local copy = _.std.base.copy local merge = _.std.base.merge @@ -69,13 +68,6 @@ local function floor (n, p) end -local function monkey_patch (namespace) - namespace = namespace or _G - namespace.math = copy (namespace.math or {}, M) - return M -end - - local function round (n, p) local e = 10 ^ (p or 0) return math_floor (n * e + 0.5) / e @@ -112,17 +104,6 @@ M = { -- @treturn number `n` rounded to `p` decimal places -- @usage roughly = round (exactly, 2) round = X ("round (number, ?int)", round), - - - --- Module Functions - -- @section modulefuncs - - --- Overwrite core `math` methods with `std` enhanced versions. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage require "std.math".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), } diff --git a/lib/std/string.lua b/lib/std/string.lua index 4a9dfdc..95ab2e9 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -124,18 +124,6 @@ local function finds (s, p, i, ...) end -local function monkey_patch (namespace) - namespace = namespace or _G - namespace.string = copy (namespace.string or {}, M) - - local string_metatable = getmetatable "" - string_metatable.__concat = M.__concat - string_metatable.__index = M.__index - - return M -end - - local function caps (s) return (s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end)) end @@ -302,7 +290,7 @@ M = { -- @param o object to stringify and concatenate -- @return s .. tostring (o) -- @usage - -- local string = require "std.string".monkey_patch () + -- local string = setmetatable ("", require "std.string") -- concatenated = "foo" .. {"bar"} __concat = __concat, @@ -492,20 +480,6 @@ M = { -- @usage -- print (wrap (copyright, 72, 4)) wrap = X ("wrap (string, ?int, ?int, ?int)", wrap), - - - --- Module Functions - -- @section modulefuncs - - --- Overwrite core `string` methods with `std` enhanced versions. - -- - -- Also adds auto-stringification to `..` operator on core strings, and - -- integer indexing of strings with `[]` dereferencing. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage local string = require "std.string".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), } diff --git a/lib/std/table.lua b/lib/std/table.lua index 01b1ace..6427103 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -75,7 +75,7 @@ _ = nil --[[ =============== ]]-- -local M, monkeys +local M local function merge_allfields (t, u, map, nometa) @@ -180,13 +180,6 @@ local function sort (t, c) end -local function monkey_patch (namespace) - namespace = namespace or _G - namespace.table = copy (namespace.table or {}, monkeys) - return M -end - - local _remove = table.remove local function remove (t, pos) @@ -444,23 +437,9 @@ M = { -- @usage merge_select (_G, require "std.debug", {"say"}, false) merge_select = X ("merge_select (table, table, [table], ?boolean|:nometa)", merge_namedfields), - - - --- Module Functions - -- @section modulefuncs - - --- Overwrite core `table` methods with `std` enhanced versions. - -- @function monkey_patch - -- @tparam[opt=_G] table namespace where to install global functions - -- @treturn table the module table - -- @usage local table = require "std.table".monkey_patch () - monkey_patch = X ("monkey_patch (?table)", monkey_patch), } -monkeys = copy ({}, M) -- before deprecations and core merge - - return merge (M, table) diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index e9399a0..6b43b76 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -3,7 +3,7 @@ before: | this_module = "std.io" global_table = "_G" - extend_base = { "catdir", "catfile", "die", "dirname", "monkey_patch", + extend_base = { "catdir", "catfile", "die", "dirname", "process_files", "readlines", "shell", "slurp", "splitdir", "warn", "writelines" } @@ -144,39 +144,6 @@ specify std.io: expect (f (path)).to_be (table.concat ({"", "one", "two"}, dirsep)) -- describe monkey_patch: - - before: - namespace = {} - - f = M.monkey_patch - - - context with bad arguments: - badargs.diagnose (f, "std.io.monkey_patch (?table)") - - # Ideally, `.to_be (M)`, except that M is cloned from a nested context - # by Specl to prevent us from affecting any other examples, thus the - # address is different by now. - - it returns std.io module table: - expect (f {}).to_equal (M) - - it injects std.io apis into the given namespace: - namespace = {} - f (namespace) - for _, api in ipairs (extend_base) do - expect (namespace.io[api]).to_be (M[api]) - end - - it installs file methods: - mt = { "file metatable" } - io = f { - io = { - stdin = setmetatable ({ "stdin" }, mt), - stdout = setmetatable ({ "stdout" }, mt), - stderr = setmetatable ({ "stderr" }, mt) - } - } - expect (mt.readlines).to_be (M.readlines) - expect (mt.writelines).to_be (M.writelines) - - - describe process_files: - before: name = "Makefile" diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index f198877..a90d65f 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -3,7 +3,7 @@ before: this_module = "std.math" global_table = "_G" - extend_base = { "floor", "monkey_patch", "round" } + extend_base = { "floor", "round" } M = require (this_module) @@ -52,26 +52,6 @@ specify std.math: expect (f (99999e-5, 3)).to_be (999e-3) -- describe monkey_patch: - - before: - f = M.monkey_patch - - - context with bad arguments: - badargs.diagnose (f, "std.math.monkey_patch (?table)") - - # Ideally, `.to_be (M)`, except that M is cloned from a nested context - # by Specl to prevent us from affecting any other examples, thus the - # address is different by now. - - it returns std.math module table: - expect (f {}).to_equal (M) - - it injects std.math apis into the given namespace: - namespace = {} - f (namespace) - for _, api in ipairs (extend_base) do - expect (namespace.math[api]).to_be (M[api]) - end - - - describe round: - before: f = M.round diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index cfcd389..841b52d 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -2,8 +2,8 @@ before: | this_module = "std" global_table = "_G" - exported_apis = { "assert", "barrel", "elems", "eval", "getmetamethod", - "ielems", "ipairs", "monkey_patch", "npairs", "pairs", + exported_apis = { "assert", "elems", "eval", "getmetamethod", + "ielems", "ipairs", "npairs", "pairs", "require", "ripairs", "rnpairs", "tostring" } -- Tables with iterator metamethods used by various examples. @@ -81,101 +81,6 @@ specify std: to_raise (string.format ("%s %d: %q", "here", 42, "a string")) -- describe barrel: - - before: - mt = { "file metatable" } - namespace = { - io = { - stdin = setmetatable ({}, mt), - stdout = setmetatable ({}, mt), - stderr = setmetatable ({}, mt), - }, - } - - f = M.barrel - - f (namespace) - - - context with bad arguments: - badargs.diagnose (f, "std.barrel (?table)") - - # Ideally, `.to_be (M)`, except that M is cloned from a nested context - # by Specl to prevent us from affecting any other examples, thus the - # address is different by now. - #- it returns std module table: - # expect (f (namespace)).to_equal (M) - - it installs std monkey patches: - for _, api in ipairs (exported_apis) do - if type (M[api]) == "function" and - api ~= "barrel" and api ~= "monkey_patch" - then - expect (namespace[api]).to_be (M[api]) - end - end - - it installs std.io monkey patches: - for _, api in ipairs { "catdir", "catfile", "die", "monkey_patch", - "process_files", "readlines", "shell", "slurp", "splitdir", "warn", - "writelines" } - do - expect (namespace.io[api]).to_be (M.io[api]) - end - expect (mt.readlines).to_be (M.io.readlines) - expect (mt.writelines).to_be (M.io.writelines) - - it installs std.math monkey patches: - for _, api in ipairs { "floor", "monkey_patch", "round" } do - expect (namespace.math[api]).to_be (M.math[api]) - end - - it installs std.string monkey patches: - # FIXME: string metatable monkey-patches leak out! - mt = getmetatable "" - expect (mt.__concat).to_be (M.string.__concat) - expect (mt.__index).to_be (M.string.__index) - - for _, api in ipairs { "__concat", "__index", "caps", "chomp", - "escape_pattern", "escape_shell", "finds", "format", "ltrim", - "monkey_patch", "numbertosi", "ordinal_suffix", "pad", - "prettytostring", "render", "rtrim", "split", "tfind", "trim", - "wrap" } - do - expect (namespace.string[api]).to_be (M.string[api]) - end - - it installs std.table monkey patches: - for _, api in ipairs { "clone", "clone_select", "depair", "empty", - "enpair", "insert", "invert", "keys", "maxn", "merge", - "merge_select", "monkey_patch", "new", "pack", "project", - "size", "sort", "values" } - do - expect (namespace.table[api]).to_be (M.table[api]) - end - - it scribbles backwards compatibility apis: - for api, fn in pairs { - die = M.io.die, - ileaves = M.tree.ileaves, - inodes = M.tree.inodes, - leaves = M.tree.leaves, - metamethod = M.getmetamethod, - nodes = M.tree.nodes, - pack = M.table.pack, - prettytostring = M.string.prettytostring, - render = M.string.render, - require_version = M.require, - warn = M.io.warn, - } do - expect (namespace[api]).to_be (fn) - end - - 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.strbuf - expect (lazy).to_be (require "std.strbuf") - - it loads submodule functions on demand: - expect (M.object.type (M.strbuf {"Lazy"})). - to_be "StrBuf" - - - describe elems: - before: f = M.elems @@ -313,39 +218,6 @@ specify std: expect (t).to_equal {} -- describe monkey_patch: - - before: - io_mt = {} - t = { - io = { - stdin = setmetatable ({}, io_mt), - stdout = setmetatable ({}, io_mt), - stderr = setmetatable ({}, io_mt), - }, - math = {}, - table = {}, - } - - f = M.monkey_patch - - f (t) - - - context with bad arguments: - badargs.diagnose (f, "std.monkey_patch (?table)") - - # Ideally, `.to_be (M)`, except that M is cloned from a nested context - # by Specl to prevent us from affecting any other examples, thus the - # address is different by now. - - it returns std module table: - expect (f (t)).to_equal (M) - - it installs std module functions: - for _, v in ipairs (exported_apis) do - if type (M[v]) == "function" and v ~= "barrel" and v ~= "monkey_patch" then - expect (t[v]).to_be (M[v]) - end - end - - - describe npairs: - before: f = M.npairs diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 38ede91..b3f4f3e 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -5,7 +5,7 @@ before: extend_base = { "__concat", "__index", "caps", "chomp", "escape_pattern", "escape_shell", - "finds", "format", "ltrim", "monkey_patch", + "finds", "format", "ltrim", "numbertosi", "ordinal_suffix", "pad", "prettytostring", "render", "rtrim", "split", "tfind", "trim", "wrap" } @@ -232,31 +232,6 @@ specify std.string: expect (subject).to_be (original) -- describe monkey_patch: - - before: - f = M.monkey_patch - - - context with bad arguments: - badargs.diagnose (f, "std.string.monkey_patch (?table)") - - # Ideally, `.to_be (M)`, except that M is cloned from a nested context - # by Specl to prevent us from affecting any other examples, thus the - # address is different by now. - - it returns std.string module table: - expect (f {}).to_equal (M) - - it injects std.string apis into given namespace: - namespace = {} - f (namespace) - for _, api in ipairs (extend_base) do - expect (namespace.string[api]).to_be (M[api]) - end - - it installs string metamethods: - # FIXME: string metatable monkey-patches leak out! - mt = getmetatable "" - expect (mt.__concat).to_be (M.__concat) - expect (mt.__index).to_be (M.__index) - - - describe numbertosi: - before: f = M.numbertosi diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index edb9535..d513208 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -5,7 +5,7 @@ before: | extend_base = { "clone", "clone_select", "depair", "empty", "enpair", "insert", "invert", "keys", "maxn", - "merge", "merge_select", "monkey_patch", "new", + "merge", "merge_select", "new", "pack", "project", "remove", "size", "sort", "unpack", "values" } @@ -384,26 +384,6 @@ specify std.table: expect (getmetatable (f ({}, t1mt, {"k1"}, ":nometa"))).to_be (nil) -- describe monkey_patch: - - before: - f = M.monkey_patch - - - context with bad arguments: - badargs.diagnose (f, "std.table.monkey_patch (?table)") - - # Ideally, `.to_be (M)`, except that M is cloned from a nested context - # by Specl to prevent us from affecting any other examples, thus the - # address is different by now. - - it returns std.table module table: - expect (f {}).to_equal (M) - - it injects std.table apis into given namespace: - namespace = {} - f (namespace) - for _, api in ipairs (extend_base) do - expect (namespace.table[api]).to_be (M[api]) - end - - - describe new: - before: f = M.new From b873e428336e0e9c9bd05850cea92a1c473c9a71 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 21:59:28 +0000 Subject: [PATCH 675/703] configury: remove in favour of LuaRocks builtin build type. * .prev-version, .slackid, .x-update-copyright, bootstrap, bootstrap.conf, build-aux/sanity-cfg.mk, configure.ac, local.mk, rockspec.conf, slingshot, specs/specs.mk: Delete. * AUTHORS, COPYING, HACKING, build-aux/config.ld.in: Move from here... * AUTHORS.md, LICENSE.md, STYLE.md, doc/config.ld.in: ...to here. * README.md: Update. * specs/io_spec.yaml: Fix example that checked `ls` output in current directory now that those files are gone. * .gitignore, .travis.yml, stdlib-git-1.rockspec: Update accordingly. Signed-off-by: Gary V. Vaughan --- .gitignore | 24 +- .prev-version | 1 - .slackid | 1 - .travis.yml | 38 +- .x-update-copyright | 1 - AUTHORS => AUTHORS.md | 14 +- COPYING => LICENSE.md | 0 Makefile | 41 + README.md | 55 +- HACKING => STYLE.md | 15 +- bootstrap | 5796 ------------------------------- bootstrap.conf | 72 - build-aux/sanity-cfg.mk | 3 - configure.ac | 40 - {build-aux => doc}/config.ld.in | 4 +- lib/std/version.lua | 1 + local.mk | 157 - rockspec.conf | 16 - slingshot | 1 - specs/io_spec.yaml | 4 +- specs/specs.mk | 33 - stdlib-git-1.rockspec | 34 +- 22 files changed, 105 insertions(+), 6246 deletions(-) delete mode 100644 .prev-version delete mode 100644 .slackid delete mode 100644 .x-update-copyright rename AUTHORS => AUTHORS.md (63%) rename COPYING => LICENSE.md (100%) create mode 100644 Makefile rename HACKING => STYLE.md (86%) delete mode 100755 bootstrap delete mode 100644 bootstrap.conf delete mode 100644 build-aux/sanity-cfg.mk delete mode 100644 configure.ac rename {build-aux => doc}/config.ld.in (95%) create mode 100644 lib/std/version.lua delete mode 100644 local.mk delete mode 100644 rockspec.conf delete mode 160000 slingshot delete mode 100644 specs/specs.mk diff --git a/.gitignore b/.gitignore index 3c58006..847ee2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,7 @@ .DS_Store -/.autom4te.cfg /.gitmodules -/ChangeLog -/ChangeLog~ -/GNUmakefile -/INSTALL /Makefile -/Makefile.am -/Makefile.in -/NEWS -/README -/aclocal.m4 -/autom4te.cache -/build-aux/* -!/build-aux/sanity-cfg.mk -!/build-aux/config.ld.in -/config.log -/config.status -/configure /doc -/lib/std/version.lua -/luarocks -/m4/ax_lua.m4 -/m4/slingshot.m4 -/stdlib-*.tar.gz +!/doc/config.ld.in /stdlib-*.rockspec !/stdlib-git-*.rockspec -/travis.yml.in diff --git a/.prev-version b/.prev-version deleted file mode 100644 index 6b3b677..0000000 --- a/.prev-version +++ /dev/null @@ -1 +0,0 @@ -41.2.0 diff --git a/.slackid b/.slackid deleted file mode 100644 index 4fa5980..0000000 --- a/.slackid +++ /dev/null @@ -1 +0,0 @@ -aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih diff --git a/.travis.yml b/.travis.yml index 85fff05..95ea11f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ addons: env: global: + - _VERSION=42.0.0 + - LUAJIT_DIR=luajit-2.0 - LUAJIT_VER=2.0.4 - LUA53_VER=5.3.2 @@ -124,45 +126,21 @@ install: # Build from rockspec, forcing uninstall of older luarocks installed # above when testing the git rockspec, both for enforcing backwards # compatibility by default, and for ease of maintenance. - - if test -f 'stdlib-42.0.0-1.rockspec'; then - sudo luarocks make 'stdlib-42.0.0-1.rockspec' LUA="$LUA"; + - if test -f "stdlib-$_VERSION-1.rockspec"; then + sudo luarocks make "stdlib-$_VERSION-1.rockspec" LUA="$LUA"; else - sudo luarocks make 'stdlib-git-1.rockspec' LUA="$LUA"; + sudo luarocks make --force 'stdlib-git-1.rockspec' LUA="$LUA"; fi # Clean up files created by root - sudo git clean -dfx - - sudo rm -rf slingshot /tmp/ldoc + - sudo rm -rf /tmp/ldoc script: - # Reconfigure for in-tree test install. - - test -f configure || ./bootstrap --verbose - - ./configure --prefix="$_inst" --disable-silent-rules LUA="$LUA" - - # Verify luarocks installation. - - make installcheck || make installcheck V=1 - # Verify local build. - - make - - make check || make check V=1 - - # Verify configured installation. - - make install prefix="$_inst" luadir="$luadir" luaexecdir="$luaexecdir" - - LUA_PATH="$luadir/?.lua;$luadir/?/init.lua;;" - LUA_CPATH="$luaexecdir/?.so;;" - make installcheck V=1 - - -# Run sanity checks on CI server, ignoring buggy automakes. -after_success: - - '{ _assign="="; - if grep local-checks-to-skip build-aux/sanity-cfg.mk >/dev/null; then - _assign="+="; - fi; - printf "local-checks-to-skip %s sc_vulnerable_makefile_CVE-2012-3386\n" "$_assign"; - } >> build-aux/sanity-cfg.mk' - - 'make syntax-check || : this will usually fail on the release branch' + - make check LUA=$LUA + notifications: slack: aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih diff --git a/.x-update-copyright b/.x-update-copyright deleted file mode 100644 index fbb3c02..0000000 --- a/.x-update-copyright +++ /dev/null @@ -1 +0,0 @@ -^bootstrap$ diff --git a/AUTHORS b/AUTHORS.md similarity index 63% rename from AUTHORS rename to AUTHORS.md index 9a36600..6589984 100644 --- a/AUTHORS +++ b/AUTHORS.md @@ -1,12 +1,10 @@ - Stdlib's contributors - --------------------- +# Stdlib's contributors -This file lists major contributors to stdlib. If you think you should -be on it, please tell the mailing list (see README for the address). -Thanks also to all those who have contributed bug fixes, suggestions -and support. +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 +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. @@ -25,3 +23,5 @@ 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/COPYING b/LICENSE.md similarity index 100% rename from COPYING rename to LICENSE.md diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5094bc3 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +LDOC = ldoc +LUA = lua +MKDIR = mkdir -p +SED = sed +SPECL = specl + +luadir = lib/std +SOURCES = \ + $(luadir)/base.lua \ + $(luadir)/container.lua \ + $(luadir)/debug.lua \ + $(luadir)/debug_init/init.lua \ + $(luadir)/init.lua \ + $(luadir)/io.lua \ + $(luadir)/list.lua \ + $(luadir)/math.lua \ + $(luadir)/object.lua \ + $(luadir)/package.lua \ + $(luadir)/set.lua \ + $(luadir)/strbuf.lua \ + $(luadir)/string.lua \ + $(luadir)/table.lua \ + $(luadir)/tree.lua \ + $(luadir)/version.lua \ + $(NOTHING_ELSE) + + +all: doc + +doc: doc/config.ld $(SOURCES) + $(LDOC) -c doc/config.ld . + +doc/config.ld: doc/config.ld.in + version=`LUA_PATH=$$(pwd)'/lib/?.lua;;' $(LUA) -e 'io.stdout:write(require"std.version")'`; \ + $(SED) -e "s,@PACKAGE_VERSION@,$$version," '$<' > '$@' + + +CHECK_ENV = LUA=$(LUA) + +check: + LUA=$(LUA) $(SPECL) $(SPECL_OPTS) specs/*_spec.yaml diff --git a/README.md b/README.md index eef5ae7..e2242e8 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,21 @@ by the [stdlib project][github] [![Stories in Ready](https://badge.waffle.io/lua-stdlib/lua-stdlib.png?label=ready&title=Ready)](https://waffle.io/lua-stdlib/lua-stdlib) -This is a collection of Lua libraries for LuaJIT, Lua 5.1, 5.2 and 5.3. -The libraries are copyright by their authors 2000-2016 (see the +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 2000-2016 (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. +_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 +[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 @@ -38,47 +42,10 @@ report for example): luarocks install http://raw.githubusercontent.com/lua-stdlib/lua-stdlib/master/stdlib-git-1.rockspec ``` -The best way to install without [LuaRocks][] is to download a github -[release tarball][releases] and follow the instructions in the included -[INSTALL][] file. Even if you are repackaging or redistributing -[stdlib][github], this is by far the most straight forward place to -begin. - -Note that you'll be responsible for providing dependencies if you choose -not to let [LuaRocks][] handle them for you, though you can find a list -of minimal dependencies in the [rockspec.conf][] file. - -It is also possible to perform a complete bootstrap of the -[master][github] development branch, although this branch is unstable, -and sometimes breaks subtly, or does not build at all, or provides -experimental new APIs that end up being removed prior to the next -official release. Unfortunately, we don't have time to provide support -for taking this most difficult and dangerous option. It is presumed -that you already know enough to be aware of what you are getting yourself -into - however, there are full logs of complete bootstrap builds in -[Travis][] after every commit, that you can examine if you get stuck. -Also, the bootstrap script tries very hard to tell you why it is unhappy -and, sometimes, even how to fix things before trying again. - -[install]: http://raw.githubusercontent.com/lua-stdlib/lua-stdlib/release/INSTALL -[luarocks]: http://www.luarocks.org "Lua package manager" -[releases]: http://github.com/lua-stdlib/lua-stdlib/releases -[rockspec.conf]: http://github.com/lua-stdlib/lua-stdlib/blob/release/rockspec.conf -[travis]: http://travis-ci.org/lua-stdlib/lua-stdlib/builds - - -Use ---- - -As well as requiring individual libraries, you can load the standard -set with +The best way to install without [LuaRocks][] is to copy the `functional` +folder and its contents into a directory on your package search path. -```lua - local std = require "std" -``` - -Modules not in the standard set may be removed from future versions of -stdlib. +[luarocks]: http://www.luarocks.org "Lua package manager" Documentation diff --git a/HACKING b/STYLE.md similarity index 86% rename from HACKING rename to STYLE.md index 7803b86..f01f630 100644 --- a/HACKING +++ b/STYLE.md @@ -1,10 +1,7 @@ ## Lua - Requiring any stdlib module must not leak any symbols into the global - namespace. To help users who want to do that, there are monkey_patch - functions in the relevant modules. For convenience when writing - throw-away scripts, there's also `std.barrel()`, to replicate the - behaviour of pre-hygienic stdlib. + 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, @@ -63,24 +60,22 @@ - 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 `debug.argscheck` wrapper around the - internal implementatin: this way, implementation functions can call - each other without excessive rechecking of argument types. + 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. - - Avoid using the `_functions` table in objects as much as possible; - it slows down cloning and complicates the API. ## 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 `debug.argscheck` or equivalent. + enforced by `typecheck.argscheck` or equivalent. - `backtick_references` is disabled for stdlib, if you want an inline cross-reference, use `@{reference}`. diff --git a/bootstrap b/bootstrap deleted file mode 100755 index 97d3fe9..0000000 --- a/bootstrap +++ /dev/null @@ -1,5796 +0,0 @@ -#! /bin/sh -## DO NOT EDIT - This file generated from build-aux/bootstrap.in -## by inline-source v2014-01-03.01 - -# Bootstrap an Autotooled package from checked-out sources. -# Written by Gary V. Vaughan, 2010 - -# Copyright (C) 2010-2016 Free Software Foundation, Inc. -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Originally written by Paul Eggert. The canonical version of this -# script is maintained as build-aux/bootstrap in gnulib, however, to -# be useful to your project, you should place a copy of it under -# version control in the top-level directory of your project. The -# intent is that all customization can be done with a bootstrap.conf -# file also maintained in your version control; gnulib comes with a -# template build-aux/bootstrap.conf to get you started. - -# Please report bugs or propose patches to bug-gnulib@gnu.org. - - -## ------ ## -## Usage. ## -## ------ ## - -# Most GNUish projects do not keep all of the generated Autotool -# files under version control, but running all of the right tools -# with the right arguments, in the correct order to regenerate -# all of those files in readiness for configuration and building -# can be surprisingly involved! Many projects have a 'bootstrap' -# script under version control to invoke Autotools and perform -# other assorted book-keeping with version numbers and the like. -# -# This bootstrap script aims to probe the configure.ac and top -# Makefile.am of your project to automatically determine what -# the correct ordering and arguments are and then run the tools for -# you. In order to use it, you can generate an initial standalone -# script with: -# -# gl/build-aux/inline-source gl/build-aux/bootstrap.in > bootstrap -# -# You should then store than script in version control for other -# developers in you project. It will give you instructions about -# how to keep it up to date if the sources change. -# -# See gl/doc/bootstrap.texi for documentation on how to write -# a bootstrap.conf to customize it for your project's -# idiosyncracies. - - -## ================================================================== ## -## ## -## DO NOT EDIT THIS FILE, CUSTOMIZE IT USING A BOOTSTRAP.CONF ## -## ## -## ================================================================== ## - -## ------------------------------- ## -## User overridable command paths. ## -## ------------------------------- ## - -# All uppercase denotes values stored in the environment. These -# variables should generally be overridden by the user - however, we do -# set them to 'true' in some parts of this script to prevent them being -# called at the wrong time by other tools that we call ('autoreconf', -# for example). -# -# We also allow 'LIBTOOLIZE', 'M4', 'SHA1SUM' and some others to be -# overridden, and export the result for child processes, but they are -# handled by the function 'func_find_tool' and not defaulted in this -# section. - -: ${ACLOCAL="aclocal"} -: ${AUTOCONF="autoconf"} -: ${AUTOHEADER="autoheader"} -: ${AUTOM4TE="autom4te"} -: ${AUTOHEADER="autoheader"} -: ${AUTOMAKE="automake"} -: ${AUTOPOINT="autopoint"} -: ${AUTORECONF="autoreconf"} -: ${CMP="cmp"} -: ${CONFIG_SHELL="/bin/sh"} -: ${DIFF="diff"} -: ${GIT="git"} -: ${LN_S="ln -s"} -: ${RM="rm"} - -export ACLOCAL -export AUTOCONF -export AUTOHEADER -export AUTOM4TE -export AUTOHEADER -export AUTOMAKE -export AUTOPOINT -export AUTORECONF -export CONFIG_SHELL - - -: ${LUAROCKS="luarocks"} - -export LUAROCKS - - -## -------------- ## -## Configuration. ## -## -------------- ## - -# A newline delimited list of triples of programs (that respond to -# --version), the minimum version numbers required (or just '-' in the -# version field if any version will be sufficient) and homepage URLs -# to help locate missing packages. -buildreq= - -# Name of a file containing instructions on installing missing packages -# required in 'buildreq'. -buildreq_readme=README-hacking - -# These are extracted from AC_INIT in configure.ac, though you can -# override those values in 'bootstrap.conf' if you prefer. -build_aux= -macro_dir= -package= -package_name= -package_version= -package_bugreport= - -# These are extracted from 'gnulib-cache.m4', or else fall-back -# automatically on the gnulib defaults; unless you set the values -# manually in 'bootstrap.conf'. -doc_base= -gnulib_mk= -gnulib_name= -local_gl_dir= -source_base= -tests_base= - -# The list of gnulib modules required at 'gnulib-tool' time. If you -# check 'gnulib-cache.m4' into your repository, then this list will be -# extracted automatically. -gnulib_modules= - -# Extra gnulib files that are not in modules, which override files of -# the same name installed by other bootstrap tools. -gnulib_non_module_files=" - build-aux/compile - build-aux/install-sh - build-aux/mdate-sh - build-aux/texinfo.tex - build-aux/depcomp - build-aux/config.guess - build-aux/config.sub - doc/INSTALL -" - -# Relative path to the local gnulib submodule, and url to the upstream -# git repository. If you have a gnulib entry in your .gitmodules file, -# these values are ignored. -gnulib_path= -gnulib_url= - -# Additional gnulib-tool options to use. -gnulib_tool_options=" - --no-changelog -" - -# bootstrap removes any macro-files that are not included by aclocal.m4, -# except for files listed in this variable that are always kept. -gnulib_precious=" - gnulib-tool.m4 -" - -# When truncating long commands for display, always allow at least this -# many characters before truncating. -min_cmd_len=160 - -# The command to download all .po files for a specified domain into -# a specified directory. Fill in the first %s is the domain name, and -# the second with the destination directory. Use rsync's -L and -r -# options because the latest/%s directory and the .po files within are -# all symlinks. -po_download_command_format=\ -"rsync --delete --exclude '*.s1' -Lrtvz \ -'translationproject.org::tp/latest/%s/' '%s'" - -# Other locale categories that need message catalogs. -extra_locale_categories= - -# Additional xgettext options to use. Gnulib might provide you with an -# extensive list of additional options to append to this, but gettext -# 0.16.1 and newer appends them automaticaly, so you can safely ignore -# the complaints from 'gnulib-tool' if your $configure_ac states: -# -# AM_GNU_GETTEXT_VERSION([0.16.1]) -xgettext_options=" - --flag=_:1:pass-c-format - --flag=N_:1:pass-c-format -" - -# Package copyright holder for gettext files. Defaults to FSF if unset. -copyright_holder= - -# File that should exist in the top directory of a checked out hierarchy, -# but not in a distribution tarball. -checkout_only_file= - -# Whether to use copies instead of symlinks by default (if set to true, -# the --copy option has no effect). -copy=false - -# Set this to ".cvsignore .gitignore" in 'bootstrap.conf' if you want -# those files to be generated in directories like 'lib/', 'm4/', and 'po/', -# or set it to "auto" to make this script select what to use based -# on what version control system (if any) is used in the source directory. -# Or set it to "none" to ignore VCS ignore files entirely. Default is -# "auto". -vc_ignore= - - -# List of slingshot files to link into stdlib tree before autotooling. -slingshot_files=$slingshot_files - -# Relative path to the local slingshot submodule, and url to the upsream -# git repository. If you have a slingshot entry in your .gitmodules file, -# these values are ignored. -slingshot_path=$slingshot_path -slingshot_url=$slingshot_url - -# NOTE: slingshot bootstrap will check rockspecs listed in $buildreq, -# according to the URL part of a specification triple ending in -# `.rockspec`. - - -## ------------------- ## -## External Libraries. ## -## ------------------- ## - -# Source required external libraries: -# Set a version string for this script. -scriptversion=2014-01-03.01; # UTC - -# General shell script boiler plate, and helper functions. -# Written by Gary V. Vaughan, 2004 - -# Copyright (C) 2004-2014 Free Software Foundation, Inc. -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. - -# As a special exception to the GNU General Public License, if you distribute -# this file as part of a program or library that is built using GNU Libtool, -# you may include this file under the same distribution terms that you use -# for the rest of that program. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNES FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Please report bugs or propose patches to gary@gnu.org. - - -## ------ ## -## Usage. ## -## ------ ## - -# Evaluate this file near the top of your script to gain access to -# the functions and variables defined here: -# -# . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh -# -# If you need to override any of the default environment variable -# settings, do that before evaluating this file. - - -## -------------------- ## -## Shell normalisation. ## -## -------------------- ## - -# Some shells need a little help to be as Bourne compatible as possible. -# Before doing anything else, make sure all that help has been provided! - -DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : - emulate sh - NULLCMD=: - # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac -fi - -# NLS nuisances: We save the old values in case they are required later. -_G_user_locale= -_G_safe_locale= -for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES -do - eval "if test set = \"\${$_G_var+set}\"; then - save_$_G_var=\$$_G_var - $_G_var=C - export $_G_var - _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\" - _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\" - fi" -done - -# CDPATH. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH - -# Make sure IFS has a sensible default -sp=' ' -nl=' -' -IFS="$sp $nl" - -# There are apparently some retarded systems that use ';' as a PATH separator! -if test "${PATH_SEPARATOR+set}" != set; then - PATH_SEPARATOR=: - (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { - (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || - PATH_SEPARATOR=';' - } -fi - - - -## ------------------------- ## -## Locate command utilities. ## -## ------------------------- ## - - -# func_executable_p FILE -# ---------------------- -# Check that FILE is an executable regular file. -func_executable_p () -{ - test -f "$1" && test -x "$1" -} - - -# func_path_progs PROGS_LIST CHECK_FUNC [PATH] -# -------------------------------------------- -# Search for either a program that responds to --version with output -# containing "GNU", or else returned by CHECK_FUNC otherwise, by -# trying all the directories in PATH with each of the elements of -# PROGS_LIST. -# -# CHECK_FUNC should accept the path to a candidate program, and -# set $func_check_prog_result if it truncates its output less than -# $_G_path_prog_max characters. -func_path_progs () -{ - _G_progs_list=$1 - _G_check_func=$2 - _G_PATH=${3-"$PATH"} - - _G_path_prog_max=0 - _G_path_prog_found=false - _G_save_IFS=$IFS; IFS=$PATH_SEPARATOR - for _G_dir in $_G_PATH; do - IFS=$_G_save_IFS - test -z "$_G_dir" && _G_dir=. - for _G_prog_name in $_G_progs_list; do - for _exeext in '' .EXE; do - _G_path_prog=$_G_dir/$_G_prog_name$_exeext - func_executable_p "$_G_path_prog" || continue - case `"$_G_path_prog" --version 2>&1` in - *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;; - *) $_G_check_func $_G_path_prog - func_path_progs_result=$func_check_prog_result - ;; - esac - $_G_path_prog_found && break 3 - done - done - done - IFS=$_G_save_IFS - test -z "$func_path_progs_result" && { - echo "no acceptable sed could be found in \$PATH" >&2 - exit 1 - } -} - - -# We want to be able to use the functions in this file before configure -# has figured out where the best binaries are kept, which means we have -# to search for them ourselves - except when the results are already set -# where we skip the searches. - -# Unless the user overrides by setting SED, search the path for either GNU -# sed, or the sed that truncates its output the least. -test -z "$SED" && { - _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ - for _G_i in 1 2 3 4 5 6 7; do - _G_sed_script=$_G_sed_script$nl$_G_sed_script - done - echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed - _G_sed_script= - - func_check_prog_sed () - { - _G_path_prog=$1 - - _G_count=0 - printf 0123456789 >conftest.in - while : - do - cat conftest.in conftest.in >conftest.tmp - mv conftest.tmp conftest.in - cp conftest.in conftest.nl - echo '' >> conftest.nl - "$_G_path_prog" -f conftest.sed conftest.out 2>/dev/null || break - diff conftest.out conftest.nl >/dev/null 2>&1 || break - _G_count=`expr $_G_count + 1` - if test "$_G_count" -gt "$_G_path_prog_max"; then - # Best one so far, save it but keep looking for a better one - func_check_prog_result=$_G_path_prog - _G_path_prog_max=$_G_count - fi - # 10*(2^10) chars as input seems more than enough - test 10 -lt "$_G_count" && break - done - rm -f conftest.in conftest.tmp conftest.nl conftest.out - } - - func_path_progs "sed gsed" func_check_prog_sed $PATH:/usr/xpg4/bin - rm -f conftest.sed - SED=$func_path_progs_result -} - - -# Unless the user overrides by setting GREP, search the path for either GNU -# grep, or the grep that truncates its output the least. -test -z "$GREP" && { - func_check_prog_grep () - { - _G_path_prog=$1 - - _G_count=0 - _G_path_prog_max=0 - printf 0123456789 >conftest.in - while : - do - cat conftest.in conftest.in >conftest.tmp - mv conftest.tmp conftest.in - cp conftest.in conftest.nl - echo 'GREP' >> conftest.nl - "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' conftest.out 2>/dev/null || break - diff conftest.out conftest.nl >/dev/null 2>&1 || break - _G_count=`expr $_G_count + 1` - if test "$_G_count" -gt "$_G_path_prog_max"; then - # Best one so far, save it but keep looking for a better one - func_check_prog_result=$_G_path_prog - _G_path_prog_max=$_G_count - fi - # 10*(2^10) chars as input seems more than enough - test 10 -lt "$_G_count" && break - done - rm -f conftest.in conftest.tmp conftest.nl conftest.out - } - - func_path_progs "grep ggrep" func_check_prog_grep $PATH:/usr/xpg4/bin - GREP=$func_path_progs_result -} - - -## ------------------------------- ## -## User overridable command paths. ## -## ------------------------------- ## - -# All uppercase variable names are used for environment variables. These -# variables can be overridden by the user before calling a script that -# uses them if a suitable command of that name is not already available -# in the command search PATH. - -: ${CP="cp -f"} -: ${ECHO="printf %s\n"} -: ${EGREP="$GREP -E"} -: ${FGREP="$GREP -F"} -: ${LN_S="ln -s"} -: ${MAKE="make"} -: ${MKDIR="mkdir"} -: ${MV="mv -f"} -: ${RM="rm -f"} -: ${SHELL="${CONFIG_SHELL-/bin/sh}"} - - -## -------------------- ## -## Useful sed snippets. ## -## -------------------- ## - -sed_dirname='s|/[^/]*$||' -sed_basename='s|^.*/||' - -# Sed substitution that helps us do robust quoting. It backslashifies -# metacharacters that are still active within double-quoted strings. -sed_quote_subst='s|\([`"$\\]\)|\\\1|g' - -# Same as above, but do not quote variable references. -sed_double_quote_subst='s/\(["`\\]\)/\\\1/g' - -# Sed substitution that turns a string into a regex matching for the -# string literally. -sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g' - -# Sed substitution that converts a w32 file name or path -# that contains forward slashes, into one that contains -# (escaped) backslashes. A very naive implementation. -sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g' - -# Re-'\' parameter expansions in output of sed_double_quote_subst that -# were '\'-ed in input to the same. If an odd number of '\' preceded a -# '$' in input to sed_double_quote_subst, that '$' was protected from -# expansion. Since each input '\' is now two '\'s, look for any number -# of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'. -_G_bs='\\' -_G_bs2='\\\\' -_G_bs4='\\\\\\\\' -_G_dollar='\$' -sed_double_backslash="\ - s/$_G_bs4/&\\ -/g - s/^$_G_bs2$_G_dollar/$_G_bs&/ - s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g - s/\n//g" - - -## ----------------- ## -## Global variables. ## -## ----------------- ## - -# Except for the global variables explicitly listed below, the following -# functions in the '^func_' namespace, and the '^require_' namespace -# variables initialised in the 'Resource management' section, sourcing -# this file will not pollute your global namespace with anything -# else. There's no portable way to scope variables in Bourne shell -# though, so actually running these functions will sometimes place -# results into a variable named after the function, and often use -# temporary variables in the '^_G_' namespace. If you are careful to -# avoid using those namespaces casually in your sourcing script, things -# should continue to work as you expect. And, of course, you can freely -# overwrite any of the functions or variables defined here before -# calling anything to customize them. - -EXIT_SUCCESS=0 -EXIT_FAILURE=1 -EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing. -EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. - -# Allow overriding, eg assuming that you follow the convention of -# putting '$debug_cmd' at the start of all your functions, you can get -# bash to show function call trace with: -# -# debug_cmd='eval echo "${FUNCNAME[0]} $*" >&2' bash your-script-name -debug_cmd=${debug_cmd-":"} -exit_cmd=: - -# By convention, finish your script with: -# -# exit $exit_status -# -# so that you can set exit_status to non-zero if you want to indicate -# something went wrong during execution without actually bailing out at -# the point of failure. -exit_status=$EXIT_SUCCESS - -# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh -# is ksh but when the shell is invoked as "sh" and the current value of -# the _XPG environment variable is not equal to 1 (one), the special -# positional parameter $0, within a function call, is the name of the -# function. -progpath=$0 - -# The name of this program. -progname=`$ECHO "$progpath" |$SED "$sed_basename"` - -# Make sure we have an absolute progpath for reexecution: -case $progpath in - [\\/]*|[A-Za-z]:\\*) ;; - *[\\/]*) - progdir=`$ECHO "$progpath" |$SED "$sed_dirname"` - progdir=`cd "$progdir" && pwd` - progpath=$progdir/$progname - ;; - *) - _G_IFS=$IFS - IFS=${PATH_SEPARATOR-:} - for progdir in $PATH; do - IFS=$_G_IFS - test -x "$progdir/$progname" && break - done - IFS=$_G_IFS - test -n "$progdir" || progdir=`pwd` - progpath=$progdir/$progname - ;; -esac - - -## ----------------- ## -## Standard options. ## -## ----------------- ## - -# The following options affect the operation of the functions defined -# below, and should be set appropriately depending on run-time para- -# meters passed on the command line. - -opt_dry_run=false -opt_quiet=false -opt_verbose=false - -# Categories 'all' and 'none' are always available. Append any others -# you will pass as the first argument to func_warning from your own -# code. -warning_categories= - -# By default, display warnings according to 'opt_warning_types'. Set -# 'warning_func' to ':' to elide all warnings, or func_fatal_error to -# treat the next displayed warning as a fatal error. -warning_func=func_warn_and_continue - -# Set to 'all' to display all warnings, 'none' to suppress all -# warnings, or a space delimited list of some subset of -# 'warning_categories' to display only the listed warnings. -opt_warning_types=all - - -## -------------------- ## -## Resource management. ## -## -------------------- ## - -# This section contains definitions for functions that each ensure a -# particular resource (a file, or a non-empty configuration variable for -# example) is available, and if appropriate to extract default values -# from pertinent package files. Call them using their associated -# 'require_*' variable to ensure that they are executed, at most, once. -# -# It's entirely deliberate that calling these functions can set -# variables that don't obey the namespace limitations obeyed by the rest -# of this file, in order that that they be as useful as possible to -# callers. - - -# require_term_colors -# ------------------- -# Allow display of bold text on terminals that support it. -require_term_colors=func_require_term_colors -func_require_term_colors () -{ - $debug_cmd - - test -t 1 && { - # COLORTERM and USE_ANSI_COLORS environment variables take - # precedence, because most terminfo databases neglect to describe - # whether color sequences are supported. - test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"} - - if test 1 = "$USE_ANSI_COLORS"; then - # Standard ANSI escape sequences - tc_reset='' - tc_bold=''; tc_standout='' - tc_red=''; tc_green='' - tc_blue=''; tc_cyan='' - else - # Otherwise trust the terminfo database after all. - test -n "`tput sgr0 2>/dev/null`" && { - tc_reset=`tput sgr0` - test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold` - tc_standout=$tc_bold - test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso` - test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1` - test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2` - test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4` - test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5` - } - fi - } - - require_term_colors=: -} - - -# require_rockspecs_req -# --------------------- -# Remove rockspecs from $buildreq, and add them to $rockspecs_req. -require_rockspecs_req=slingshot_require_rockspecs_req -slingshot_require_rockspecs_req () -{ - $debug_cmd - - test -n "$rockspecs_req" || { - _G_non_rockspecs= - - set dummy $buildreq; shift - - while test $# -gt 2; do - case $3 in - *.rockspec) - func_append rockspecs_req " $1 $2 $3" - ;; - [a-z]*://*) - func_append _G_non_rockspecs " $1 $2 $3" - ;; - *) func_fatal_error "\ -'$3' from the buildreq table in -'bootstrap.conf' does not look like the URL for downloading -$1. Please ensure that buildreq is a strict newline -delimited list of triples; 'program min-version url'." - ;; - esac - shift; shift; shift - done - - buildreq=$_G_non_rockspecs - } - - require_rockspecs_req=: -} - - -# require_slingshot_dotgitmodules -# ------------------------------- -# Ensure we have a '.gitmodules' file, with appropriate 'slingshot' settings. -require_slingshot_dotgitmodules=slingshot_require_slingshot_dotgitmodules -slingshot_require_slingshot_dotgitmodules () -{ - $debug_cmd - - $require_git - - test true = "$GIT" || { - # A slingshot entry in .gitmodules always takes precedence. - _G_path=`$GIT config --file .gitmodules submodule.slingshot.path 2>/dev/null` - - test -n "$_G_path" || { - $require_vc_ignore_files - - func_verbose "adding slingshot entries to '.gitmodules'" - - test -n "$slingshot_path" || slingshot_path=slingshot - test -n "$slingshot_url" || slingshot_url=git://github.com/gvvaughan/slingshot.git - - { - echo '[submodule "slingshot"]' - echo " path=$slingshot_path" - echo " url=$slingshot_url" - } >> .gitmodules - - test -n "$vc_ignore_files" \ - || func_insert_if_absent ".gitmodules" $vc_ignore_files - } - } - - require_slingshot_dotgitmodules=: -} - - -# require_slingshot_path -# require_slingshot_url -# ---------------------- -# Ensure 'slingshot_path' and 'slingshot_url' are set. -require_slingshot_path=slingshot_require_slingshot_dotgitmodules_parameters -require_slingshot_url=slingshot_require_slingshot_dotgitmodules_parameters -slingshot_require_slingshot_dotgitmodules_parameters () -{ - $debug_cmd - - $require_git - $require_slingshot_dotgitmodules - - test -f .gitmodules \ - || func_fatal_error "Unable to update '.gitmodules' with slingshot submodule" - - test true = "$GIT" || { - slingshot_path=`$GIT config --file=.gitmodules --get submodule.slingshot.path` - slingshot_url=`$GIT config --file=.gitmodules --get submodule.slingshot.url` - - func_verbose "slingshot_path='$slingshot_path'" - func_verbose "slingshot_url='$slingshot_url'" - } - - require_slingshot_path=: - require_slingshot_url=: -} - - -# require_slingshot_submodule -# --------------------------- -# Ensure that there is a current slingshot submodule. -require_slingshot_submodule=slingshot_require_slingshot_submodule -slingshot_require_slingshot_submodule () -{ - $debug_cmd - - $require_git - - if test true = "$GIT"; then - func_warning recommend \ - "No 'git' found; imported slingshot modules may be missing." - else - $require_slingshot_dotgitmodules - - if test -f .gitmodules; then - $require_slingshot_path - $require_slingshot_url - - if test -f "slingshot/src/mkrockspecs.in"; then - : All present and correct. - - else - trap slingshot_cleanup 1 2 13 15 - - shallow= - $GIT clone -h 2>&1 |func_grep_q -- --depth \ - && shallow='--depth 365' - - func_show_eval "$GIT clone $shallow '$slingshot_url' '$slingshot_path'" \ - slingshot_cleanup - - # FIXME: Solaris /bin/sh will try to execute '-' if any of - # these signals are caught after this. - trap - 1 2 13 15 - fi - - # Make sure we've checked out the correct revision of slingshot. - func_show_eval "$GIT submodule init -- $slingshot_path" \ - && func_show_eval "$GIT submodule update -- $slingshot_path" \ - || func_fatal_error "Unable to update slingshot submodule." - fi - fi - - require_slingshot_submodule=: -} - - -# require_bootstrap_uptodate -# -------------------------- -# Complain if the version of bootstrap in the build-aux directory differs -# from the one we are running. -require_bootstrap_uptodate=slingshot_require_bootstrap_uptodate -slingshot_require_bootstrap_uptodate () -{ - $debug_cmd - - $require_slingshot_submodule - - _G_slingshot_bootstrap=slingshot/bootstrap - - rm -f $progname.new - - if test -f "$_G_slingshot_bootstrap"; then - if func_cmp_s "$progpath" "$_G_slingshot_bootstrap"; then - func_verbose "bootstrap script up to date" - else - cp -f $_G_slingshot_bootstrap $progname.new - func_warning upgrade "\ -An updated slingshot bootstrap script is ready for you in -'$progname.new'. After you've verified that you want the -changes, you can update with: - mv -f $progname.new $progname - ./$progname - -Or you can disable this check permanently by adding the -following to 'bootstrap.conf': - require_bootstrap_uptodate=:" - fi - else - func_warning upgrade "\ -Your slingshot submodule appears to be damagedi, so I can't tell -whether your bootstrap has gone out of sync. Please check for -and undo any local changes, or revert to the slingshot revision -you were using previously, and rerun this script." - fi - - require_bootstrap_uptodate=: -} - - -# slingshot_cleanup -# ----------------- -# Recursively delete everything at $slingshot_path. -slingshot_cleanup () -{ - $debug_cmd - - $require_slingshot_path - - _G_status=$? - $RM -fr $slingshot_path - exit $_G_status -} - - -## ----------------- ## -## Function library. ## -## ----------------- ## - -# This section contains a variety of useful functions to call in your -# scripts. Take note of the portable wrappers for features provided by -# some modern shells, which will fall back to slower equivalents on -# less featureful shells. - - -# func_append VAR VALUE -# --------------------- -# Append VALUE onto the existing contents of VAR. - - # We should try to minimise forks, especially on Windows where they are - # unreasonably slow, so skip the feature probes when bash or zsh are - # being used: - if test set = "${BASH_VERSION+set}${ZSH_VERSION+set}"; then - : ${_G_HAVE_ARITH_OP="yes"} - : ${_G_HAVE_XSI_OPS="yes"} - # The += operator was introduced in bash 3.1 - case $BASH_VERSION in - [12].* | 3.0 | 3.0*) ;; - *) - : ${_G_HAVE_PLUSEQ_OP="yes"} - ;; - esac - fi - - # _G_HAVE_PLUSEQ_OP - # Can be empty, in which case the shell is probed, "yes" if += is - # useable or anything else if it does not work. - test -z "$_G_HAVE_PLUSEQ_OP" \ - && (eval 'x=a; x+=" b"; test "a b" = "$x"') 2>/dev/null \ - && _G_HAVE_PLUSEQ_OP=yes - -if test yes = "$_G_HAVE_PLUSEQ_OP" -then - # This is an XSI compatible shell, allowing a faster implementation... - eval 'func_append () - { - $debug_cmd - - eval "$1+=\$2" - }' -else - # ...otherwise fall back to using expr, which is often a shell builtin. - func_append () - { - $debug_cmd - - eval "$1=\$$1\$2" - } -fi - - -# func_append_quoted VAR VALUE -# ---------------------------- -# Quote VALUE and append to the end of shell variable VAR, separated -# by a space. -if test yes = "$_G_HAVE_PLUSEQ_OP"; then - eval 'func_append_quoted () - { - $debug_cmd - - func_quote_for_eval "$2" - eval "$1+=\\ \$func_quote_for_eval_result" - }' -else - func_append_quoted () - { - $debug_cmd - - func_quote_for_eval "$2" - eval "$1=\$$1\\ \$func_quote_for_eval_result" - } -fi - - -# func_append_uniq VAR VALUE -# -------------------------- -# Append unique VALUE onto the existing contents of VAR, assuming -# entries are delimited by the first character of VALUE. For example: -# -# func_append_uniq options " --another-option option-argument" -# -# will only append to $options if " --another-option option-argument " -# is not already present somewhere in $options already (note spaces at -# each end implied by leading space in second argument). -func_append_uniq () -{ - $debug_cmd - - eval _G_current_value='`$ECHO $'$1'`' - _G_delim=`expr "$2" : '\(.\)'` - - case $_G_delim$_G_current_value$_G_delim in - *"$2$_G_delim"*) ;; - *) func_append "$@" ;; - esac -} - - -# func_arith TERM... -# ------------------ -# Set func_arith_result to the result of evaluating TERMs. - test -z "$_G_HAVE_ARITH_OP" \ - && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \ - && _G_HAVE_ARITH_OP=yes - -if test yes = "$_G_HAVE_ARITH_OP"; then - eval 'func_arith () - { - $debug_cmd - - func_arith_result=$(( $* )) - }' -else - func_arith () - { - $debug_cmd - - func_arith_result=`expr "$@"` - } -fi - - -# func_basename FILE -# ------------------ -# Set func_basename_result to FILE with everything up to and including -# the last / stripped. -if test yes = "$_G_HAVE_XSI_OPS"; then - # If this shell supports suffix pattern removal, then use it to avoid - # forking. Hide the definitions single quotes in case the shell chokes - # on unsupported syntax... - _b='func_basename_result=${1##*/}' - _d='case $1 in - */*) func_dirname_result=${1%/*}$2 ;; - * ) func_dirname_result=$3 ;; - esac' - -else - # ...otherwise fall back to using sed. - _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`' - _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"` - if test "X$func_dirname_result" = "X$1"; then - func_dirname_result=$3 - else - func_append func_dirname_result "$2" - fi' -fi - -eval 'func_basename () -{ - $debug_cmd - - '"$_b"' -}' - - -# func_dirname FILE APPEND NONDIR_REPLACEMENT -# ------------------------------------------- -# Compute the dirname of FILE. If nonempty, add APPEND to the result, -# otherwise set result to NONDIR_REPLACEMENT. -eval 'func_dirname () -{ - $debug_cmd - - '"$_d"' -}' - - -# func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT -# -------------------------------------------------------- -# Perform func_basename and func_dirname in a single function -# call: -# dirname: Compute the dirname of FILE. If nonempty, -# add APPEND to the result, otherwise set result -# to NONDIR_REPLACEMENT. -# value returned in "$func_dirname_result" -# basename: Compute filename of FILE. -# value retuned in "$func_basename_result" -# For efficiency, we do not delegate to the functions above but instead -# duplicate the functionality here. -eval 'func_dirname_and_basename () -{ - $debug_cmd - - '"$_b"' - '"$_d"' -}' - - -# func_echo ARG... -# ---------------- -# Echo program name prefixed message. -func_echo () -{ - $debug_cmd - - _G_message=$* - - func_echo_IFS=$IFS - IFS=$nl - for _G_line in $_G_message; do - IFS=$func_echo_IFS - $ECHO "$progname: $_G_line" - done - IFS=$func_echo_IFS -} - - -# func_echo_all ARG... -# -------------------- -# Invoke $ECHO with all args, space-separated. -func_echo_all () -{ - $ECHO "$*" -} - - -# func_echo_infix_1 INFIX ARG... -# ------------------------------ -# Echo program name, followed by INFIX on the first line, with any -# additional lines not showing INFIX. -func_echo_infix_1 () -{ - $debug_cmd - - $require_term_colors - - _G_infix=$1; shift - _G_indent=$_G_infix - _G_prefix="$progname: $_G_infix: " - _G_message=$* - - # Strip color escape sequences before counting printable length - for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" - do - test -n "$_G_tc" && { - _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"` - _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"` - } - done - _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes - - func_echo_infix_1_IFS=$IFS - IFS=$nl - for _G_line in $_G_message; do - IFS=$func_echo_infix_1_IFS - $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 - _G_prefix=$_G_indent - done - IFS=$func_echo_infix_1_IFS -} - - -# func_error ARG... -# ----------------- -# Echo program name prefixed message to standard error. -func_error () -{ - $debug_cmd - - $require_term_colors - - func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2 -} - - -# func_fatal_error ARG... -# ----------------------- -# Echo program name prefixed message to standard error, and exit. -func_fatal_error () -{ - $debug_cmd - - func_error "$*" - exit $EXIT_FAILURE -} - - -# func_grep EXPRESSION FILENAME -# ----------------------------- -# Check whether EXPRESSION matches any line of FILENAME, without output. -func_grep () -{ - $debug_cmd - - $GREP "$1" "$2" >/dev/null 2>&1 -} - - -# func_len STRING -# --------------- -# Set func_len_result to the length of STRING. STRING may not -# start with a hyphen. - test -z "$_G_HAVE_XSI_OPS" \ - && (eval 'x=a/b/c; - test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ - && _G_HAVE_XSI_OPS=yes - -if test yes = "$_G_HAVE_XSI_OPS"; then - eval 'func_len () - { - $debug_cmd - - func_len_result=${#1} - }' -else - func_len () - { - $debug_cmd - - func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len` - } -fi - - -# func_mkdir_p DIRECTORY-PATH -# --------------------------- -# Make sure the entire path to DIRECTORY-PATH is available. -func_mkdir_p () -{ - $debug_cmd - - _G_directory_path=$1 - _G_dir_list= - - if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then - - # Protect directory names starting with '-' - case $_G_directory_path in - -*) _G_directory_path=./$_G_directory_path ;; - esac - - # While some portion of DIR does not yet exist... - while test ! -d "$_G_directory_path"; do - # ...make a list in topmost first order. Use a colon delimited - # list incase some portion of path contains whitespace. - _G_dir_list=$_G_directory_path:$_G_dir_list - - # If the last portion added has no slash in it, the list is done - case $_G_directory_path in */*) ;; *) break ;; esac - - # ...otherwise throw away the child directory and loop - _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"` - done - _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'` - - func_mkdir_p_IFS=$IFS; IFS=: - for _G_dir in $_G_dir_list; do - IFS=$func_mkdir_p_IFS - # mkdir can fail with a 'File exist' error if two processes - # try to create one of the directories concurrently. Don't - # stop in that case! - $MKDIR "$_G_dir" 2>/dev/null || : - done - IFS=$func_mkdir_p_IFS - - # Bail out if we (or some other process) failed to create a directory. - test -d "$_G_directory_path" || \ - func_fatal_error "Failed to create '$1'" - fi -} - - -# func_mktempdir [BASENAME] -# ------------------------- -# Make a temporary directory that won't clash with other running -# libtool processes, and avoids race conditions if possible. If -# given, BASENAME is the basename for that directory. -func_mktempdir () -{ - $debug_cmd - - _G_template=${TMPDIR-/tmp}/${1-$progname} - - if test : = "$opt_dry_run"; then - # Return a directory name, but don't create it in dry-run mode - _G_tmpdir=$_G_template-$$ - else - - # If mktemp works, use that first and foremost - _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null` - - if test ! -d "$_G_tmpdir"; then - # Failing that, at least try and use $RANDOM to avoid a race - _G_tmpdir=$_G_template-${RANDOM-0}$$ - - func_mktempdir_umask=`umask` - umask 0077 - $MKDIR "$_G_tmpdir" - umask $func_mktempdir_umask - fi - - # If we're not in dry-run mode, bomb out on failure - test -d "$_G_tmpdir" || \ - func_fatal_error "cannot create temporary directory '$_G_tmpdir'" - fi - - $ECHO "$_G_tmpdir" -} - - -# func_normal_abspath PATH -# ------------------------ -# Remove doubled-up and trailing slashes, "." path components, -# and cancel out any ".." path components in PATH after making -# it an absolute path. -func_normal_abspath () -{ - $debug_cmd - - # These SED scripts presuppose an absolute path with a trailing slash. - _G_pathcar='s|^/\([^/]*\).*$|\1|' - _G_pathcdr='s|^/[^/]*||' - _G_removedotparts=':dotsl - s|/\./|/|g - t dotsl - s|/\.$|/|' - _G_collapseslashes='s|/\{1,\}|/|g' - _G_finalslash='s|/*$|/|' - - # Start from root dir and reassemble the path. - func_normal_abspath_result= - func_normal_abspath_tpath=$1 - func_normal_abspath_altnamespace= - case $func_normal_abspath_tpath in - "") - # Empty path, that just means $cwd. - func_stripname '' '/' "`pwd`" - func_normal_abspath_result=$func_stripname_result - return - ;; - # The next three entries are used to spot a run of precisely - # two leading slashes without using negated character classes; - # we take advantage of case's first-match behaviour. - ///*) - # Unusual form of absolute path, do nothing. - ;; - //*) - # Not necessarily an ordinary path; POSIX reserves leading '//' - # and for example Cygwin uses it to access remote file shares - # over CIFS/SMB, so we conserve a leading double slash if found. - func_normal_abspath_altnamespace=/ - ;; - /*) - # Absolute path, do nothing. - ;; - *) - # Relative path, prepend $cwd. - func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath - ;; - esac - - # Cancel out all the simple stuff to save iterations. We also want - # the path to end with a slash for ease of parsing, so make sure - # there is one (and only one) here. - func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ - -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"` - while :; do - # Processed it all yet? - if test / = "$func_normal_abspath_tpath"; then - # If we ascended to the root using ".." the result may be empty now. - if test -z "$func_normal_abspath_result"; then - func_normal_abspath_result=/ - fi - break - fi - func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \ - -e "$_G_pathcar"` - func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ - -e "$_G_pathcdr"` - # Figure out what to do with it - case $func_normal_abspath_tcomponent in - "") - # Trailing empty path component, ignore it. - ;; - ..) - # Parent dir; strip last assembled component from result. - func_dirname "$func_normal_abspath_result" - func_normal_abspath_result=$func_dirname_result - ;; - *) - # Actual path component, append it. - func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent" - ;; - esac - done - # Restore leading double-slash if one was found on entry. - func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result -} - - -# func_notquiet ARG... -# -------------------- -# Echo program name prefixed message only when not in quiet mode. -func_notquiet () -{ - $debug_cmd - - $opt_quiet || func_echo ${1+"$@"} - - # A bug in bash halts the script if the last line of a function - # fails when set -e is in force, so we need another command to - # work around that: - : -} - - -# func_relative_path SRCDIR DSTDIR -# -------------------------------- -# Set func_relative_path_result to the relative path from SRCDIR to DSTDIR. -func_relative_path () -{ - $debug_cmd - - func_relative_path_result= - func_normal_abspath "$1" - func_relative_path_tlibdir=$func_normal_abspath_result - func_normal_abspath "$2" - func_relative_path_tbindir=$func_normal_abspath_result - - # Ascend the tree starting from libdir - while :; do - # check if we have found a prefix of bindir - case $func_relative_path_tbindir in - $func_relative_path_tlibdir) - # found an exact match - func_relative_path_tcancelled= - break - ;; - $func_relative_path_tlibdir*) - # found a matching prefix - func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir" - func_relative_path_tcancelled=$func_stripname_result - if test -z "$func_relative_path_result"; then - func_relative_path_result=. - fi - break - ;; - *) - func_dirname $func_relative_path_tlibdir - func_relative_path_tlibdir=$func_dirname_result - if test -z "$func_relative_path_tlibdir"; then - # Have to descend all the way to the root! - func_relative_path_result=../$func_relative_path_result - func_relative_path_tcancelled=$func_relative_path_tbindir - break - fi - func_relative_path_result=../$func_relative_path_result - ;; - esac - done - - # Now calculate path; take care to avoid doubling-up slashes. - func_stripname '' '/' "$func_relative_path_result" - func_relative_path_result=$func_stripname_result - func_stripname '/' '/' "$func_relative_path_tcancelled" - if test -n "$func_stripname_result"; then - func_append func_relative_path_result "/$func_stripname_result" - fi - - # Normalisation. If bindir is libdir, return '.' else relative path. - if test -n "$func_relative_path_result"; then - func_stripname './' '' "$func_relative_path_result" - func_relative_path_result=$func_stripname_result - fi - - test -n "$func_relative_path_result" || func_relative_path_result=. - - : -} - - -# func_quote_for_eval ARG... -# -------------------------- -# Aesthetically quote ARGs to be evaled later. -# This function returns two values: -# i) func_quote_for_eval_result -# double-quoted, suitable for a subsequent eval -# ii) func_quote_for_eval_unquoted_result -# has all characters that are still active within double -# quotes backslashified. -func_quote_for_eval () -{ - $debug_cmd - - func_quote_for_eval_unquoted_result= - func_quote_for_eval_result= - while test 0 -lt $#; do - case $1 in - *[\\\`\"\$]*) - _G_unquoted_arg=`printf '%s\n' "$1" |$SED "$sed_quote_subst"` ;; - *) - _G_unquoted_arg=$1 ;; - esac - if test -n "$func_quote_for_eval_unquoted_result"; then - func_append func_quote_for_eval_unquoted_result " $_G_unquoted_arg" - else - func_append func_quote_for_eval_unquoted_result "$_G_unquoted_arg" - fi - - case $_G_unquoted_arg in - # Double-quote args containing shell metacharacters to delay - # word splitting, command substitution and variable expansion - # for a subsequent eval. - # Many Bourne shells cannot handle close brackets correctly - # in scan sets, so we specify it separately. - *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") - _G_quoted_arg=\"$_G_unquoted_arg\" - ;; - *) - _G_quoted_arg=$_G_unquoted_arg - ;; - esac - - if test -n "$func_quote_for_eval_result"; then - func_append func_quote_for_eval_result " $_G_quoted_arg" - else - func_append func_quote_for_eval_result "$_G_quoted_arg" - fi - shift - done -} - - -# func_quote_for_expand ARG -# ------------------------- -# Aesthetically quote ARG to be evaled later; same as above, -# but do not quote variable references. -func_quote_for_expand () -{ - $debug_cmd - - case $1 in - *[\\\`\"]*) - _G_arg=`$ECHO "$1" | $SED \ - -e "$sed_double_quote_subst" -e "$sed_double_backslash"` ;; - *) - _G_arg=$1 ;; - esac - - case $_G_arg in - # Double-quote args containing shell metacharacters to delay - # word splitting and command substitution for a subsequent eval. - # Many Bourne shells cannot handle close brackets correctly - # in scan sets, so we specify it separately. - *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") - _G_arg=\"$_G_arg\" - ;; - esac - - func_quote_for_expand_result=$_G_arg -} - - -# func_stripname PREFIX SUFFIX NAME -# --------------------------------- -# strip PREFIX and SUFFIX from NAME, and store in func_stripname_result. -# PREFIX and SUFFIX must not contain globbing or regex special -# characters, hashes, percent signs, but SUFFIX may contain a leading -# dot (in which case that matches only a dot). -if test yes = "$_G_HAVE_XSI_OPS"; then - eval 'func_stripname () - { - $debug_cmd - - # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are - # positional parameters, so assign one to ordinary variable first. - func_stripname_result=$3 - func_stripname_result=${func_stripname_result#"$1"} - func_stripname_result=${func_stripname_result%"$2"} - }' -else - func_stripname () - { - $debug_cmd - - case $2 in - .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;; - *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;; - esac - } -fi - - -# func_show_eval CMD [FAIL_EXP] -# ----------------------------- -# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is -# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP -# is given, then evaluate it. -func_show_eval () -{ - $debug_cmd - - _G_cmd=$1 - _G_fail_exp=${2-':'} - - func_quote_for_expand "$_G_cmd" - eval "func_notquiet $func_quote_for_expand_result" - - $opt_dry_run || { - eval "$_G_cmd" - _G_status=$? - if test 0 -ne "$_G_status"; then - eval "(exit $_G_status); $_G_fail_exp" - fi - } -} - - -# func_show_eval_locale CMD [FAIL_EXP] -# ------------------------------------ -# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is -# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP -# is given, then evaluate it. Use the saved locale for evaluation. -func_show_eval_locale () -{ - $debug_cmd - - _G_cmd=$1 - _G_fail_exp=${2-':'} - - $opt_quiet || { - func_quote_for_expand "$_G_cmd" - eval "func_echo $func_quote_for_expand_result" - } - - $opt_dry_run || { - eval "$_G_user_locale - $_G_cmd" - _G_status=$? - eval "$_G_safe_locale" - if test 0 -ne "$_G_status"; then - eval "(exit $_G_status); $_G_fail_exp" - fi - } -} - - -# func_tr_sh -# ---------- -# Turn $1 into a string suitable for a shell variable name. -# Result is stored in $func_tr_sh_result. All characters -# not in the set a-zA-Z0-9_ are replaced with '_'. Further, -# if $1 begins with a digit, a '_' is prepended as well. -func_tr_sh () -{ - $debug_cmd - - case $1 in - [0-9]* | *[!a-zA-Z0-9_]*) - func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'` - ;; - * ) - func_tr_sh_result=$1 - ;; - esac -} - - -# func_verbose ARG... -# ------------------- -# Echo program name prefixed message in verbose mode only. -func_verbose () -{ - $debug_cmd - - $opt_verbose && func_echo "$*" - - : -} - - -# func_warn_and_continue ARG... -# ----------------------------- -# Echo program name prefixed warning message to standard error. -func_warn_and_continue () -{ - $debug_cmd - - $require_term_colors - - func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2 -} - - -# func_warning CATEGORY ARG... -# ---------------------------- -# Echo program name prefixed warning message to standard error. Warning -# messages can be filtered according to CATEGORY, where this function -# elides messages where CATEGORY is not listed in the global variable -# 'opt_warning_types'. -func_warning () -{ - $debug_cmd - - # CATEGORY must be in the warning_categories list! - case " $warning_categories " in - *" $1 "*) ;; - *) func_internal_error "invalid warning category '$1'" ;; - esac - - _G_category=$1 - shift - - case " $opt_warning_types " in - *" $_G_category "*) $warning_func ${1+"$@"} ;; - esac -} - - -# func_sort_ver VER1 VER2 -# ----------------------- -# 'sort -V' is not generally available. -# Note this deviates from the version comparison in automake -# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a -# but this should suffice as we won't be specifying old -# version formats or redundant trailing .0 in bootstrap.conf. -# If we did want full compatibility then we should probably -# use m4_version_compare from autoconf. -func_sort_ver () -{ - $debug_cmd - - printf '%s\n%s\n' "$1" "$2" \ - | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n -} - -# func_lt_ver PREV CURR -# --------------------- -# Return true if PREV and CURR are in the correct order according to -# func_sort_ver, otherwise false. Use it like this: -# -# func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..." -func_lt_ver () -{ - $debug_cmd - - test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q` -} - - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: -#! /bin/sh - -# Set a version string for this script. -scriptversion=2014-01-07.03; # UTC - -# A portable, pluggable option parser for Bourne shell. -# Written by Gary V. Vaughan, 2010 - -# Copyright (C) 2010-2014 Free Software Foundation, Inc. -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Please report bugs or propose patches to gary@gnu.org. - - -## ------ ## -## Usage. ## -## ------ ## - -# This file is a library for parsing options in your shell scripts along -# with assorted other useful supporting features that you can make use -# of too. -# -# For the simplest scripts you might need only: -# -# #!/bin/sh -# . relative/path/to/funclib.sh -# . relative/path/to/options-parser -# scriptversion=1.0 -# func_options ${1+"$@"} -# eval set dummy "$func_options_result"; shift -# ...rest of your script... -# -# In order for the '--version' option to work, you will need to have a -# suitably formatted comment like the one at the top of this file -# starting with '# Written by ' and ending with '# warranty; '. -# -# For '-h' and '--help' to work, you will also need a one line -# description of your script's purpose in a comment directly above the -# '# Written by ' line, like the one at the top of this file. -# -# The default options also support '--debug', which will turn on shell -# execution tracing (see the comment above debug_cmd below for another -# use), and '--verbose' and the func_verbose function to allow your script -# to display verbose messages only when your user has specified -# '--verbose'. -# -# After sourcing this file, you can plug processing for additional -# options by amending the variables from the 'Configuration' section -# below, and following the instructions in the 'Option parsing' -# section further down. - -## -------------- ## -## Configuration. ## -## -------------- ## - -# You should override these variables in your script after sourcing this -# file so that they reflect the customisations you have added to the -# option parser. - -# The usage line for option parsing errors and the start of '-h' and -# '--help' output messages. You can embed shell variables for delayed -# expansion at the time the message is displayed, but you will need to -# quote other shell meta-characters carefully to prevent them being -# expanded when the contents are evaled. -usage='$progpath [OPTION]...' - -# Short help message in response to '-h' and '--help'. Add to this or -# override it after sourcing this library to reflect the full set of -# options your script accepts. -usage_message="\ - --debug enable verbose shell tracing - -W, --warnings=CATEGORY - report the warnings falling in CATEGORY [all] - -v, --verbose verbosely report processing - --version print version information and exit - -h, --help print short or long help message and exit -" - -# Additional text appended to 'usage_message' in response to '--help'. -long_help_message=" -Warning categories include: - 'all' show all warnings - 'none' turn off all the warnings - 'error' warnings are treated as fatal errors" - -# Help message printed before fatal option parsing errors. -fatal_help="Try '\$progname --help' for more information." - - - -## ------------------------- ## -## Hook function management. ## -## ------------------------- ## - -# This section contains functions for adding, removing, and running hooks -# to the main code. A hook is just a named list of of function, that can -# be run in order later on. - -# func_hookable FUNC_NAME -# ----------------------- -# Declare that FUNC_NAME will run hooks added with -# 'func_add_hook FUNC_NAME ...'. -func_hookable () -{ - $debug_cmd - - func_append hookable_fns " $1" -} - - -# func_add_hook FUNC_NAME HOOK_FUNC -# --------------------------------- -# Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must -# first have been declared "hookable" by a call to 'func_hookable'. -func_add_hook () -{ - $debug_cmd - - case " $hookable_fns " in - *" $1 "*) ;; - *) func_fatal_error "'$1' does not accept hook functions." ;; - esac - - eval func_append ${1}_hooks '" $2"' -} - - -# func_remove_hook FUNC_NAME HOOK_FUNC -# ------------------------------------ -# Remove HOOK_FUNC from the list of functions called by FUNC_NAME. -func_remove_hook () -{ - $debug_cmd - - eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`' -} - - -# func_run_hooks FUNC_NAME [ARG]... -# --------------------------------- -# Run all hook functions registered to FUNC_NAME. -# It is assumed that the list of hook functions contains nothing more -# than a whitespace-delimited list of legal shell function names, and -# no effort is wasted trying to catch shell meta-characters or preserve -# whitespace. -func_run_hooks () -{ - $debug_cmd - - case " $hookable_fns " in - *" $1 "*) ;; - *) func_fatal_error "'$1' does not support hook funcions.n" ;; - esac - - eval _G_hook_fns=\$$1_hooks; shift - - for _G_hook in $_G_hook_fns; do - eval $_G_hook '"$@"' - - # store returned options list back into positional - # parameters for next 'cmd' execution. - eval _G_hook_result=\$${_G_hook}_result - eval set dummy "$_G_hook_result"; shift - done - - func_quote_for_eval ${1+"$@"} - func_run_hooks_result=$func_quote_for_eval_result -} - - - -## --------------- ## -## Option parsing. ## -## --------------- ## - -# In order to add your own option parsing hooks, you must accept the -# full positional parameter list in your hook function, remove any -# options that you action, and then pass back the remaining unprocessed -# options in '_result', escaped suitably for -# 'eval'. Like this: -# -# my_options_prep () -# { -# $debug_cmd -# -# # Extend the existing usage message. -# usage_message=$usage_message' -# -s, --silent don'\''t print informational messages -# ' -# -# func_quote_for_eval ${1+"$@"} -# my_options_prep_result=$func_quote_for_eval_result -# } -# func_add_hook func_options_prep my_options_prep -# -# -# my_silent_option () -# { -# $debug_cmd -# -# # Note that for efficiency, we parse as many options as we can -# # recognise in a loop before passing the remainder back to the -# # caller on the first unrecognised argument we encounter. -# while test $# -gt 0; do -# opt=$1; shift -# case $opt in -# --silent|-s) opt_silent=: ;; -# # Separate non-argument short options: -# -s*) func_split_short_opt "$_G_opt" -# set dummy "$func_split_short_opt_name" \ -# "-$func_split_short_opt_arg" ${1+"$@"} -# shift -# ;; -# *) set dummy "$_G_opt" "$*"; shift; break ;; -# esac -# done -# -# func_quote_for_eval ${1+"$@"} -# my_silent_option_result=$func_quote_for_eval_result -# } -# func_add_hook func_parse_options my_silent_option -# -# -# my_option_validation () -# { -# $debug_cmd -# -# $opt_silent && $opt_verbose && func_fatal_help "\ -# '--silent' and '--verbose' options are mutually exclusive." -# -# func_quote_for_eval ${1+"$@"} -# my_option_validation_result=$func_quote_for_eval_result -# } -# func_add_hook func_validate_options my_option_validation -# -# You'll alse need to manually amend $usage_message to reflect the extra -# options you parse. It's preferable to append if you can, so that -# multiple option parsing hooks can be added safely. - - -# func_options [ARG]... -# --------------------- -# All the functions called inside func_options are hookable. See the -# individual implementations for details. -func_hookable func_options -func_options () -{ - $debug_cmd - - func_options_prep ${1+"$@"} - eval func_parse_options \ - ${func_options_prep_result+"$func_options_prep_result"} - eval func_validate_options \ - ${func_parse_options_result+"$func_parse_options_result"} - - eval func_run_hooks func_options \ - ${func_validate_options_result+"$func_validate_options_result"} - - # save modified positional parameters for caller - func_options_result=$func_run_hooks_result -} - - -# func_options_prep [ARG]... -# -------------------------- -# All initialisations required before starting the option parse loop. -# Note that when calling hook functions, we pass through the list of -# positional parameters. If a hook function modifies that list, and -# needs to propogate that back to rest of this script, then the complete -# modified list must be put in 'func_run_hooks_result' before -# returning. -func_hookable func_options_prep -func_options_prep () -{ - $debug_cmd - - # Option defaults: - opt_verbose=false - opt_warning_types= - - func_run_hooks func_options_prep ${1+"$@"} - - # save modified positional parameters for caller - func_options_prep_result=$func_run_hooks_result -} - - -# func_parse_options [ARG]... -# --------------------------- -# The main option parsing loop. -func_hookable func_parse_options -func_parse_options () -{ - $debug_cmd - - func_parse_options_result= - - # this just eases exit handling - while test $# -gt 0; do - # Defer to hook functions for initial option parsing, so they - # get priority in the event of reusing an option name. - func_run_hooks func_parse_options ${1+"$@"} - - # Adjust func_parse_options positional parameters to match - eval set dummy "$func_run_hooks_result"; shift - - # Break out of the loop if we already parsed every option. - test $# -gt 0 || break - - _G_opt=$1 - shift - case $_G_opt in - --debug|-x) debug_cmd='set -x' - func_echo "enabling shell trace mode" - $debug_cmd - ;; - - --no-warnings|--no-warning|--no-warn) - set dummy --warnings none ${1+"$@"} - shift - ;; - - --warnings|--warning|-W) - test $# = 0 && func_missing_arg $_G_opt && break - case " $warning_categories $1" in - *" $1 "*) - # trailing space prevents matching last $1 above - func_append_uniq opt_warning_types " $1" - ;; - *all) - opt_warning_types=$warning_categories - ;; - *none) - opt_warning_types=none - warning_func=: - ;; - *error) - opt_warning_types=$warning_categories - warning_func=func_fatal_error - ;; - *) - func_fatal_error \ - "unsupported warning category: '$1'" - ;; - esac - shift - ;; - - --verbose|-v) opt_verbose=: ;; - --version) func_version ;; - -\?|-h) func_usage ;; - --help) func_help ;; - - # Separate optargs to long options (plugins may need this): - --*=*) func_split_equals "$_G_opt" - set dummy "$func_split_equals_lhs" \ - "$func_split_equals_rhs" ${1+"$@"} - shift - ;; - - # Separate optargs to short options: - -W*) - func_split_short_opt "$_G_opt" - set dummy "$func_split_short_opt_name" \ - "$func_split_short_opt_arg" ${1+"$@"} - shift - ;; - - # Separate non-argument short options: - -\?*|-h*|-v*|-x*) - func_split_short_opt "$_G_opt" - set dummy "$func_split_short_opt_name" \ - "-$func_split_short_opt_arg" ${1+"$@"} - shift - ;; - - --) break ;; - -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; - *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; - esac - done - - # save modified positional parameters for caller - func_quote_for_eval ${1+"$@"} - func_parse_options_result=$func_quote_for_eval_result -} - - -# func_validate_options [ARG]... -# ------------------------------ -# Perform any sanity checks on option settings and/or unconsumed -# arguments. -func_hookable func_validate_options -func_validate_options () -{ - $debug_cmd - - # Display all warnings if -W was not given. - test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" - - func_run_hooks func_validate_options ${1+"$@"} - - # Bail if the options were screwed! - $exit_cmd $EXIT_FAILURE - - # save modified positional parameters for caller - func_validate_options_result=$func_run_hooks_result -} - - - - -# slingshot_options_prep -# ---------------------- -# Preparation for additional slingshot option parsing. -slingshot_options_prep () -{ - $debug_cmd - - # Option defaults: - opt_skip_rock_checks=false - # opt_luarocks_tree default in *unset*! - - # Extend the existing usage message. - usage_message=$usage_message' -Slingshot Options: - - --luarocks-tree=DIR - check a non-default tree for prerequisite rocks - --skip-rock-checks - ignore Lua rocks in bootstrap.conf:buildreq' - - func_quote_for_eval ${1+"$@"} - slingshot_options_prep_result=$func_quote_for_eval_result -} -func_add_hook func_options_prep slingshot_options_prep - - -# slingshot_parse_options OPT... -# ------------------------------ -# Called at the end of each main option parse loop to process any -# additional slingshot options. -slingshot_parse_options () -{ - $debug_cmd - - # Perform our own loop to consume as many options as possible in - # each iteration. - while test $# -gt 0; do - _G_opt=$1 - shift - case $_G_opt in - --luarocks-tree) - test $# = 0 && func_missing_arg $_G_opt && break - opt_luarocks_tree=$1 - shift - ;; - - --skip-rock-checks) - opt_skip_rock_checks=: - ;; - - # Separate optargs to long options (plugins may need this): - --*=*) func_split_equals "$_G_opt" - set dummy "$func_split_equals_lhs" \ - "$func_split_equals_rhs" ${1+"$@"} - shift - ;; - - *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; - esac - done - - # save modified positional parameters for caller - func_quote_for_eval ${1+"$@"} - slingshot_parse_options_result=$func_quote_for_eval_result -} -func_add_hook func_parse_options slingshot_parse_options - - -# slingshot_option_validation -# --------------------------- -# Flag any inconsistencies in users' selection of slingshot options. -slingshot_option_validation () -{ - $debug_cmd - - test -z "$opt_luarocks_tree" \ - || test -d "$opt_luarocks_tree" \ - || func_fatal_help "$opt_luarocks_tree: not a directory" -} -func_add_hook func_validate_options slingshot_option_validation - - -## ----------------- ## -## Helper functions. ## -## ----------------- ## - -# This section contains the helper functions used by the rest of the -# hookable option parser framework in ascii-betical order. - - -# func_fatal_help ARG... -# ---------------------- -# Echo program name prefixed message to standard error, followed by -# a help hint, and exit. -func_fatal_help () -{ - $debug_cmd - - eval \$ECHO \""Usage: $usage"\" - eval \$ECHO \""$fatal_help"\" - func_error ${1+"$@"} - exit $EXIT_FAILURE -} - - -# func_help -# --------- -# Echo long help message to standard output and exit. -func_help () -{ - $debug_cmd - - func_usage_message - $ECHO "$long_help_message" - exit 0 -} - - -# func_missing_arg ARGNAME -# ------------------------ -# Echo program name prefixed message to standard error and set global -# exit_cmd. -func_missing_arg () -{ - $debug_cmd - - func_error "Missing argument for '$1'." - exit_cmd=exit -} - - -# func_split_equals STRING -# ------------------------ -# Set func_split_equals_lhs and func_split_equals_rhs shell variables after -# splitting STRING at the '=' sign. -test -z "$_G_HAVE_XSI_OPS" \ - && (eval 'x=a/b/c; - test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ - && _G_HAVE_XSI_OPS=yes - -if test yes = "$_G_HAVE_XSI_OPS" -then - # This is an XSI compatible shell, allowing a faster implementation... - eval 'func_split_equals () - { - $debug_cmd - - func_split_equals_lhs=${1%%=*} - func_split_equals_rhs=${1#*=} - test "x$func_split_equals_lhs" = "x$1" \ - && func_split_equals_rhs= - }' -else - # ...otherwise fall back to using expr, which is often a shell builtin. - func_split_equals () - { - $debug_cmd - - func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'` - func_split_equals_rhs= - test "x$func_split_equals_lhs" = "x$1" \ - || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'` - } -fi #func_split_equals - - -# func_split_short_opt SHORTOPT -# ----------------------------- -# Set func_split_short_opt_name and func_split_short_opt_arg shell -# variables after splitting SHORTOPT after the 2nd character. -if test yes = "$_G_HAVE_XSI_OPS" -then - # This is an XSI compatible shell, allowing a faster implementation... - eval 'func_split_short_opt () - { - $debug_cmd - - func_split_short_opt_arg=${1#??} - func_split_short_opt_name=${1%"$func_split_short_opt_arg"} - }' -else - # ...otherwise fall back to using expr, which is often a shell builtin. - func_split_short_opt () - { - $debug_cmd - - func_split_short_opt_name=`expr "x$1" : 'x-\(.\)'` - func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'` - } -fi #func_split_short_opt - - -# func_usage -# ---------- -# Echo short help message to standard output and exit. -func_usage () -{ - $debug_cmd - - func_usage_message - $ECHO "Run '$progname --help |${PAGER-more}' for full usage" - exit 0 -} - - -# func_usage_message -# ------------------ -# Echo short help message to standard output. -func_usage_message () -{ - $debug_cmd - - eval \$ECHO \""Usage: $usage"\" - echo - $SED -n 's|^# || - /^Written by/{ - x;p;x - } - h - /^Written by/q' < "$progpath" - echo - eval \$ECHO \""$usage_message"\" -} - - -# func_version -# ------------ -# Echo version message to standard output and exit. -func_version () -{ - $debug_cmd - - printf '%s\n' "$progname $scriptversion" - $SED -n ' - /(C)/!b go - :more - /\./!{ - N - s|\n# | | - b more - } - :go - /^# Written by /,/# warranty; / { - s|^# || - s|^# *$|| - s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2| - p - } - /^# Written by / { - s|^# || - p - } - /^warranty; /q' < "$progpath" - - exit $? -} - - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: -#! /bin/sh - -# Extract macro arguments from autotools input with GNU M4. -# Written by Gary V. Vaughan, 2010 -# -# Copyright (C) 2010-2014 Free Software Foundation, Inc. -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# Make sure we've evaluated scripts we depend on. -test -z "$progpath" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/funclib.sh -test extract-trace = "$progname" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/options-parser - -# Set a version string. -scriptversion=2014-12-03.16; # UTC - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Please report bugs or propose patches to gary@gnu.org. - - -# slingshot_copy FILENAME SRCDIR DESTDIR -# -------------------------------------- -# If option '--copy' was specified, or soft-linking SRCFILE to DESTFILE -# fails, then try to copy SRCFILE to DESTFILE (making sure to update the -# timestamp so that a series of files with dependencies can be copied -# in the right order that their timestamps won't trigger rebuilds). -slingshot_copy () -{ - $debug_cmd - - slingshot_srcfile=`echo "$2/$1" |sed -e 's|/\./|/|g'` - slingshot_destfile=`echo "$3/$1" |sed -e 's|/\./|/|g'` - - $opt_force || { - # Nothing to do if the files are already identical. - if func_cmp_s "$slingshot_srcfile" "$slingshot_destfile"; then - func_verbose "'$slingshot_destfile' is up to date." - return 0 - fi - } - - # Require --force to remove existing $slingshot_destfile. - $opt_force && $RM "$slingshot_destfile" - test -f "$slingshot_destfile" && { - func_warn_and_continue "'$slingshot_destfile' exists: use '--force' to overwrite" - return 0 - } - - # Be careful to support 'func_copy dir/target srcbase destbase'. - func_dirname "$slingshot_destfile" - func_mkdir_p "$func_dirname_result" - - # Copy or link according to '--copy' option. - if $opt_copy; then - slingshot_copycmd=$CP - slingshot_copy_type=copying - else - slingshot_copycmd=$LN_S - slingshot_copy_type=linking - - func_relative_path "$3" "$2" - slingshot_srcfile=$func_relative_path_result/$1 - fi - slingshot_copy_msg="$slingshot_copy_type file '$slingshot_destfile'" - $opt_verbose && \ - slingshot_copy_msg="$slingshot_copy_type $slingshot_srcfile $3" - - if $opt_dry_run || { - ( umask 0 - $slingshot_copycmd "$slingshot_srcfile" "$slingshot_destfile" - ) >/dev/null 2>&1 - } - then - echo "$slingshot_copy_msg" - else - func_error "$slingshot_copy_type '$2/$1' to '$3/' failed" - return 1 - fi -} - - -# slingshot_rockspec_error -# ------------------------ -# Called by zile_check_rockspecs for missing rocks. -slingshot_rockspec_error () -{ - $debug_cmd - - _G_strippedver=`expr "$_G_reqver" : '=*\(.*\)'` - func_error "\ -Prerequisite LuaRock '$_G_rock $_G_strippedver' not found. Please install it." - - rockspecs_uptodate_result=false -} - - -## ------ ## -## Usage. ## -## ------ ## - -# Run './extract-trace --help' for help with using this script from the -# command line. -# -# Or source first 'options-parser' and then this file into your own -# scripts in order to make use of the function and variable framework -# they define, and also to avoid the overhead of forking to run this -# script in its own process on every call. - - - -## ----------------- ## -## Helper functions. ## -## ----------------- ## - -# This section contains the helper functions used by the rest of -# 'extract-trace'. - - -# func_autoconf_configure MAYBE-CONFIGURE-FILE -# -------------------------------------------- -# Ensure that MAYBE-CONFIGURE-FILE is the name of a file in the current -# directory that contains an uncommented call to AC_INIT. -func_autoconf_configure () -{ - $debug_cmd - - _G_sed_no_comment=' - s|#.*$|| - s|^dnl .*$|| - s| dnl .*$||' - _G_ac_init= - - # If we were passed a genuine file, make sure it calls AC_INIT. - test -f "$1" \ - && _G_ac_init=`$SED "$_G_sed_no_comment" "$1" |$GREP AC_INIT` - - # Otherwise it is not a genuine Autoconf input file. - test -n "$_G_ac_init" - _G_status=$? - - test 0 -ne "$_G_status" \ - && func_verbose "'$1' not using Autoconf" - - (exit $_G_status) -} - - -# func_tool_version_output CMD [FATAL-ERROR-MSG] -# ---------------------------------------------- -# Attempt to run 'CMD --version', discarding errors. The output can be -# ignored by redirecting stdout, and this function used simply to test -# whether the command exists and exits normally when passed a -# '--version' argument. -# When FATAL-ERROR-MSG is given, then this function will display the -# message and exit if running 'CMD --version' returns a non-zero exit -# status. -func_tool_version_output () -{ - $debug_cmd - - _G_cmd=$1 - _G_fatal_error_msg=$2 - - # Some tools, like 'git2cl' produce thousands of lines of output - # unless stdin is /dev/null - in that case we want to return - # successfully without saving all of that output. Other tools, - # such as 'help2man' exit with a non-zero status when stdin comes - # from /dev/null, so we re-execute without /dev/null if that - # happens. This means that occasionally, the output from both calls - # ends up in the result, but the alternative would be to discard the - # output from one call, and hope the other produces something useful. - { $_G_cmd --version /dev/null - _G_status=$? - - test 0 -ne "$_G_status" && test -n "$_G_fatal_error_msg" \ - && func_fatal_error "$_G_fatal_error_msg" - - (exit $_G_status) -} - - -# func_tool_version_number CMD [FATAL-ERROR-MSG] -# ---------------------------------------------- -# Pass arguments to func_tool_version_output, but set -# $func_tool_version_number_result to the last dot delimited digit string -# on the first line of output. -func_tool_version_number () -{ - $debug_cmd - - _G_verout=`func_tool_version_output "$@"` - _G_status=$? - - # A version number starts with a digit following a space on the first - # line of output from `--version`. - _G_verout=`echo "$_G_verout" |sed 1q` - if test -n "$_G_verout"; then - _G_vernum=`expr "$_G_verout" : '.* \([0-9][^ ]*\)'` - fi - - if test -n "$_G_vernum"; then - printf '%s\n' "$_G_vernum" - else - printf '%s\n' "$_G_verout" - fi - - (exit $_G_status) -} - - -# func_find_tool ENVVAR NAMES... -# ------------------------------ -# Search for a required program. Use the value of ENVVAR, if set, -# otherwise find the first of the NAMES that can be run (i.e., -# supports --version). If found, set ENVVAR to the program name, -# die otherwise. -func_find_tool () -{ - $debug_cmd - - _G_find_tool_envvar=$1 - shift - _G_find_tool_names=$@ - eval "_G_find_tool_res=\$$_G_find_tool_envvar" - if test -n "$_G_find_tool_res"; then - _G_find_tool_error_prefix="\$$find_tool_envvar: " - else - _G_find_tool_res= - _G_bestver= - for _G_prog - do - _G_find_tool_save_IFS=$IFS - IFS=: - for _G_dir in $PATH; do - IFS=$_G_find_tool_save_IFS - _G_progpath=$_G_dir/$_G_prog - test -r "$_G_progpath" && { - _G_curver=`func_tool_version_number $_G_progpath` - case $_G_bestver,$_G_curver in - ,) - # first non--version responsive prog sticks! - test -n "$_G_progpath" || _G_find_tool_res=$_G_progpath - ;; - ,*) - # first --version responsive prog beats non--version responsive! - _G_find_tool_res=$_G_progpath - _G_bestver=$_G_curver - ;; - *,*) - # another --version responsive prog must be newer to beat previous one! - test "x$_G_curver" = "x$_G_bestver" \ - || func_lt_ver "$_G_curver" "$_G_bestver" \ - || { - _G_find_tool_res=$_G_progpath - _G_bestver=$_G_curver - } - ;; - esac - } - done - IFS=$_G_find_tool_save_IFS - done - fi - if test -n "$_G_find_tool_res"; then - func_tool_version_number >/dev/null $_G_find_tool_res "\ -${_G_find_tool_error_prefix}Cannot run '$_G_find_tool_res --version'" - - # Make sure the result is exported to the environment for children - # to use. - eval "$_G_find_tool_envvar=\$_G_find_tool_res" - eval "export $_G_find_tool_envvar" - else - func_error "\ -One of these is required: - $_G_find_tool_names" - fi -} - - - -## -------------------- ## -## Resource management. ## -## -------------------- ## - -# This section contains definitions for functions that each ensure a -# particular resource (a file, or a non-empty configuration variable for -# example) is available, and if appropriate to extract default values -# from pertinent package files. Where a variable already has a non- -# empty value (as set by the package's 'bootstrap.conf'), that value is -# used in preference to deriving the default. Call them using their -# associated 'require_*' variable to ensure that they are executed, at -# most, once. -# -# It's entirely deliberate that calling these functions can set -# variables that don't obey the namespace limitations obeyed by the rest -# of this file, in order that that they be as useful as possible to -# callers. - - -# require_configure_ac -# -------------------- -# Ensure that there is a 'configure.ac' or 'configure.in' file in the -# current directory that contains an uncommented call to AC_INIT, and -# that '$configure_ac' contains its name. -require_configure_ac=func_require_configure_ac -func_require_configure_ac () -{ - $debug_cmd - - test -z "$configure_ac" \ - && func_autoconf_configure configure.ac && configure_ac=configure.ac - test -z "$configure_ac" \ - && func_autoconf_configure configure.in && configure_ac=configure.in - test -z "$configure_ac" \ - || func_verbose "found '$configure_ac'" - - require_configure_ac=: -} - - -# require_gnu_m4 -# -------------- -# Search for GNU M4, and export it in $M4. -require_gnu_m4=func_require_gnu_m4 -func_require_gnu_m4 () -{ - $debug_cmd - - test -n "$M4" || { - # Find the first m4 binary that responds to --version. - func_find_tool M4 gm4 gnum4 m4 - } - - test -n "$M4" || func_fatal_error "\ -Please install GNU M4, or 'export M4=/path/to/gnu/m4'." - - func_verbose "export M4='$M4'" - - # Make sure the search result is visible to subshells - export M4 - - require_gnu_m4=: -} - - -## --------------- ## -## Core functions. ## -## --------------- ## - -# This section contains the high level functions used when calling this -# file as a script. 'func_extract_trace' is probably the only one that you -# won't want to replace if you source this file into your own script. - - -# func_extract_trace MACRO_NAMES [FILENAME]... -# -------------------------------------------- -# set '$func_extract_trace_result' to a colon delimited list of arguments -# to any of the comma separated list of MACRO_NAMES in FILENAME. If no -# FILENAME is given, then '$configure_ac' is assumed. -func_extract_trace () -{ - $debug_cmd - - $require_configure_ac - $require_gnu_m4 - - _G_m4_traces=`$ECHO "--trace=$1" |$SED 's%,% --trace=%g'` - _G_re_macros=`$ECHO "($1)" |$SED 's%,%|%g'` - _G_macros="$1"; shift - test $# -gt 0 || { - set dummy $configure_ac - shift - } - - # Generate an error if the first file is missing - <"$1" - - # Sadly, we can't use 'autom4te' tracing to extract macro arguments, - # because it complains about things we want to ignore at bootstrap - # time - like missing m4_include files; AC_PREREQ being newer than - # the installed autoconf; and returns nothing when tracing - # 'AM_INIT_AUTOMAKE' when aclocal hasn't been generated yet. - # - # The following tries to emulate a less persnickety version of (and - # due to not having to wait for Perl startup on every invocation, - # it's probably faster too): - # - # autom4te --language=Autoconf --trace=$my_macro:\$% "$@" - # - # First we give a minimal set of macro declarations to M4 to prime - # it for reading Autoconf macros, while still providing some of the - # functionality generally used at m4-time to supply dynamic - # arguments to Autocof functions, but without following - # 'm4_s?include' files. - _G_mini=' - # Initialisation. - m4_changequote([,]) - m4_define([m4_copy], [m4_define([$2], m4_defn([$1]))]) - m4_define([m4_rename], [m4_copy([$1], [$2])m4_undefine([$1])]) - - # Disable these macros. - m4_undefine([m4_dnl]) - m4_undefine([m4_include]) - m4_undefine([m4_m4exit]) - m4_undefine([m4_m4wrap]) - m4_undefine([m4_maketemp]) - - # Copy and rename macros not handled by "m4 --prefix". - m4_define([dnl], [m4_builtin([dnl])]) - m4_copy([m4_define], [m4_defun]) - m4_rename([m4_ifelse], [m4_if]) - m4_ifdef([m4_mkstemp], [m4_undefine([m4_mkstemp])]) - m4_rename([m4_patsubst], [m4_bpatsubst]) - m4_rename([m4_regexp], [m4_bregexp]) - - # "m4sugar.mini" - useful m4-time macros for dynamic arguments. - # If we discover packages that need more m4 macros defined in - # order to bootstrap correctly, add them here: - m4_define([m4_bmatch], - [m4_if([$#], 0, [], [$#], 1, [], [$#], 2, [$2], - [m4_if(m4_bregexp([$1], [$2]), -1, - [$0([$1], m4_shift3($@))], [$3])])]) - m4_define([m4_ifndef], [m4_ifdef([$1], [$3], [$2])]) - m4_define([m4_ifset], - [m4_ifdef([$1], [m4_ifval(m4_defn([$1]), [$2], [$3])], [$3])]) - m4_define([m4_require], [$1]) - m4_define([m4_shift3], [m4_shift(m4shift(m4shift($@)))]) - - # "autoconf.mini" - things from autoconf macros we care about. - m4_copy([m4_defun], [AC_DEFUN]) - - # Dummy definitions for the macros we want to trace. - # AM_INIT_AUTOMAKE at least produces no trace without this. - ' - - _G_save=$IFS - IFS=, - for _G_macro in $_G_macros; do - IFS=$_G_save - func_append _G_mini "AC_DEFUN([$_G_macro])$nl" - done - IFS=$_G_save - - # We discard M4's stdout, but the M4 trace output from reading our - # "autoconf.mini" followed by any other files passed to this - # function is then scanned by sed to transform it into a colon - # delimited argument list assigned to a shell variable. - _G_transform='s|#.*$||; s|^dnl .*$||; s| dnl .*$||;' - - # Unfortunately, alternation in regexp addresses doesn't work in at - # least BSD (and hence Mac OS X) sed, so we have to append a capture - # and print block for each traced macro to the sed transform script. - _G_save=$IFS - IFS=, - for _G_macro in $_G_macros; do - IFS=$_G_save - func_append _G_transform ' - /^m4trace: -1- '"$_G_macro"'/ { - s|^m4trace: -1- '"$_G_macro"'[([]*|| - s|], [[]|:|g - s|[])]*$|:| - s|\(.\):$|\1| - p - }' - done - IFS=$_G_save - - # Save the command pipeline results for further use by callers of - # this function. - func_extract_trace_result=`$ECHO "$_G_mini" \ - |$M4 -daq --prefix $_G_m4_traces - "$@" 2>&1 1>/dev/null \ - |$SED -n -e "$_G_transform"` -} - - -# func_extract_trace_first MACRO_NAMES [FILENAME]... -# -------------------------------------------------- -# Exactly like func_extract_trace, except that only the first argument -# to the first invocation of one of the comma separated MACRO_NAMES is -# returned in '$func_extract_trace_first_result'. -func_extract_trace_first () -{ - $debug_cmd - - func_extract_trace ${1+"$@"} - func_extract_trace_first_result=`$ECHO "$func_extract_trace_result" \ - |$SED -e 's|:.*$||g' -e 1q` -} - - -# func_main [ARG]... -# ------------------ -func_main () -{ - $debug_cmd - - # Configuration. - usage='$progname MACRO_NAME FILE [...]' - - long_help_message=' -The first argument to this program is the name of an autotools macro -whose arguments you want to extract by examining the files listed in the -remaining arguments using the same tool that Autoconf and Automake use, -GNU M4. - -The arguments are returned separated by colons, with each traced call -on a separate line.' - - # Option processing. - func_options "$@" - eval set dummy "$func_options_result"; shift - - # Validate remaining non-option arguments. - test $# -gt 1 \ - || func_fatal_help "not enough arguments" - - # Pass non-option arguments to extraction function. - func_extract_trace "$@" - - # Display results. - test -n "$func_extract_trace_result" \ - && $ECHO "$func_extract_trace_result" - - # The End. - exit $EXIT_SUCCESS -} - - -## --------------------------- ## -## Actually perform the trace. ## -## --------------------------- ## - -# Only call 'func_main' if this script was called directly. -test extract-trace = "$progname" && func_main "$@" - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-pattern: "20/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: - -# Set a version string for *this* script. -scriptversion=2014-11-04.13; # UTC - - -## ------------------- ## -## Hookable functions. ## -## ------------------- ## - -# After 'bootstrap.conf' has been sourced, execution proceeds by calling -# 'func_bootstrap'. Wherever a function is decorated with -# 'func_hookable func_name', you will find a matching 'func_run_hooks -# func_name', which executes all functions added with 'func_add_hook -# func_name my_func'. -# -# You might notice that many of these functions begin with a series of -# '$require_foo' lines. See the docu-comments at the start of the -# 'Resource management' section for a description of what these are. - - -# func_bootstrap [ARG]... -# ----------------------- -# All the functions called inside func_bootstrap are hookable. See the -# the individual implementations for details. -func_bootstrap () -{ - $debug_cmd - - # Save the current positional parameters to prevent them being - # corrupted by calls to 'set' in 'func_init'. - func_quote_for_eval ${1+"$@"} - _G_saved_positional_parameters=$func_quote_for_eval_result - - # Initialisation. - func_init - - # Option processing. - eval func_options "$_G_saved_positional_parameters" - - # Post-option preparation. - func_prep - - # Reconfigure the package. - func_reconfigure - - # Ensure .version is up-to-date. - func_update_dotversion - - # Finalisation. - func_fini -} - - -# func_init -# --------- -# Any early initialisations can be hooked to this function. Consider -# whether you can hook onto 'func_prep' instead, because if you hook -# any slow to execute code in here, it will also add to the time before -# './bootstrap --version' can respond. -func_hookable func_init -func_init () -{ - $debug_cmd - - func_run_hooks func_init -} - - -# func_prep -# --------- -# Function to perform preparation for remaining bootstrap process. If -# your hooked code relies on the outcome of 'func_options' hook it here -# rather than to 'func_init'. -# -# All the functions called inside func_prep are hookable. See the -# individual implementations for details. -func_hookable func_prep -func_prep () -{ - $debug_cmd - - $require_buildtools_uptodate - $require_checkout_only_file - - $require_gnulib_merge_changelog - - # Report the results of SED and GREP searches from funclib.sh. - func_verbose "GREP='$GREP'" - func_verbose "SED='$SED'" - - # fetch update files from the translation project - func_update_translations - - func_run_hooks func_prep -} - - -# func_update_translations -# ------------------------ -# Update package po files and translations. -func_hookable func_update_translations -func_update_translations () -{ - $debug_cmd - - $opt_skip_po || { - test -d po && { - $require_package - - func_update_po_files po $package || exit $? - } - - func_run_hooks func_update_translations - } -} - - -# func_reconfigure -# ---------------- -# Reconfigure the current package by running the appropriate autotools in a -# suitable order. -func_hookable func_reconfigure -func_reconfigure () -{ - $debug_cmd - - $require_automake_options - - # Automake (without 'foreign' option) requires that NEWS & README exist. - case " $automake_options " in - " foreign ") ;; - *) - func_ensure_NEWS - func_ensure_README - ;; - esac - - # Ensure ChangeLog presence. - if test -n "$gnulib_modules"; then - func_ifcontains "$gnulib_modules" gitlog-to-changelog \ - func_ensure_changelog - else - $require_gnulib_cache - if $SED -n '/^gl_MODULES(\[/,/^])$/p' $gnulib_cache 2>/dev/null | - func_grep_q gitlog-to-changelog - then - func_ensure_changelog - fi - fi - - # Released 'autopoint' has the tendency to install macros that have - # been obsoleted in current 'gnulib', so run this before 'gnulib-tool'. - func_autopoint - - # Autoreconf runs 'aclocal' before 'libtoolize', which causes spurious - # warnings if the initial 'aclocal' is confused by the libtoolized - # (or worse: out-of-date) macro directory. - func_libtoolize - - # If you need to do anything after 'gnulib-tool' is done, but before - # 'autoreconf' runs, you don't need to override this whole function, - # because 'func_gnulib_tool' is hookable. - func_gnulib_tool - - func_autoreconf - - func_run_hooks func_reconfigure -} - - -# func_gnulib_tool -# ---------------- -# Run 'gnulib-tool' to fetch gnulib modules into the current package. -# -# It's assumed that since you are using gnulib's 'bootstrap' script, -# you're also using gnulib elsewhere in your package. If not, then -# you can replace this function in 'bootstrap.conf' with: -# -# func_gnulib_tool () { :; } -# -# (although the function returns immediately if $gnulib_tool is set to -# true in any case). -func_hookable func_gnulib_tool -func_gnulib_tool () -{ - $debug_cmd - - $require_gnulib_tool - $require_libtoolize - - test true = "$gnulib_tool" || { - # bootstrap.conf written for gnulib bootstrap expects - # gnulib_tool_option_extras to which --no-changelog is appended, - # but libtool bootstrap expects you to append to gnulib_tool_options - # so that you can override the --no-changelog default: make sure we - # support both styles so users can migrate between them easily. - gnulib_tool_all_options="$gnulib_tool_options $gnulib_tool_option_extras" - - if test -n "$gnulib_modules"; then - $require_gnulib_cache - $require_gnulib_tool_base_options - - gnulib_mode=--import - - # Try not to pick up any stale values from 'gnulib-cache.m4'. - rm -f "$gnulib_cache" - - test -n "$gnulib_tool_base_options" \ - && func_append_uniq gnulib_tool_all_options " $gnulib_tool_base_options" - test -n "$gnulib_mk" \ - && func_append_uniq gnulib_tool_all_options " --makefile-name=$gnulib_mk" - test -n "$tests_base" && { - func_append_uniq gnulib_tool_all_options " --tests-base=$tests_base" - func_append_uniq gnulib_tool_all_options " --with-tests" - } - else - - # 'gnulib_modules' and others are cached in 'gnulib-cache.m4': - # Use 'gnulib --update' to fetch gnulib modules. - gnulib_mode=--update - fi - - # Add a sensible default libtool option to gnulib_tool_options. - # The embedded echo is to squash whitespace before globbing. - case `echo " "$gnulib_tool_all_options" "` in - *" --no-libtool "*|*" --libtool "*) ;; - *) if test true = "$LIBTOOLIZE"; then - func_append_uniq gnulib_tool_all_options " --no-libtool" - else - func_append_uniq gnulib_tool_all_options " --libtool" - fi - ;; - esac - - $opt_copy || func_append_uniq gnulib_tool_all_options " --symlink" - - func_append_uniq gnulib_tool_all_options " $gnulib_mode" - func_append gnulib_tool_all_options " $gnulib_modules" - - # The embedded echo is to squash whitespace before display. - gnulib_cmd=`echo $gnulib_tool $gnulib_tool_all_options` - - func_show_eval "$gnulib_cmd" 'exit $?' - - # Use 'gnulib-tool --copy-file' to install non-module files. - func_install_gnulib_non_module_files - } - - func_run_hooks func_gnulib_tool -} - - -# func_fini -# --------- -# Function to perform all finalisation for the bootstrap process. -func_hookable func_fini -func_fini () -{ - $debug_cmd - - func_gettext_configuration - func_clean_dangling_symlinks - func_clean_unused_macros - func_skip_po_recommendation - - func_run_hooks func_fini - - $require_bootstrap_uptodate - - func_echo "Done. Now you can run './configure'." -} - - -# func_gettext_configuration -# -------------------------- -# Edit configuration values into po/Makevars. -func_hookable func_gettext_configuration -func_gettext_configuration () -{ - $debug_cmd - - $require_autopoint - - test true = "$AUTOPOINT" || { - $require_copyright_holder - $require_extra_locale_categories - $require_package_bugreport - - # Escape xgettext options for sed Makevars generation below. - # We have to delete blank lines in a separate script so that we don't - # append \\\ to the penultimate line, and then delete the last empty - # line, which messes up the variable substitution later in this - # function. Note that adding a literal \\\ requires double escaping - # here, once for the execution subshell, and again for the assignment, - # which is why there are actually 12 (!!) backslashes in the script. - _G_xgettext_options=`echo "$xgettext_options$nl" |$SED '/^$/d' |$SED ' - $b - s|$| \\\\\\\\\\\\|'` - - # Create gettext configuration. - func_echo "Creating po/Makevars from po/Makevars.template ..." - $RM -f po/Makevars - $SED ' - /^EXTRA_LOCALE_CATEGORIES *=/s|=.*|= '"$extra_locale_categories"'| - /^COPYRIGHT_HOLDER *=/s|=.*|= '"$copyright_holder"'| - /^MSGID_BUGS_ADDRESS *=/s|=.*|= '"$package_bugreport"'| - /^XGETTEXT_OPTIONS *=/{ - s|$| \\| - a\ - '"$_G_xgettext_options"' \\\ - $${end_of_xgettext_options+} - } - ' po/Makevars.template >po/Makevars || exit 1 - } - - func_run_hooks func_gettext_configuration -} - - - -## The section title above is chosen for what section of bootstrap -## these functions will be merged to, so that the invocations of -## `func_add_hook` are guaranteed not to be executed until after -## the hook management functions are defined. - - -# slingshot_split_buildreq -# ------------------------ -# For convenience, let the user add rockspec requirements to $buildreq. -# Note that this is for *build-time* requirements (e.g. ldoc), so that -# make can complete without error. You should add *run-time* rockspec -# requirements (e.g. stdlib) to rockspec.conf. -slingshot_split_buildreq () -{ - $debug_cmd - - $require_rockspecs_req -} -func_add_hook func_init slingshot_split_buildreq - - -# slingshot_check_rockspecs -# ------------------------- -# Check build-time rockspecs from $buildreq are uptodate. -# It would be nice if we could rely on luarock binaries to respond to -# `--version` like GNU apps, but there is no reliable consensus, so we -# have to check installed luarock versions directly, and warn the user -# if the apps we're checking for are not somewhere along PATH. -slingshot_check_rockspecs () -{ - $debug_cmd - - $opt_skip_rock_checks && return - - $require_rockspecs_req - - _G_req= - rockspecs_uptodate_result=: - - set dummy $rockspecs_req; shift - while test $# -gt 0; do - _G_rock=$1; shift - _G_reqver=$1; shift - _G_url=$1; shift - - func_append _G_req " $_G_rock $_G_url" - - # Honor $APP variables ($LDOC, $SPECL, etc.) - _G_appvar=`echo $_G_rock |tr '[a-z]' '[A-Z]'` - eval "_G_rock=\${$_G_appvar-$_G_rock}" - - # Trust the user will ensure the binaries will arive at the - # specified location before they are needed if they set these. - if eval 'test -n "${'$_G_appvar'+set}"'; then - eval test -f '"${'$_G_appvar'}"' \ - || eval 'func_warning settings "\ -not checking whether $'$_G_appvar' has version $_G_reqver; -configure or make may fail because you set $_G_appvar, but -$'$_G_appvar' does not yet exist!"' - else - _G_instver=`$LUAROCKS ${opt_luarocks_tree+--tree=$opt_luarocks_tree} \ - show $_G_rock 2>/dev/null \ - |sed -n '/^'"$_G_rock"' .* - /{s/^'"$_G_rock"' \(.*\) - .*$/\1/p;}'` - - if test -z "$_G_instver"; then - slingshot_rockspec_error - else - func_verbose "found '$_G_rock' version $_G_instver." - - case $_G_reqver in - =*) - test "x$_G_reqver" = "x=$_G_instver" || slingshot_rockspec_error - ;; - *) - func_lt_ver "$_G_reqver" "$_G_instver" || slingshot_rockspec_error - ;; - esac - fi - fi - done - - $rockspecs_uptodate_result || { - func_strtable 0 10 48 \ - "Program" "Rockspec_URL" $_G_req - func_fatal_error "Missing rocks: -$func_strtable_result -Install missing rockspecs with: - $LUAROCKS ${opt_luarocks_tree+--tree=$opt_luarocks_tree }install \$Rockspec_URL -and then rerun bootstrap with the --luarocks-tree option set -appropriately, or if you're sure that the missing rocks will -be installed before running make by exporting: - APPNAME=/path/to/app. -" - } -} -func_add_hook func_prep slingshot_check_rockspecs - - -# slingshot_copy_files -# -------------------- -# Update files from slingshot subproject. -slingshot_copy_files () -{ - $debug_cmd - - $require_package - - test slingshot = "$package" || { - func_check_configuration slingshot_files - - $require_slingshot_submodule - - # Make sure we have the latest mkrockspecs - # (the rebootstrap rule in slingshot/GNUmakefile autoruns). - make -C slingshot build-aux/mkrockspecs - - # Update in-tree links. - for file in $slingshot_files; do - func_dirname_and_basename "./$file" - slingshot_copy "$func_basename_result" \ - "slingshot/$func_dirname_result" "$func_dirname_result" - done - } -} -func_add_hook func_prep slingshot_copy_files - - -# slingshot_ensure_changelog -# -------------------------- -# Slingshot project probably won't have a gnulib_modules list. -# So we redo the ChangeLog check against slingshot_files. -slingshot_ensure_changelog () -{ - $debug_cmd - - if test -n "$slingshot_files"; then - func_ifcontains "$slingshot_files" build-aux/gitlog-to-changelog \ - func_ensure_changelog - fi - - return 0 -} -func_add_hook func_prep slingshot_ensure_changelog - - -# slingshot_update_travis_yml -# --------------------------- -# When 'travis.yml.in' is listed in $slingshot_files, complain if -# regenerating '.travis.yml' would change it. -slingshot_update_travis_yml () -{ - $debug_cmd - - $require_git - - _G_travis_yml_in=travis.yml.in - _G_travis_yml_out=.travis.yml - - rm -f "$_G_travis_yml_out.new" - - test true = "$GIT" || { - case " "`echo $slingshot_files`" " in - *" travis.yml.in "*) - # Remove trailing blanks so as not to trip sc_trailing_blank in syntax check - test -f "$_G_travis_yml_in" && { - $slingshot_require_travis_extra_rocks - - eval `grep '^ *PACKAGE=' configure | sed 1q` - eval `grep '^ *VERSION=' configure | sed 1q` - - cat "$_G_travis_yml_in" | - sed 's| *$||' | - sed "s|@EXTRA_ROCKS@|`echo $travis_extra_rocks`|g" | - sed "s|@PACKAGE@|$PACKAGE|g" | - sed "s|@VERSION@|$VERSION|g" - - if test -f .slackid; then - read slackid < .slackid - printf '%s\n' '' 'notifications:' " slack: $slackid" - fi - } > "$_G_travis_yml_out.new" - - if test -f "$_G_travis_yml_out"; then - if func_cmp_s "$_G_travis_yml_out" "$_G_travis_yml_out.new"; then - func_verbose "$_G_travis_yml_out is up to date" - rm -f "$_G_travis_yml_out.new" - else - func_warning upgrade "\ -An updated $_G_travis_yml_out script is ready for you in -'$_G_travis_yml_out.new'. After you've verified that you want -the changes, you can update with: - mv -f $_G_travis_yml_out.new $_G_travis_yml_out" - fi - else - func_verbose "creating '$_G_travis_yml_out'" - mv -f "$_G_travis_yml_out.new" "$_G_travis_yml_out" - fi - ;; - esac - } -} -func_add_hook func_fini slingshot_update_travis_yml - - -# slingshot_check_rockstree_path -# ------------------------------ -# Show a warning at the end of bootstrap if --luarocks-tree was passed -# set, but $opt_luarocks_tree/bin is not in the command PATH. -slingshot_check_rockstree_path () -{ - $debug_cmd - - test -z "$rockspecs_req" || { - case :$PATH: in - *:$opt_luarocks_tree/bin:*) ;; - *) func_warning recommend \ - "If configure or make fail, try adding $opt_luarocks_tree/bin to PATH" ;; - esac - } -} -func_add_hook func_fini slingshot_check_rockstree_path - - -## --------------- ## -## Core functions. ## -## --------------- ## - -# This section contains the main functions called from the 'Hookable -# functions' (shown above), and are the ones you're most likely -# to want to replace with your own implementations in 'bootstrap.conf'. - - -# func_autopoint -# -------------- -# If this package uses gettext, then run 'autopoint'. -func_autopoint () -{ - $debug_cmd - - $require_autopoint - - test true = "$AUTOPOINT" \ - || func_show_eval "$AUTOPOINT --force" 'exit $?' -} - - -# func_libtoolize -# --------------- -# If this package uses libtool, then run 'libtoolize'. -func_libtoolize () -{ - $debug_cmd - - $require_libtoolize - - test true = "$LIBTOOLIZE" || { - _G_libtoolize_options= - $opt_copy && func_append _G_libtoolize_options " --copy" - $opt_force && func_append _G_libtoolize_options " --force" - $opt_verbose || func_append _G_libtoolize_options " --quiet" - func_show_eval "$LIBTOOLIZE$_G_libtoolize_options" 'exit $?' - } -} - - -# func_gnulib_tool_copy_file SRC DEST -# ----------------------------------- -# Copy SRC, a path relative to the gnulib sub-tree, to DEST, a path -# relative to the top-level source directory using gnulib-tool so that -# any patches or replacements in $local_gl_dir are applied. -func_gnulib_tool_copy_file () -{ - $debug_cmd - - $require_gnulib_tool - $require_patch - - if test true = "$gnulib_tool"; then - # If gnulib-tool is not available (e.g. bootstrapping in a - # distribution tarball), make sure that at least we have some - # version of the required file already in place. - test -f "$2" || func_fatal_error "\ -Can't find, copy or download '$2', a required -gnulib supplied file, please provide the location of a -complete 'gnulib' tree by setting 'gnulib_path' in your -'bootstrap.conf' or with the '--gnulib-srcdir' option - -or else specify the location of your 'git' binary by -setting 'GIT' in the environment so that a fresh -'gnulib' submodule can be cloned." - else - $require_gnulib_copy_cmd - - $gnulib_copy_cmd $1 $2 2>/dev/null || { - $require_gnulib_path - - func_error "'$gnulib_path/$1' does not exist" - return 1 - } - fi -} - - -# func_install_gnulib_non_module_files -# ------------------------------------ -# Get additional non-module files from gnulib, overriding existing files. -func_install_gnulib_non_module_files () -{ - $debug_cmd - - $require_build_aux - $require_gnulib_tool - - test -n "$gnulib_non_module_files" && { - maybe_exit_cmd=: - - for file in $gnulib_non_module_files; do - case $file in - */COPYING*) dest=COPYING;; - */INSTALL) dest=INSTALL;; - build-aux/missing) dest= - func_warning settings "\ -Please remove build-aux/missing from gnulib_module_files in -'bootstrap.conf', as it may clash with Automake's version." - ;; - build-aux/*) dest=$build_aux/`expr "$file" : 'build-aux/\(.*\)'`;; - *) dest=$file;; - esac - - # Be sure to show all copying errors before bailing out - test -z "$dest" \ - || func_gnulib_tool_copy_file "$file" "$dest" \ - || maybe_exit_cmd="exit $EXIT_FAILURE" - done - - $maybe_exit_cmd - } -} - - -# func_ensure_changelog -# --------------------- -# Even with 'gitlog-to-changelog' generated ChangeLogs, automake -# will not run to completion with no ChangeLog file. -func_ensure_changelog () -{ - $debug_cmd - - test -f ChangeLog && mv -f ChangeLog ChangeLog~ - - cat >ChangeLog <<'EOT' -## ---------------------- ## -## DO NOT EDIT THIS FILE! ## -## ---------------------- ## - -ChangeLog is generated by gitlog-to-changelog. -EOT - - _G_message="creating dummy 'ChangeLog'" - test -f ChangeLog~ \ - && func_append _G_message ' (backup in ChangeLog~)' - func_verbose "$_G_message" - - return 0 -} - - -# func_ensure_NEWS -# ---------------- -# Without AM_INIT_AUTOMAKE([foreign]), automake will not run to -# completion with no NEWS file, even though NEWS.md or NEWS.txt -# is often preferable. -func_ensure_NEWS () -{ - $debug_cmd - - test -f NEWS || { - _G_NEWS= - for _G_news in NEWS.txt NEWS.md NEWS.rst; do - test -f "$_G_news" && break - done - - test -f "$_G_news" && $LN_S $_G_news NEWS - func_verbose "$LN_S $_G_news NEWS" - } - - return 0 -} - - -# func_ensure_README -# ------------------ -# Without AM_INIT_AUTOMAKE([foreign]), automake will not run to -# completion with no README file, even though README.md or README.txt -# is often preferable. -func_ensure_README () -{ - $debug_cmd - - test -f README || { - _G_README= - for _G_readme in README.txt README.md README.rst; do - test -f "$_G_readme" && break - done - - test -f "$_G_readme" && $LN_S $_G_readme README - func_verbose "$LN_S $_G_readme README" - } - - return 0 -} - - -# func_autoreconf [SUBDIR] -# ------------------------ -# Being careful not to re-run 'autopoint' or 'libtoolize', and not to -# try to run 'autopoint', 'libtoolize' or 'autoheader' on packages that -# don't use them, defer to 'autoreconf' for execution of the remaining -# autotools to bootstrap this package. -# -# Projects with multiple trees to reconfigure can hook another call to -# this function onto func_reconfigure: -# -# my_autoreconf_foo () -# { -# func_autoreconf foo -# } -# func_add_hook func_reconfigure my_autoreconf_foo -func_autoreconf () -{ - $debug_cmd - - $require_autoheader - $require_build_aux # automake and others put files in here - $require_macro_dir # aclocal and others put files in here - - # We ran these manually already, and autoreconf won't exec ':' - save_AUTOPOINT=$AUTOPOINT; AUTOPOINT=true - save_LIBTOOLIZE=$LIBTOOLIZE; LIBTOOLIZE=true - - _G_autoreconf_options= - $opt_copy || func_append _G_autoreconf_options " --symlink" - $opt_force && func_append _G_autoreconf_options " --force" - $opt_verbose && func_append _G_autoreconf_options " --verbose" - func_show_eval "$AUTORECONF$_G_autoreconf_options --install${1+ $1}" 'exit $?' - - AUTOPOINT=$save_AUTOPOINT - LIBTOOLIZE=$save_LIBTOOLIZE -} - - -# func_check_configuration VARNAME [CONFIGURE_MACRO] -# -------------------------------------------------- -# Exit with a suitable diagnostic for an important configuration change -# that needs to be made before bootstrap can run correctly. -func_check_configuration () -{ - $debug_cmd - - $require_configure_ac - - eval 'test -n "$'$1'"' || { - _G_error_msg="please set '$1' in 'bootstrap.conf'" - if test -n "$configure_ac" && test -n "$2"; then - func_append _G_error_msg " -or add the following (or similar) to your '$configure_ac': -$2" - fi - - func_fatal_error "$_G_error_msg" - } -} - - -# func_clean_dangling_symlinks -# ---------------------------- -# Remove any dangling symlink matching "*.m4" or "*.[ch]" in some -# gnulib-populated directories. Such .m4 files would cause aclocal to -# fail. The following requires GNU find 4.2.3 or newer. Considering -# the usual portability constraints of this script, that may seem a very -# demanding requirement, but it should be ok. Ignore any failure, -# which is fine, since this is only a convenience to help developers -# avoid the relatively unusual case where a symlinked-to .m4 file is -# git-removed from gnulib between successive runs of this script. -func_clean_dangling_symlinks () -{ - $debug_cmd - - $require_macro_dir - $require_source_base - - func_verbose "cleaning dangling symlinks" - - find "$macro_dir" "$source_base" \ - -depth \( -name '*.m4' -o -name '*.[ch]' \) \ - -type l -xtype l -delete > /dev/null 2>&1 -} - - -# func_clean_unused_macros -# ------------------------ -# Autopoint can result in over-zealously adding macros into $macro_dir -# even though they are not actually used, for example tests to help -# build the 'intl' directory even though you have specified -# 'AM_GNU_GETTEXT([external])' in your configure.ac. This function -# looks removes any macro files that can be found in gnulib, but -# are not 'm4_include'd by 'aclocal.m4'. -func_clean_unused_macros () -{ - $debug_cmd - - $require_gnulib_path - $require_macro_dir - - test -n "$gnulib_path" && test -f aclocal.m4 && { - aclocal_m4s=`find . -name aclocal.m4 -print` - - # We use 'ls|grep' instead of 'ls *.m4' to avoid exceeding - # command line length limits in some shells. - for file in `cd "$macro_dir" && ls -1 |$GREP '\.m4$'`; do - - # Remove a macro file when aclocal.m4 does not m4_include it... - func_grep_q 'm4_include([[]'$macro_dir/$file'])' $aclocal_m4s \ - || test ! -f "$gnulib_path/m4/$file" || { - - # ...and there is an identical file in gnulib... - if func_cmp_s "$gnulib_path/m4/$file" "$macro_dir/$file"; then - - # ...and it's not in the precious list ('echo' is needed - # here to squash whitespace for the match expression). - case " "`echo $gnulib_precious`" " in - *" $file "*) ;; - *) rm -f "$macro_dir/$file" - func_verbose \ - "removing unused gnulib file '$macro_dir/$file'" - esac - fi - } - done - } -} - - -# func_skip_po_recommendation -# --------------------------- -# If there is a po directory, and '--skip-po' wasn't passed, let the -# user know that they can use '--skip-po' on subsequent invocations. -func_skip_po_recommendation () -{ - $debug_cmd - - test ! -d po \ - || $opt_skip_po \ - || func_warning recommend "\ -If your pofiles are up-to-date, you can rerun bootstrap -as '$progname --skip-po' to avoid redownloading." -} - - -# func_update_dotversion -# ---------------------- -# Even with 'gitlog-to-changelog' generated ChangeLogs, automake -# will not run to completion with no ChangeLog file. -func_update_dotversion () -{ - $debug_cmd - - test -f "$build_aux/git-version-gen" && { - _G_message="updating .version" - test -f .version && { - mv .version .version~ - func_append _G_message " (backup in .version~)" - } - func_verbose "updating .version" - - $build_aux/git-version-gen dummy-arg > .version - } -} - - - -## -------------------- ## -## Resource management. ## -## -------------------- ## - -# This section contains definitions for functions that each ensure a -# particular resource (a file, or a non-empty configuration variable for -# example) is available, and if appropriate to extract default values -# from pertinent package files. Where a variable already has a non- -# empty value (as set by the package's 'bootstrap.conf'), that value is -# used in preference to deriving the default. Call them using their -# associated 'require_*' variable to ensure that they are executed, at -# most, once. - - -# require_checkout_only_file -# -------------------------- -# Bail out if this package only bootstraps properly from a repository -# checkout. -require_checkout_only_file=func_require_checkout_only_file -func_require_checkout_only_file () -{ - $debug_cmd - - $opt_force || { - test -n "$checkout_only_file" && test ! -f "$checkout_only_file" \ - && func_fatal_error "\ -Bootstrapping from a non-checked-out distribution is risky. -If you wish to bootstrap anyway, use the '--force' option." - } - - require_checkout_only_file=: -} - - -# require_aclocal_amflags -# ----------------------- -# Ensure '$aclocal_amflags' has a sensible default, extracted from -# 'Makefile.am' if necessary. -require_aclocal_amflags=func_require_aclocal_amflags -func_require_aclocal_amflags () -{ - $debug_cmd - - $require_makefile_am - - _G_sed_extract_aclocal_amflags='s|#.*$|| - /^[ ]*ACLOCAL_AMFLAGS[ ]*=/ { - s|^.*=[ ]*\(.*\)|aclocal_amflags="\1"| - p - }' - - _G_aclocal_flags_cmd=`$SED -n "$_G_sed_extract_aclocal_amflags" \ - "$makefile_am"` - eval "$_G_aclocal_flags_cmd" - - func_verbose "ACLOCAL_AMFLAGS='$aclocal_amflags'" - - require_aclocal_amflags=: -} - - -# require_autoheader -# ------------------ -# Skip autoheader if it's not needed. -require_autoheader=func_require_autoheader -func_require_autoheader () -{ - $debug_cmd - - test true = "$AUTOHEADER" || { - func_extract_trace AC_CONFIG_HEADERS - test -n "$func_extract_trace_result" \ - || func_extract_trace AC_CONFIG_HEADER - - test -n "$func_extract_trace_result" || { - AUTOHEADER=true - - func_verbose "export AUTOHEADER='$AUTOHEADER'" - - # Make sure the search result is visible to subshells - export AUTOHEADER - } - } - - require_autoheader=: -} - - -# require_automake_options -# ------------------------ -# Extract options from AM_AUTOMAKE_INIT. -require_automake_options=func_require_automake_options -func_require_automake_options () -{ - $debug_cmd - - func_extract_trace AM_INIT_AUTOMAKE - automake_options=$func_extract_trace_result - - require_automake_options=: -} - - -# require_autopoint -# ----------------- -# Skip autopoint if it's not needed. -require_autopoint=func_require_autopoint -func_require_autopoint () -{ - $debug_cmd - - test true = "$AUTOPOINT" || { - func_extract_trace AM_GNU_GETTEXT_VERSION - - test -n "$func_extract_trace_result" || { - AUTOPOINT=true - - func_verbose "export AUTOPOINT='$AUTOPOINT'" - - # Make sure the search result is visible to subshells - export AUTOPOINT - } - } - - require_autopoint=: -} - - -# require_bootstrap_uptodate -# -------------------------- -# Complain if the version of bootstrap in the gnulib directory differs -# from the one we are running. - -func_require_bootstrap_uptodate () -{ - $debug_cmd - - $require_build_aux - - _G_bootstrap_sources=" - $build_aux/bootstrap.in - $build_aux/extract-trace - $build_aux/funclib.sh - $build_aux/options-parser - " - - _G_missing_bootstrap_sources=false - for _G_src in $_G_bootstrap_sources; do - test -f "$_G_src" || _G_missing_bootstrap_sources=: - done - - if $_G_missing_bootstrap_sources; then - func_warning upgrade "\ -Please add bootstrap to your gnulib_modules list in -'bootstrap.conf', so that I can tell you when there are -updates available." - else - rm -f bootstrap.new - $build_aux/inline-source $build_aux/bootstrap.in > bootstrap.new - - if func_cmp_s "$progpath" bootstrap.new; then - rm -f bootstrap.new - func_verbose "bootstrap script up to date" - else - chmod 555 bootstrap.new - func_warning upgrade "\ -An updated bootstrap script has been generated for you in -'bootstrap.new'. After you've verified that you want -the changes, you can update with: - mv -f bootstrap.new $progname - ./$progname - -Or you can disable this check permanently by adding the -following to 'bootstrap.conf': - require_bootstrap_uptodate=:" - fi - fi - - require_bootstrap_uptodate=: -} - - -# require_build_aux -# ----------------- -# Ensure that '$build_aux' is set, and if it doesn't already point to an -# existing directory, create one. -require_build_aux=func_require_build_aux -func_require_build_aux () -{ - $debug_cmd - - test -n "$build_aux" || { - func_extract_trace_first AC_CONFIG_AUX_DIR - build_aux=$func_extract_trace_first_result - func_check_configuration build_aux \ - "AC_CONFIG_AUX_DIR([name of a directory for build scripts])" - - func_verbose "build_aux='$build_aux'" - } - - $require_vc_ignore_files - - # If the build_aux directory doesn't exist, create it now, and mark it - # as ignored for the VCS. - if test ! -d "$build_aux"; then - func_show_eval "mkdir '$build_aux'" - - test -n "$vc_ignore_files" \ - || func_insert_if_absent "$build_aux" $vc_ignore_files - fi - - require_build_aux=: -} - - -# require_buildreq_autobuild -# -------------------------- -# Try to find whether the bootstrap requires autobuild. -require_buildreq_autobuild=func_require_buildreq_autobuild -func_require_buildreq_autobuild () -{ - $debug_cmd - - $require_macro_dir - - test -f "$macro_dir/autobuild.m4" \ - || printf '%s\n' "$buildreq" |func_grep_q '^[ ]*autobuild' \ - || { - func_extract_trace AB_INIT - test -n "$func_extract_trace_result" && { - func_append buildreq 'autobuild - http://josefsson.org/autobuild/ -' - func_verbose "auto-adding 'autobuild' to build requirements" - } - } - - require_buildreq_autobuild=: -} - - -# require_buildreq_autoconf -# require_buildreq_autopoint -# require_buildreq_libtoolize -# --------------------------- -# Try to find the minimum compatible version of autoconf/libtool -# required to bootstrap successfully, and add it to '$buildreq'. -for tool in autoconf libtoolize autopoint; do - b=$tool - v=require_buildreq_${tool} - f=func_$v - case $tool in - autoconf) m=AC_PREREQ ;; - libtoolize) m=LT_PREREQ; b=libtool ;; - autopoint) m=AM_GNU_GETTEXT_VERSION b=gettext ;; - esac - - eval $v'='$f' - '$f' () - { - $debug_cmd - - # The following is ignored if undefined, but might be necessary - # in order for `func_find_tool` to run. - ${require_'$tool'-:} - - printf '\''%s\n'\'' "$buildreq" |func_grep_q '\''^[ ]*'$tool\'' || { - func_extract_trace '$m' - _G_version=$func_extract_trace_result - test -n "$_G_version" && { - func_append buildreq "\ - '$tool' $_G_version http://www.gnu.org/s/'$b' -" - func_verbose \ - "auto-adding '\'$tool'-$_G_version'\'' to build requirements" - } - } - - '$v'=: - } -' -done - - -# require_buildreq_automake -# ------------------------- -# Try to find the minimum compatible version of automake required to -# bootstrap successfully, and add it to '$buildreq'. -require_buildreq_automake=func_require_buildreq_automake -func_require_buildreq_automake () -{ - $debug_cmd - - # if automake is not already listed in $buildreq... - printf '%s\n' "$buildreq" |func_grep_q automake || { - func_extract_trace AM_INIT_AUTOMAKE - - # ...and AM_INIT_AUTOMAKE is declared... - test -n "$func_extract_trace_result" && { - automake_version=`$ECHO "$func_extract_trace_result" \ - |$SED -e 's|[^0-9]*||' -e 's| .*$||'` - test -n "$automake_version" || automake_version=- - - func_append buildreq "\ - automake $automake_version http://www.gnu.org/s/automake -" - func_verbose \ - "auto-adding 'automake-$automake_version' to build requirements" - } - } - - require_buildreq_automake=: -} - - -# require_buildreq_patch -# ---------------------- -# Automatically add a patch build-requirement if there are diff files -# in $local_gl_dir. -require_buildreq_patch=func_require_buildreq_patch -func_require_buildreq_patch () -{ - $debug_cmd - - $require_local_gl_dir - - # This ensures PATCH is set appropriately by the time - # func_check_versions enforces $buildreq. - $require_patch - - # If patch is not already listed in $buildreq... - printf '%s\n' "$buildreq" |func_grep_q '^[ ]*patch' || { - # The ugly find invocation is necessary to exit with non-zero - # status for old find binaries that don't support -exec fully. - if test ! -d "$local_gl_dir" \ - || find "$local_gl_dir" -name "*.diff" -exec false {} \; ; then : - else - func_append buildreq 'patch - http://www.gnu.org/s/patch -' - fi - } - - require_buildreq_patch=: -} - - -# require_buildtools_uptodate -# --------------------------- -# Ensure all the packages listed in BUILDREQS are available on the build -# machine at the minimum versions or better. -require_buildtools_uptodate=func_require_buildtools_uptodate -func_require_buildtools_uptodate () -{ - $debug_cmd - - $require_buildreq_autobuild - $require_buildreq_autoconf - $require_buildreq_automake - $require_buildreq_libtoolize - $require_buildreq_autopoint - $require_buildreq_patch - - test -n "$buildreq" && { - _G_error_hdr= - - func_check_versions $buildreq - $func_check_versions_result || { - test -n "$buildreq_readme" \ - && test -f "$buildreq_readme" \ - && _G_error_hdr="\ -$buildreq_readme explains how to obtain these prerequisite programs: -" - func_strtable 0 11 12 36 \ - "Program" "Min_version" "Homepage" $buildreq - func_fatal_error "$_G_error_hdr$func_strtable_result" - } - } - - require_buildtools_uptodate=: -} - - -# require_copyright_holder -# ------------------------ -# Ensure there is a sensible non-empty default value in '$copyright_holder'. -require_copyright_holder=func_require_copyright_holder -func_require_copyright_holder () -{ - $debug_cmd - - test -n "$copyright_holder" || { - copyright_holder='Free Software Foundation, Inc.' - func_warning settings "\ -Please set copyright_holder explicitly in 'bootstrap.conf'; -defaulting to '$copyright_holder'." - } - - require_copyright_holder=: -} - - -# require_doc_base -# ---------------- -# Ensure doc_base has a sensible value, extracted from 'gnulib-cache.m4' -# if possible, otherwise letting 'gnulib-tool' pick a default. -require_doc_base=func_require_doc_base -func_require_doc_base () -{ - $debug_cmd - - $require_gnulib_cache - - test -f "$gnulib_cache" && test -z "$doc_base" && { - func_extract_trace_first "gl_DOC_BASE" "$gnulib_cache" - doc_base=$func_extract_trace_first_result - - test -n "$doc_base" && func_verbose "doc_base='$doc_base'" - } - - require_doc_base=: -} - - -# require_dotgitmodules -# --------------------- -# Ensure we have a '.gitmodules' file, with appropriate 'gnulib' settings. -require_dotgitmodules=func_require_dotgitmodules -func_require_dotgitmodules () -{ - $debug_cmd - - $require_git - - test true = "$GIT" || { - # A gnulib entry in .gitmodules always takes precedence. - _G_path=`$GIT config --file .gitmodules submodule.gnulib.path 2>/dev/null` - - test -n "$_G_path" || { - $require_vc_ignore_files - - func_verbose "creating '.gitmodules'" - - # If the .gitmodules file doesn't exist, create it now, and mark - # it as ignored for the VCS. - test -n "$gnulib_path" || gnulib_path=gnulib - test -n "$gnulib_url" || gnulib_url=git://git.sv.gnu.org/gnulib - - { - echo '[submodule "gnulib"]' - echo " path = $gnulib_path" - echo " url = $gnulib_url" - } >> .gitmodules - - test -n "$vc_ignore_files" \ - || func_insert_if_absent ".gitmodules" $vc_ignore_files - } - } - - require_dotgitmodules=: -} - - -# require_extra_locale_categories -# ------------------------------- -# Ensure there is a default value in '$extra_locale_categories' -require_extra_locale_categories=func_require_extra_locale_categories -func_require_extra_locale_categories () -{ - $debug_cmd - - # Defaults to empty, so run with whatever value may have been set in - # 'bootstrap.conf'. - require_extra_locale_categories=: -} - - -# require_git -# ----------- -# Ignore git if it's not available, or we're not in a git checkout tree. -require_git=func_require_git -func_require_git () -{ - $debug_cmd - - $opt_skip_git && GIT=true - - test true = "$GIT" || { - if test -d .git/.; then - ($GIT --version) >/dev/null 2>&1 || GIT=true - fi - } - - func_verbose "GIT='$GIT'" - - require_git=: -} - - -# require_gnulib_cache -# -------------------- -# Ensure there is a non-empty default for '$gnulib_cache', and that it -# names an existing file. -require_gnulib_cache=func_require_gnulib_cache -func_require_gnulib_cache () -{ - $debug_cmd - - $require_macro_dir - - test -n "$gnulib_cache" \ - || gnulib_cache=$macro_dir/gnulib-cache.m4 - - func_verbose "found '$gnulib_cache'" - - require_gnulib_cache=: -} - - -# require_gnulib_copy_cmd -# ----------------------- -# Only calculate the options for copying files with gnulib once. -require_gnulib_copy_cmd=func_require_gnulib_copy_cmd -func_require_gnulib_copy_cmd () -{ - $debug_cmd - - $require_gnulib_tool - $require_gnulib_tool_base_options - - gnulib_copy_cmd="$gnulib_tool $gnulib_tool_base_options --copy-file" - $opt_copy || func_append gnulib_copy_cmd " --symlink" - $opt_quiet || func_append gnulib_copy_cmd " --verbose" - - require_gnulib_copy_cmd=: -} - - -# require_gnulib_merge_changelog -# ------------------------------ -# See if we can use gnulib's git-merge-changelog merge driver. -require_gnulib_merge_changelog=func_require_gnulib_merge_changelog -func_require_gnulib_merge_changelog () -{ - $debug_cmd - - test -f ChangeLog && { - $require_git - - func_grep_q '^\(/\|\)ChangeLog$' .gitignore || test true = "$GIT" || { - if $GIT config merge.merge-changelog.driver >/dev/null; then - : - elif (git-merge-changelog --version) >/dev/null 2>&1; then - func_echo "initializing git-merge-changelog driver" - $GIT config merge.merge-changelog.name 'GNU-style ChangeLog merge driver' - $GIT config merge.merge-changelog.driver 'git-merge-changelog %O %A %B' - else - func_warning recommend \ - "Consider installing git-merge-changelog from gnulib." - fi - } - } - - require_gnulib_merge_changelog=: -} - - -# require_gnulib_mk -# ----------------- -# Ensure gnulib_mk has a sensible value, extracted from 'gnulib-cache.m4' -# if possible, otherwise letting 'gnulib-tool' pick a default. -require_gnulib_mk=func_require_gnulib_mk -func_require_gnulib_mk () -{ - $debug_cmd - - $require_gnulib_cache - - test -f "$gnulib_cache" && test -z "$gnulib_mk" && { - func_extract_trace_first "gl_MAKEFILE_NAME" "$gnulib_cache" - gnulib_mk=$func_extract_trace_first_result - - test -n "$gnulib_mk" && func_verbose "gnulib_mk='$gnulib_mk'" - } - - require_gnulib_mk=: -} - - -# require_gnulib_name -# ------------------- -# Ensure gnulib_name has a sensible value, extracted from 'gnulib-cache.m4' -# if possible, otherwise letting 'gnulib-tool' pick a default. -require_gnulib_name=func_require_gnulib_name -func_require_gnulib_name () -{ - $debug_cmd - - $require_gnulib_cache - - test -f "$gnulib_cache" && test -z "$gnulib_name" && { - func_extract_trace_first "gl_LIB" "$gnulib_cache" - gnulib_name=$func_extract_trace_first_result - - test -n "$gnulib_name" && func_verbose "gnulib_name='$gnulib_name'" - } - - require_gnulib_name=: -} - - -# require_gnulib_path -# require_gnulib_url -# ------------------- -# Ensure 'gnulib_path' and 'gnulib_url' are set. -require_gnulib_path=func_require_dotgitmodules_parameters -require_gnulib_url=func_require_dotgitmodules_parameters -func_require_dotgitmodules_parameters () -{ - $debug_cmd - - $require_git - - test true = "$GIT" && { - # If we can't find git (or if the user specified '--skip-git'), - # then use an existing gnulib directory specified with - # '--gnulib-srcdir' if possible. - test -n "$gnulib_path" \ - || test ! -x "$opt_gnulib_srcdir/gnulib-tool" \ - || gnulib_path=$opt_gnulib_srcdir - } - - - $require_dotgitmodules - - test -f .gitmodules && { - # Extract the parameters with sed, since git may be missing - test -n "$gnulib_path" \ - || gnulib_path=`$SED -e '/^.submodule "gnulib".$/,${ - /[ ]*path *= */{ - s|[ ]*||g;s|^[^=]*=||;p - } - } - d' .gitmodules |$SED 1q` - test -n "$gnulib_url" \ - || gnulib_url=`$SED -e '/^.submodule "gnulib".$/,${ - /[ ]*url *= */{ - s|[ ]*||g;s|^[^=]*=||;p - } - } - d' .gitmodules |$SED 1q` - - func_verbose "gnulib_path='$gnulib_path'" - func_verbose "gnulib_url='$gnulib_url'" - } - - require_gnulib_path=: - require_gnulib_url=: -} - - -# require_gnulib_submodule -# ------------------------ -# Ensure that there is a current gnulib submodule at '$gnulib_path'. -require_gnulib_submodule=func_require_gnulib_submodule -func_require_gnulib_submodule () -{ - $debug_cmd - - $require_git - - if test true = "$GIT"; then - func_warning recommend \ - "No 'git' found; imported gnulib modules may be outdated." - else - $require_gnulib_path - $require_gnulib_url - - if test -f .gitmodules && test -f "$gnulib_path/gnulib-tool"; then - : All present and correct. - - elif test -n "$opt_gnulib_srcdir"; then - # Older git can't clone into an empty directory. - rmdir "$gnulib_path" 2>/dev/null - func_show_eval "$GIT clone --reference '$opt_gnulib_srcdir' \ - '$gnulib_url' '$gnulib_path'" \ - || func_fatal_error "Unable to fetch gnulib submodule." - - # Without --gnulib-srcdir, and no existing checked out submodule, we - # create a new shallow clone of the remote gnulib repository. - else - trap func_cleanup_gnulib 1 2 13 15 - - shallow= - $GIT clone -h 2>&1 |func_grep_q -- --depth \ - && shallow='--depth 365' - - func_show_eval "$GIT clone $shallow '$gnulib_url' '$gnulib_path'" \ - func_cleanup_gnulib - - # FIXME: Solaris /bin/sh will try to execute '-' if any of - # these signals are caught after this. - trap - 1 2 13 15 - fi - - # Make sure we've checked out the correct revision of gnulib. - func_show_eval "$GIT submodule init -- $gnulib_path" \ - && func_show_eval "$GIT submodule update -- $gnulib_path" \ - || func_fatal_error "Unable to update gnulib submodule." - fi - - require_gnulib_submodule=: -} - - -# require_gnulib_tool -# ------------------- -# Ensure that '$gnulib_tool' is set, and points to an executable file, -# or else fall back to using the binary 'true' if the main gnulib -# files appear to have been imported already. -require_gnulib_tool=func_require_gnulib_tool -func_require_gnulib_tool () -{ - $debug_cmd - - test true = "$gnulib_tool" || { - $require_gnulib_submodule - $require_gnulib_path - - test -n "$gnulib_tool" \ - || gnulib_tool=$gnulib_path/gnulib-tool - - test -x "$gnulib_tool" || { - gnulib_tool=true - func_warning recommend \ - "No 'gnulib-tool' found; gnulib modules may be missing." - } - - test true = "$gnulib_tool" \ - || func_verbose "found '$gnulib_tool'" - } - - require_gnulib_tool=: -} - - -# require_gnulib_tool_base_options -# -------------------------------- -# Ensure that '$gnulib_tool_base_options' contains all the base options -# required according to user configuration from bootstrap.conf. -require_gnulib_tool_base_options=func_require_gnulib_tool_base_options -func_require_gnulib_tool_base_options () -{ - $debug_cmd - - $require_gnulib_tool - - gnulib_tool_base_options= - - test true = "$gnulib_tool" || { - # 'gnulib_modules' and others are maintained in 'bootstrap.conf': - # Use 'gnulib --import' to fetch gnulib modules. - $require_build_aux - test -n "$build_aux" \ - && func_append_uniq gnulib_tool_base_options " --aux-dir=$build_aux" - $require_macro_dir - test -n "$macro_dir" \ - && func_append_uniq gnulib_tool_base_options " --m4-base=$macro_dir" - $require_doc_base - test -n "$doc_base" \ - && func_append_uniq gnulib_tool_base_options " --doc-base=$doc_base" - $require_gnulib_name - test -n "$gnulib_name" \ - && func_append_uniq gnulib_tool_base_options " --lib=$gnulib_name" - $require_local_gl_dir - test -n "$local_gl_dir" \ - && func_append_uniq gnulib_tool_base_options " --local-dir=$local_gl_dir" - $require_source_base - test -n "$source_base" \ - && func_append_uniq gnulib_tool_base_options " --source-base=$source_base" - } - - require_gnulib_tool_base_options=: -} - - -# require_libtoolize -# ------------------ -# Skip libtoolize if it's not needed. -require_libtoolize=func_require_libtoolize -func_require_libtoolize () -{ - $debug_cmd - - # Unless we're not searching for libtool use by this package, set - # LIBTOOLIZE to true if none of 'LT_INIT', 'AC_PROG_LIBTOOL' and - # 'AM_PROG_LIBTOOL' are used in configure. - test true = "$LIBTOOLIZE" || { - func_extract_trace LT_INIT - test -n "$func_extract_trace_result" || func_extract_trace AC_PROG_LIBTOOL - test -n "$func_extract_trace_result" || func_extract_trace AM_PROG_LIBTOOL - test -n "$func_extract_trace_result" || LIBTOOLIZE=true - } - - test -n "$LIBTOOLIZE" || { - # Find libtoolize, named glibtoolize in Mac Ports, but prefer - # user-installed libtoolize to ancient glibtoolize shipped by - # Apple with Mac OS X when Mac Ports is not installed. - func_find_tool LIBTOOLIZE libtoolize glibtoolize - } - - test -n "$LIBTOOLIZE" || func_fatal_error "\ -Please install GNU Libtool, or 'export LIBTOOLIZE=/path/to/libtoolize'." - - func_verbose "export LIBTOOLIZE='$LIBTOOLIZE'" - - # Make sure the search result is visible to subshells - export LIBTOOLIZE - - require_libtoolize=: -} - - -# require_local_gl_dir -# -------------------- -# Ensure local_gl_dir has a sensible value, extracted from 'gnulib-cache.m4' -# if possible, otherwise letting 'gnulib-tool' pick a default. -require_local_gl_dir=func_require_local_gl_dir -func_require_local_gl_dir () -{ - $debug_cmd - - $require_gnulib_cache - - test -f "$gnulib_cache" && test -z "$local_gl_dir" && { - func_extract_trace_first "gl_LOCAL_DIR" "$gnulib_cache" - local_gl_dir=$func_extract_trace_first_result - - test -n "$local_gl_dir" && func_verbose "local_gl_dir='$local_gl_dir'" - } - - require_local_gl_dir=: -} - - -# require_macro_dir -# ----------------- -# Ensure that '$macro_dir' is set, and if it doesn't already point to an -# existing directory, create one. -require_macro_dir=func_require_macro_dir -func_require_macro_dir () -{ - $debug_cmd - - # Sometimes this is stored in 'configure.ac'. - test -n "$macro_dir" || { - # AC_CONFIG_MACRO_DIRS takes a space delimited list of directories, - # but we only care about the first one in bootstrap. - func_extract_trace_first AC_CONFIG_MACRO_DIRS - macro_dir=`expr "x$func_extract_trace_first_result" : 'x\([^ ]*\)'` - } - test -n "$macro_dir" || { - func_extract_trace_first AC_CONFIG_MACRO_DIR - macro_dir=$func_extract_trace_first_result - } - - # Otherwise we might find it in 'Makefile.am'. - test -n "$macro_dir" || { - $require_aclocal_amflags - - # Take the argument following the first '-I', if any. - _G_minus_I_seen=false - for _G_arg in $aclocal_amflags; do - case $_G_minus_I_seen,$_G_arg in - :,*) macro_dir=$_G_arg; break ;; - *,-I) _G_minus_I_seen=: ;; - *,-I*) macro_dir=`expr x$_G_arg : 'x-I\(.*\)$'`; break ;; - esac - done - } - - func_verbose "macro_dir='$macro_dir'" - - func_check_configuration macro_dir \ - "AC_CONFIG_MACRO_DIRS([name of a directory for configure m4 files])" - - $require_vc_ignore_files - - # If the macro_dir directory doesn't exist, create it now, and mark it - # as ignored for the VCS. - if test ! -d "$macro_dir"; then - mkdir "$macro_dir" || func_permissions_error "$macro_dir" - - test -n "$vc_ignore_files" \ - || func_insert_if_absent "$macro_dir" $vc_ignore_files - fi - - require_macro_dir=: -} - - -# require_makefile_am -# ------------------- -# Ensure there is a 'Makefile.am' in the current directory. -require_makefile_am=func_require_makefile_am -func_require_makefile_am () -{ - $debug_cmd - - test -n "$makefile_am" \ - || makefile_am=Makefile.am - - <"$makefile_am" - - func_verbose "found '$makefile_am'" - - require_makefile_am=: -} - - -# require_package -# --------------- -# Ensure that '$package' contains a sensible default value. -require_package=func_require_package -func_require_package () -{ - $debug_cmd - - test -n "$package" || { - $require_package_name - - package=`echo "$package_name" \ - |$SED -e 's/GNU //' \ - -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'` - } - - func_verbose "package='$package'" - - require_package=: -} - - -# require_package_bugreport -# ------------------------- -# Ensure that this has a sensible value, extracted from 'configure.ac' -# if appropriate (and possible!). -require_package_bugreport=func_require_package_bugreport -func_require_package_bugreport () -{ - $debug_cmd - - func_extract_trace AC_INIT - - save_ifs=$IFS - IFS=: - set dummy $func_extract_trace_result - IFS=$save_ifs - shift - - test -n "$package_bugreport" || package_bugreport=$3 - func_check_configuration package_bugreport \ - "AC_INIT([$package_name], [$package_version], [bug-$package@gnu.org])" - func_verbose "package_bugreport='$package_bugreport'" - - require_package_bugreport=: -} - - -# require_package_name -# -------------------- -# Ensure that this has a sensible value, extracted from 'configure.ac' -# if appropriate (and possible!). -require_package_name=func_require_package_name -func_require_package_name () -{ - $debug_cmd - - func_extract_trace AC_INIT - - save_ifs=$IFS - IFS=: - set dummy $func_extract_trace_result - IFS=$save_ifs - shift - - test -n "$package_name" || package_name=$1 - func_check_configuration package_name \ - "AC_INIT([name of your package], [package version number])" - func_verbose "package_name='$package_name'" - - require_package_name=: -} - - -# require_package_version -# ----------------------- -# Ensure that this has a sensible value, extracted from 'configure.ac' -# if appropriate (and possible!). While we might have set all the -# parameters extracted from AC_INIT at once, 'package_version' in -# particular is not necessarily available as early as the others, since -# 'git-version-gen' is often involved, and until then we can't rely on -# getting a correct version number from an AC_INIT extraction. -require_package_version=func_require_package_version -func_require_package_version () -{ - $debug_cmd - - func_extract_trace AC_INIT - - save_ifs=$IFS - IFS=: - set dummy $func_extract_trace_result - IFS=$save_ifs - shift - - test -n "$package_version" || package_version=$2 - test -n "$package_version" || { - # The embedded echo is to squash whitespace before globbing. - case " "`echo $gnulib_modules`" " in - *" git-version-gen "*) - func_fatal_error "\ -cannot \$require_package_version in bootstrap.conf before -func_gnulib_tool has installed the 'git-version-gen' script." - ;; - *) - func_check_configuration package_version \ - "AC_INIT([name of your package], [package version number])" - ;; - esac - } - func_verbose "package_version='$package_version'" - - require_package_version=: -} - - -# require_patch -# ------------- -# Find patch, according to the PATCH environment variable, or else -# searching the user's PATH. -require_patch=func_require_patch -func_require_patch () -{ - $debug_cmd - - test -n "$PATCH" || { - # Find a patch program, preferring gpatch, which is usually better - # than the vendor patch. - func_find_tool PATCH gpatch patch - } - - test -n "$PATCH" || func_fatal_error "\ -Please install GNU Patch, or 'export PATCH=/path/to/gnu/patch'." - - func_verbose "export PATCH='$PATCH'" - - # Make sure the search result is visible to subshells - export PATCH - - require_patch=: -} - - -# require_source_base -# ------------------- -# Ensure that source_base has a sensible value, extracted from -# 'gnulib-cache.m4' if possible. -require_source_base=func_require_source_base -func_require_source_base () -{ - $debug_cmd - - $require_gnulib_cache - - test -f "$gnulib_cache" && test -z "$source_base" && { - func_extract_trace_first "gl_SOURCE_BASE" "$gnulib_cache" - - source_base=$func_extract_trace_first_result - - func_verbose "source_base='$source_base'" - } - - require_source_base=: -} - - -# require_vc_ignore_files -# ----------------------- -# Ensure that '$vc_ignore' has been processed to list VCS ignore files -# in '$vc_ignore_files' -require_vc_ignore_files=func_require_vc_ignore_files -func_require_vc_ignore_files () -{ - $debug_cmd - - test -n "$vc_ignore" || vc_ignore=auto - - if test auto = "$vc_ignore" && test -z "$vc_ignore_files"; then - vc_ignore_files= - test -d .git && vc_ignore_files=.gitignore - test -d CVS && vc_ignore_files="$vc_ignore_files .cvsignore" - else - vc_ignore_files=$vc_ignore - fi - - func_verbose "vc_ignore_files='$vc_ignore_files'" - - require_vc_ignore_files=: -} - - -## ----------------- ## -## Helper functions. ## -## ----------------- ## - -# This section contains the helper functions used by the rest of 'bootstrap'. - -# func_len STRING -# --------------- -# STRING may not start with a hyphen. -if (eval 'x=123; test x${#x} = "x3"') 2>/dev/null -then - # This is an XSI compatible shell, allowing a faster implementation... - eval 'func_len () - { - $debug_cmd - - func_len_result=${#1} - }' -else - # ...otherwise fall back to using expr, which is often a shell builtin. - func_len () - { - $debug_cmd - - func_len_result=`expr "$1" : ".*" 2>/dev/null || echo 0` - } -fi - - -# func_unset VAR -# -------------- -# Portably unset VAR. -# In some shells, an 'unset VAR' statement leaves a non-zero return -# status if VAR is already unset, which might be problematic if the -# statement is used at the end of a function (thus poisoning its return -# value) or when 'set -e' is active (causing even a spurious abort of -# the script in this case). -func_unset () -{ - { eval $1=; unset $1; } -} -unset=func_unset - - -# func_cmp_s FILE1 FILE2 -# ---------------------- -# Return non-zero exit status unless FILE1 and FILE2 are identical, without -# any output at all, even error messages. -func_cmp_s () -{ - $debug_cmd - - # This function relies on non-zero exit status, which will cause the - # program to exit when running in 'set -e' mode. - $CMP "$@" >/dev/null 2>&1 -} - - -# func_grep_q EXPRESSION [FILENAME..] -# ----------------------------------- -# Check whether EXPRESSION matches any line of any listed FILENAME, -# without any output at all, even error messages. -func_grep_q () -{ - $debug_cmd - - # This function relies on non-zero exit status, which will cause the - # program to exit when running in 'set -e' mode. - $GREP "$@" >/dev/null 2>&1 -} - - -# func_ifcontains LIST MEMBER YES-CMD [NO-CMD] -# -------------------------------------------- -# If whitespace-separated LIST contains MEMBER then execute YES-CMD, -# otherwise if NO-CMD was given, execute that. -func_ifcontains () -{ - $debug_cmd - - _G_wslist=$1 - _G_member=$2 - _G_yes_cmd=$3 - _G_no_cmd=${4-":"} - - _G_found=false - for _G_item in $_G_wslist; do - test "x$_G_item" = "x$_G_member" && { - _G_found=: - break - } - done - if $_G_found; then - eval "$_G_yes_cmd" - _G_status=$? - else - eval "$_G_no_cmd" - _G_status=$? - fi - - test 0 -eq "$_G_status" || exit $_G_status -} - - -# func_strpad STR WIDTH CHAR -# -------------------------- -# Trim STR, or pad with CHAR to force a total length of WIDTH. -func_strpad () -{ - $debug_cmd - - _G_width=`expr "$2" - 1` - func_strpad_result=`$ECHO "$1" |$SED ' - :a - s|^.\{0,'"$_G_width"'\}$|&'"$3"'| - ta - '` -} - - -# func_strrpad STR WIDTH CHAR -# --------------------------- -# Trim STR, or right-justify-pad with CHAR to force a total length of -# WIDTH. -func_strrpad () -{ - $debug_cmd - - _G_width=`expr "$2" - 1` - func_strrpad_result=`$ECHO "$1" |$SED ' - :a - s|^.\{0,'"$_G_width"'\}$|'"$3"'&| - ta - '` -} - - -# func_strrow INDENT FIELD WIDTH [FIELDn WIDTHn]... -# ------------------------------------------------- -# Return a string containing each FIELD left justified to WIDTH, with -# the whole thing indented by INDENT spaces. This function is used to -# render one row of aligned columns for a table by func_strtable(). -func_strrow () -{ - $debug_cmd - - func_strrow_linelen=$1; shift - - _G_row= - while test $# -gt 0; do - func_strrow_linelen=`expr $func_strrow_linelen + $2` - func_strpad "$1" $2 " " - func_append _G_row "$func_strpad_result" - shift; shift - done - - func_strrpad "$_G_row" $func_strrow_linelen " " - func_strrow_result=$func_strrpad_result -} - - -# func_strtable INDENT WIDTH1...WIDTHn HEADER1...HEADERn FIELD1...FIELDn -# ---------------------------------------------------------------------- -# Generate a string of newline-separated rows arranged in lined-up -# columns of the given WIDTHs, with the entire table indented by INDENT -# spaces. The number of columns is determined by the number of integer -# valued WIDTH arguments following INDENT. The next set (i.e. a number -# of arguments equal to the number of WIDTH arguments) of fields are -# treated as the table's column HEADERs, and are separated from the -# remainder of the table by an indented row of '-' characters. Remaining -# arguments are each aligned below the next available header, wrapping -# to a new row as necessary. Finally another row of '-' characters is -# added to mark the end of the table. -# -# For example an unindented 3 column table with 2 rows of data would be -# generated by this call: -# -# func_strtable 3 20 10 25 \ -# Header1 Header2 Header3 \ -# Row1Col1 Row1Col2 Row1Col3 \ -# Row2Col1 Row2Col2 Row2Col3 -# -# returning the following string: -# -# " Header1 Header2 Header3 -# ------------------------------------------------------- -# Row1Col1 Row1Col2 Row1Col3 -# Row2Col1 Row2Col2 Row2Col3 -# -------------------------------------------------------" -func_strtable () -{ - $debug_cmd - - # Save the indent value, we'll need it for each row we render. - _G_indent=$1; shift - - # Collect remaining numeric args into a list for reuse between - # members of each row when we call func_strrow later. - _G_widths=$1; shift - while test 0 -lt `expr "$1" : '[1-9][0-9]*$'`; do - func_append _G_widths " $1"; shift - done - - # Extract the same number of positional parameters as there are - # width elements - we'll do the header rows separately so that - # we can insert a divider line. - _G_header=$_G_indent - for _G_width in $_G_widths; do - func_append _G_header " $1 $_G_width"; shift - done - func_strrow $_G_header - - # Strip off the indent, and make a divider with '-' chars, then - # reindent. - _G_divider=`$ECHO "$func_strrow_result" \ - |$SED 's|[^ ]|-|g - :a - s|- |--|g - ta - '` - - # Append the header and divider to the running result. - func_append func_strtable_result "\ -$func_strrow_result -$_G_divider -" - - # The remaining rows are zipped between the width values we - # unwound earlier just like the header row above. - while test $# -gt 0; do - _G_row=$_G_indent - for _G_width in $_G_widths; do - func_append _G_row " $1 $_G_width"; shift - done - func_strrow $_G_row - func_append func_strtable_result "\ -$func_strrow_result -" - done - - # Mark the end of the table with a final divider line. - func_append func_strtable_result "$_G_divider" -} - - -# func_internal_error ARG... -# -------------------------- -# Echo program name prefixed message to standard error, and exit. -func_internal_error () -{ - func_fatal_error "\ -INTERNAL: " ${1+"$@"} " - Please report this bug to 'bug-gnulib@gnu.org' - in as much detail as possible." -} - - -# func_permissions_error FILE-OR-DIRECTORY -# ---------------------------------------- -# Echo program name prefixed permissions error message to standard -# error, and exit. -func_permissions_error () -{ - $debug_cmd - - func_fatal_error "Failed to create '$1', check permissions." -} - - -# func_show_eval CMD [FAIL_EXP] -# ----------------------------- -# Unless opt_silent is true, then output CMD. Then, if opt_dryrun is -# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP -# is given, then evaluate it. -func_show_eval () -{ - $debug_cmd - - $require_term_colors - - _G_cmd=$1 - _G_fail_exp=${2-':'} - - ${opt_silent-'false'} || { - func_quote_for_eval $_G_cmd - eval func_truncate_cmd $func_quote_for_eval_result - func_echo "running: $tc_bold$func_truncate_cmd_result$tc_reset" - } - - ${opt_dry_run-'false'} || { - eval "$_G_cmd" - _G_status=$? - test 0 -eq "$_G_status" || eval "(exit $_G_status); $_G_fail_exp" - } -} - - -# func_truncate_cmd CMD [ARG]... -# ------------------------------ -# For unreasonably long commands (such as a gnulib-tool invocation with -# the full module list for import), truncate CMD after the second non- -# option ARG. -func_truncate_cmd () -{ - $debug_cmd - - _G_last_arg_opt_p=false - func_truncate_cmd_result= - - set dummy "$@"; shift - - while test $# -gt 0; do - _G_opt=$1; shift - - test -n "$func_truncate_cmd_result" \ - && func_append func_truncate_cmd_result ' ' - func_append func_truncate_cmd_result "$_G_opt" - - func_len "x$func_truncate_cmd_result" - - case $_G_opt in - -*) _G_last_arg_opt_p=: ;; - *) $_G_last_arg_opt_p \ - || test "$min_cmd_len" -gt "$func_len_result" \ - || break - _G_last_arg_opt_p=false - ;; - esac - done - - test $# -gt 0 && func_append func_truncate_cmd_result "..." -} - - -# func_gitignore_entries FILE... -# ------------------------------ -# Strip blank and comment lines to leave significant entries. -func_gitignore_entries () -{ - $debug_cmd - - $SED -e '/^#/d' -e '/^$/d' "$@" -} - - -# func_insert_if_absent STR FILE... -# --------------------------------- -# If $STR is not already on a line by itself in $FILE, insert it, at the -# start. Entries are inserted at the start of the ignore list to ensure -# existing entries starting with ! are not overridden. Such entries -# support whilelisting exceptions after a more generic blacklist pattern. -# sorting the new contents of the file and replacing $FILE with the result. -func_insert_if_absent () -{ - $debug_cmd - - str=$1 - shift - - for file - do - test -f "$file" || touch "$file" - - duplicate_entries=`func_gitignore_entries "$file" |sort |uniq -d` - test -n "$duplicate_entries" \ - && func_error "duplicate entries in $file: " $duplicate_entries - - func_grep_q "^$str\$" "$file" \ - || func_verbose "inserting '$str' into '$file'" - - linesold=`func_gitignore_entries "$file" |wc -l` - linesnew=`{ $ECHO "$str"; cat "$file"; } \ - |func_gitignore_entries |sort -u |wc -l` - test "$linesold" -eq "$linesnew" \ - || { $SED "1i\\$nl$str$nl" "$file" >"$file"T && mv "$file"T "$file"; } \ - || func_permissions_error "$file" - done -} - - -# func_get_version APP -# -------------------- -# echo the version number (if any) of APP, which is looked up along your -# PATH. -func_get_version () -{ - $debug_cmd - - _G_app=$1 - - # Rather than uncomment the sed script in-situ, strip the comments - # programatically before passing the result to $SED for evaluation. - sed_get_version=`$ECHO '# extract version within line - s|.*[v ]\{1,\}\([0-9]\{1,\}\.[.a-z0-9-]*\).*|\1| - t done - - # extract version at start of line - s|^\([0-9]\{1,\}\.[.a-z0-9-]*\).*|\1| - t done - - d - - :done - # the following essentially does s|5.005|5.5| - s|\.0*\([1-9]\)|.\1|g - p - q' \ - |$SED '/^[ ]*#.*$/d'` - - func_tool_version_output $_G_app >/dev/null - _G_status=$? - - test 0 -ne "$_G_status" \ - || $_G_app --version 2>&1 |$SED -n "$sed_get_version" - - (exit $_G_status) -} - - -# func_check_tool APP -# ------------------- -# Search PATH for an executable at APP. -func_check_tool () -{ - $debug_cmd - - func_check_tool_result= - - case $1 in - *[\\/]*) - test -x "$1" && func_check_tool_result=$1 - ;; - *) - save_IFS=$IFS - IFS=${PATH_SEPARATOR-:} - for _G_check_tool_path in $PATH; do - IFS=$save_IFS - if test -x "$_G_check_tool_path/$1"; then - func_check_tool_result=$_G_check_tool_path/$1 - break - fi - done - IFS=$save_IFS - ;; - esac -} - - -# func_check_versions APP1 VER1 URL1 ...[APPN VERN URLN] -# ------------------------------------------------------ -func_check_versions () -{ - $debug_cmd - - func_check_versions_result=: - - while test $# -gt 0; do - _G_app=$1; shift - _G_reqver=$1; shift - _G_url=$1; shift - - # Diagnose bad buildreq formatting. - case $_G_url in - [a-z]*://*) ;; # looks like a url - *) func_fatal_error "\ -'$_G_url' from the buildreq table in -'bootstrap.conf' does not look like the URL for downloading -$_G_app. Please ensure that buildreq is a strict newline -delimited list of triples; 'program min-version url'." - ;; - esac - - # Honor $APP variables ($TAR, $AUTOCONF, etc.) - _G_appvar=`echo $_G_app |tr '[a-z]' '[A-Z]'` - test TAR = "$_G_appvar" && _G_appvar=AMTAR - eval "_G_app=\${$_G_appvar-$_G_app}" - - # Fail if no version specified, but the program can't be found. - if test x- = "x$_G_reqver"; then - func_check_tool $_G_app - if test -z "$func_check_tool_result"; then - func_error "Prerequisite '$_G_app' not not found. Please install it, or -'export $_G_appvar=/path/to/$_G_app'." - func_check_versions_result=false - else - func_verbose "found '$func_check_tool_result' for $_G_appvar." - fi - else - _G_instver=`func_get_version $_G_app` - - # Fail if --version didn't work. - if test -z "$_G_instver"; then - func_error "Prerequisite '$_G_app' not found. Please install it, or -'export $_G_appvar=/path/to/$_G_app'." - func_check_versions_result=false - - # Fail if a newer version than what we have is required. - else - func_verbose "found '$_G_app' version $_G_instver." - - case $_G_reqver in - =*) - # If $buildreq version starts with '=', version must - # match the installed program exactly. - test "x$_G_reqver" = "x=$_G_instver" || { - func_error "\ - '$_G_app' version == $_G_instver is too old - 'exactly $_G_app-$_G_reqver is required" - func_check_versions_result=false - } - ;; - *) - # Otherwise, anything that is not older is a match. - func_lt_ver "$_G_reqver" "$_G_instver" || { - func_error "\ - '$_G_app' version == $_G_instver is too old - '$_G_app' version >= $_G_reqver is required" - func_check_versions_result=false - } - ;; - esac - fi - fi - done -} - - -# func_cleanup_gnulib -# ------------------- -# Recursively delete everything below the path in the global variable -# GNULIB_PATH. -func_cleanup_gnulib () -{ - $debug_cmd - - _G_status=$? - $RM -fr "$gnulib_path" - exit $_G_status -} - - -# func_download_po_files SUBDIR DOMAIN -# ------------------------------------ -func_download_po_files () -{ - $debug_cmd - - func_echo "getting translations into $1 for $2..." - _G_cmd=`printf "$po_download_command_format" "$2" "$1"` - eval "$_G_cmd" -} - - -# func_update_po_files PO_DIR DOMAIN -# ---------------------------------- -# Mirror .po files to $po_dir/.reference and copy only the new -# or modified ones into $po_dir. Also update $po_dir/LINGUAS. -# Note po files that exist locally only are left in $po_dir but will -# not be included in LINGUAS and hence will not be distributed. -func_update_po_files () -{ - $debug_cmd - - # Directory containing primary .po files. - # Overwrite them only when we're sure a .po file is new. - _G_po_dir=$1 - _G_domain=$2 - - # Mirror *.po files into this dir. - # Usually contains *.s1 checksum files. - _G_ref_po_dir=$_G_po_dir/.reference - - test -d "$_G_ref_po_dir" || mkdir $_G_ref_po_dir || return - func_download_po_files $_G_ref_po_dir $_G_domain \ - && ls "$_G_ref_po_dir"/*.po 2>/dev/null \ - |$SED -e 's|.*/||' -e 's|\.po$||' > "$_G_po_dir/LINGUAS" || return - - # Find sha1sum, named gsha1sum on MacPorts, and shasum on MacOS 10.6+. - func_find_tool SHA1SUM sha1sum gsha1sum shasum sha1 - - test -n "$SHA1SUM" || func_fatal_error "\ -Please install GNU Coreutils, or 'export SHA1SUM=/path/to/sha1sum'." - - _G_langs=`cd $_G_ref_po_dir && echo *.po|$SED 's|\.po||g'` - test '*' = "$_G_langs" && _G_langs=x - for _G_po in $_G_langs; do - case $_G_po in x) continue;; esac - _G_new_po=$_G_ref_po_dir/$_G_po.po - _G_cksum_file=$_G_ref_po_dir/$_G_po.s1 - if ! test -f "$_G_cksum_file" || - ! test -f "$_G_po_dir/$_G_po.po" || - ! $SHA1SUM -c "$_G_cksum_file" \ - < "$_G_new_po" > /dev/null; then - echo "updated $_G_po_dir/$_G_po.po..." - cp "$_G_new_po" "$_G_po_dir/$_G_po.po" \ - && $SHA1SUM < "$_G_new_po" > "$_G_cksum_file" || return - fi - done -} - - - -## --------------- ## -## Option parsing. ## -## --------------- ## - -# Hook in the functions to make sure our own options are parsed during -# the option parsing loop. - -usage='$progpath [OPTION]...' - -# Short help message in response to '-h'. Add to this in 'bootstrap.conf' -# if you accept any additional options. -usage_message="Common Bootstrap Options: - -c, --copy copy files instead of creating symbolic links. - --debug enable verbose shell tracing - -n, --dry-run print commands rather than running them - -f, --force attempt to bootstrap even if the sources seem not - to have been checked out. - --gnulib-srcdir=DIRNAME - specify a local directory where gnulib sources - reside. Use this if you already have the gnulib - sources on your machine, and don't want to waste - your bandwidth downloading them again. Defaults to - \$GNULIB_SRCDIR. - --no-warnings equivalent to '-Wnone' - --skip-git do not fetch files from remote repositories - --skip-po do not download po files. - -v, --verbose verbosely report processing - --version print version information and exit - -W, --warnings=CATEGORY - report the warnings falling in CATEGORY [all] - -h, --help print short or long help message and exit -" - -# Additional text appended to 'usage_message' in response to '--help'. -long_help_message=$long_help_message" - 'recommend' show warnings about missing recommended packages - 'settings' show warnings about missing '$progname.conf' settings - 'upgrade' show warnings about out-dated files - -If the file '$progname.conf' exists in the same directory as this -script, its contents are read as shell variables to configure the -bootstrap. - -For build prerequisites, environment variables like \$AUTOCONF and -\$AMTAR are honored. - -Running without arguments will suffice in most cases. -" - -# Warning categories used by 'bootstrap', append others if you use them -# in your 'bootstrap.conf'. -warning_categories='recommend settings upgrade' - - -# bootstrap_options_prep [ARG]... -# ------------------------------- -# Preparation for options parsed by Bootstrap. -bootstrap_options_prep () -{ - $debug_cmd - - # Option defaults: - opt_copy=${copy-'false'} - opt_dry_run=false - opt_force=false - opt_gnulib_srcdir=$GNULIB_SRCDIR - opt_skip_git=false - opt_skip_po=false - - # Pass back the list of options we consumed. - func_quote_for_eval ${1+"$@"} - bootstrap_options_prep_result=$func_quote_for_eval_result -} -func_add_hook func_options_prep bootstrap_options_prep - - -# bootstrap_parse_options [ARG]... -# -------------------------------- -# Provide handling for Bootstrap specific options. -bootstrap_parse_options () -{ - $debug_cmd - - # Perform our own loop to consume as many options as possible in - # each iteration. - while test $# -gt 0; do - _G_opt=$1 - shift - case $_G_opt in - --dry-run|--dryrun|-n) - opt_dry_run=: ;; - --copy|-c) opt_copy=: ;; - --force|-f) opt_force=: ;; - - --gnulib-srcdir) - test $# = 0 && func_missing_arg $_G_opt && break - opt_gnulib_srcdir=$1 - shift - ;; - - --skip-git|--no-git) - opt_skip_git=: - ;; - - --skip-po|--no-po) - opt_skip_po=: - ;; - - # Separate non-argument short options: - -c*|-f*|-n*) - func_split_short_opt "$_G_opt" - set dummy "$func_split_short_opt_name" \ - "-$func_split_short_opt_arg" ${1+"$@"} - shift - ;; - - *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; - esac - done - - # save modified positional parameters for caller - func_quote_for_eval ${1+"$@"} - bootstrap_parse_options_result=$func_quote_for_eval_result -} -func_add_hook func_parse_options bootstrap_parse_options - - -# bootstrap_validate_options [ARG]... -# ----------------------------------- -# Perform any sanity checks on option settings and/or unconsumed -# arguments. -bootstrap_validate_options () -{ - $debug_cmd - - # Validate options. - test $# -gt 0 \ - && func_fatal_help "too many arguments" - - # Pass back the (empty) list of unconsumed options. - func_quote_for_eval ${1+"$@"} - bootstrap_validate_options_result=$func_quote_for_eval_result -} -func_add_hook func_validate_options bootstrap_validate_options - - -## -------------------------------------------------- ## -## Source package customisations in 'bootstrap.conf'. ## -## -------------------------------------------------- ## - -# Override the default configuration, if necessary. -# Make sure that bootstrap.conf is sourced from the current directory -# if we were invoked as "sh bootstrap". -case $0 in - */*) test -r "$0.conf" && . "$0.conf" ;; - *) test -r "$0.conf" && . ./"$0.conf" ;; -esac - - -## ------------------------------- ## -## Actually perform the bootstrap. ## -## ------------------------------- ## - -func_bootstrap ${1+"$@"} - -# The End. -exit ${exit_status-$EXIT_SUCCESS} - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-pattern: "500/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: diff --git a/bootstrap.conf b/bootstrap.conf deleted file mode 100644 index 3f5dd4b..0000000 --- a/bootstrap.conf +++ /dev/null @@ -1,72 +0,0 @@ -# bootstrap.conf (Stdlib) version 2015-01-03 -# -# Copyright (C) 2013-2016 Gary V. Vaughan -# Written by Gary V. Vaughan, 2013 - -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; see the file COPYING. If not, a copy -# can be downloaded from http://www.gnu.org/licenses/gpl.html. - - -## -------------- ## -## Configuration. ## -## -------------- ## - -# List of programs, minimum versions, and software urls required to -# bootstrap, maintain and release this project. - -buildreq=' - git - http://git-scm.com - ldoc 1.4.2 http://rocks.moonscript.org/manifests/steved/ldoc-1.4.2-1.rockspec - specl 14.1.4 http://rocks.moonscript.org/manifests/gvvaughan/specl-14.1.0-1.rockspec -' - -# List of slingshot files to link into stdlib tree before autotooling. -slingshot_files=' - .autom4te.cfg - GNUmakefile - Makefile.am - build-aux/do-release-commit-and-tag - build-aux/gitlog-to-changelog - build-aux/mkrockspecs - build-aux/release.mk - build-aux/rockspecs.mk - build-aux/sanity.mk - build-aux/specl.mk - build-aux/update-copyright - m4/ax_lua.m4 - travis.yml.in -' - -# Prequisite rocks that need to be installed for travis builds to work. -travis_extra_rocks=' - ansicolors - ldoc - specl -' - -# No need to do any gnulib-tooling here. -gnulib_tool=true - - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-start: "# bootstrap.conf (Stdlib) version " -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-end: "$" -# End: diff --git a/build-aux/sanity-cfg.mk b/build-aux/sanity-cfg.mk deleted file mode 100644 index 59fa6c7..0000000 --- a/build-aux/sanity-cfg.mk +++ /dev/null @@ -1,3 +0,0 @@ -exclude_file_name_regexp--sc_error_message_uppercase = ^lib/std/list.lua$$ - -EXTRA_DIST += build-aux/sanity-cfg.mk diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 414cc84..0000000 --- a/configure.ac +++ /dev/null @@ -1,40 +0,0 @@ -dnl configure.ac -dnl -dnl Copyright (C) 2012-2016 Gary V. Vaughan -dnl Written by Gary V. Vaughan, 2012 -dnl -dnl This program is free software; you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published -dnl by the Free Software Foundation; either version 3, or (at your -dnl option) any later version. -dnl -dnl This program is distributed in the hope that it will be useful, but -dnl WITHOUT ANY WARRANTY; without even the implied warranty of -dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -dnl General Public License for more details. -dnl -dnl You should have received a copy of the GNU General Public License -dnl along with this program. If not, see . - - -dnl Initialise autoconf and automake -AC_INIT([stdlib], [42.0.0], [http://github.com/lua-stdlib/lua-stdlib/issues]) -AC_CONFIG_AUX_DIR([build-aux]) -AC_CONFIG_MACRO_DIR([m4]) - -AS_BOX([Configuring AC_PACKAGE_TARNAME AC_PACKAGE_VERSION]) -echo - -AM_INIT_AUTOMAKE([-Wall]) -m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) - -dnl Check for programs -AX_PROG_LUA([5.1], [5.4]) -AC_PATH_PROG([LDOC], [ldoc], [:]) -AC_PATH_PROG([SPECL], [specl], [:]) -AC_PROG_EGREP -AC_PROG_SED - -dnl Generate output files -AC_CONFIG_FILES([Makefile build-aux/config.ld]) -AC_OUTPUT diff --git a/build-aux/config.ld.in b/doc/config.ld.in similarity index 95% rename from build-aux/config.ld.in rename to doc/config.ld.in index 8c76ef2..2891ec4 100644 --- a/build-aux/config.ld.in +++ b/doc/config.ld.in @@ -1,6 +1,6 @@ -- -*- lua -*- -title = "@PACKAGE_STRING@ Reference" -project = "@PACKAGE_STRING@" +title = "stdlib @PACKAGE_VERSION@ Reference" +project = "stdlib @PACKAGE_VERSION@" description = [[ # Standard Lua Libraries diff --git a/lib/std/version.lua b/lib/std/version.lua new file mode 100644 index 0000000..e9a70bd --- /dev/null +++ b/lib/std/version.lua @@ -0,0 +1 @@ +return "General Lua libraries / 42.0.0" diff --git a/local.mk b/local.mk deleted file mode 100644 index 87ac6a5..0000000 --- a/local.mk +++ /dev/null @@ -1,157 +0,0 @@ -# Local Make rules. -# -# Copyright (C) 2013-2016 Gary V. Vaughan -# Written by Gary V. Vaughan, 2013 -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -## ------------ ## -## Environment. ## -## ------------ ## - -std_path = $(abs_srcdir)/lib/?.lua;$(abs_srcdir)/lib/?/init.lua -LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)" - - -## ---------- ## -## Bootstrap. ## -## ---------- ## - -old_NEWS_hash = d41d8cd98f00b204e9800998ecf8427e - -update_copyright_env = \ - UPDATE_COPYRIGHT_HOLDER='(Gary V. Vaughan|Reuben Thomas)' \ - UPDATE_COPYRIGHT_USE_INTERVALS=1 \ - UPDATE_COPYRIGHT_FORCE=1 - - -## ------------- ## -## Declarations. ## -## ------------- ## - -doccorefunctionsdir = $(docdir)/core_functions -doccorelibrariesdir = $(docdir)/core_libraries -docmodulesdir = $(docdir)/modules -docobjectsdir = $(docdir)/object_system - -dist_doc_DATA = -dist_doccorefunctions_DATA = -dist_doccorelibraries_DATA = -dist_docmodules_DATA = -dist_docobjects_DATA = - -include specs/specs.mk - - -## ------ ## -## Build. ## -## ------ ## - -luastddir = $(luadir)/std - -dist_luastd_DATA = \ - lib/std/base.lua \ - lib/std/container.lua \ - lib/std/debug.lua \ - lib/std/io.lua \ - lib/std/init.lua \ - lib/std/list.lua \ - lib/std/math.lua \ - lib/std/object.lua \ - lib/std/package.lua \ - lib/std/set.lua \ - lib/std/strbuf.lua \ - lib/std/string.lua \ - lib/std/table.lua \ - lib/std/tree.lua \ - lib/std/version.lua \ - $(NOTHING_ELSE) - -# For bugwards compatibility with LuaRocks 2.1, while ensuring that -# `require "std.debug_init"` continues to work, we have to install -# the former `$(luadir)/std/debug_init.lua` to `debug_init/init.lua`. -# When LuaRocks works again, move this file back to dist_luastd_DATA -# above and rename to debug_init.lua. - -luastddebugdir = $(luastddir)/debug_init - -dist_luastddebug_DATA = \ - lib/std/debug_init/init.lua \ - $(NOTHING_ELSE) - -lib/std/version.lua: - echo 'return "General Lua libraries / $(VERSION)"' > $@T - @test -f $@ || cp -f $@T $@ - @cmp -s $@T $@ || cp -f $@T $@ - @rm -f $@T - - -## Use a builtin rockspec build with root at $(srcdir)/lib, and note -## the github repository doesn't have the same name as the rockspec! -mkrockspecs_args = --module-dir $(srcdir)/lib --repository lua-stdlib - - -## ------------- ## -## Distribution. ## -## ------------- ## - -EXTRA_DIST += \ - build-aux/config.ld.in \ - $(NOTHING_ELSE) - - -## -------------- ## -## Documentation. ## -## -------------- ## - -doccorefunctions = $(srcdir)/doc/core_functions/std -doccorelibraries = $(srcdir)/doc/core_libraries/std -docobjects = $(srcdir)/doc/object_system/std - -dist_doc_DATA += \ - $(srcdir)/doc/index.html \ - $(srcdir)/doc/ldoc.css - -dist_doccorefunctions_DATA += \ - $(doccorefunctions).html \ - $(NOTHING_ELSE) - -dist_doccorelibraries_DATA += \ - $(doccorelibraries).debug.html \ - $(doccorelibraries).io.html \ - $(doccorelibraries).math.html \ - $(doccorelibraries).package.html \ - $(doccorelibraries).string.html \ - $(doccorelibraries).table.html \ - $(NOTHING_ELSE) - -dist_docobjects_DATA += \ - $(docobjects).container.html \ - $(docobjects).list.html \ - $(docobjects).object.html \ - $(docobjects).set.html \ - $(docobjects).strbuf.html \ - $(docobjects).tree.html \ - $(NOTHING_ELSE) - -## Parallel make gets confused when one command ($(LDOC)) produces -## multiple targets (all the html files above), so use the presence -## of the doc directory as a sentinel file. -$(dist_doc_DATA) $(dist_doccorefunctions_DATA): $(srcdir)/doc -$(dist_doccorelibraries_DATA) $(dist_docobjects_DATA): $(srcdir)/doc - -$(srcdir)/doc: $(dist_lua_DATA) $(dist_luastd_DATA) - test -d $@ || mkdir $@ - $(LDOC) -c build-aux/config.ld -d $(abs_srcdir)/doc . diff --git a/rockspec.conf b/rockspec.conf deleted file mode 100644 index 13b4060..0000000 --- a/rockspec.conf +++ /dev/null @@ -1,16 +0,0 @@ -# stdlib rockspec configuration. - -description: - homepage: http://lua-stdlib.github.io/lua-stdlib - license: MIT/X11 - summary: General Lua Libraries - detailed: - stdlib is a library of modules for common programming tasks, - including list and table operations, objects, pickling and - pretty-printing. - -dependencies: -- lua >= 5.1, < 5.4 - -source: - url: git://github.com/lua-stdlib/lua-stdlib.git diff --git a/slingshot b/slingshot deleted file mode 160000 index 9476807..0000000 --- a/slingshot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 94768075ff472181d475fcd6a5a0896c14abb6d8 diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 6b43b76..ec2a5e1 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -147,7 +147,7 @@ specify std.io: - describe process_files: - before: name = "Makefile" - names = {"Makefile", "config.log", "config.status", "build-aux/config.ld"} + names = {"LICENSE.md", "Makefile", "README.md"} ascript = [[ require "std.io".process_files (function (a) print (a) end) ]] @@ -182,7 +182,7 @@ specify std.io: 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\n4\n" + 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)). diff --git a/specs/specs.mk b/specs/specs.mk deleted file mode 100644 index 311fbb8..0000000 --- a/specs/specs.mk +++ /dev/null @@ -1,33 +0,0 @@ -# Specl specs make rules. - - -## ------ ## -## Specs. ## -## ------ ## - -## For compatibility with Specl < 11, std_spec.yaml has to be -## last, so that when `require "std"` leaks symbols into the -## Specl global environment, subsequent example blocks are not -## affected. - -specl_SPECS = \ - $(srcdir)/specs/container_spec.yaml \ - $(srcdir)/specs/debug_spec.yaml \ - $(srcdir)/specs/io_spec.yaml \ - $(srcdir)/specs/list_spec.yaml \ - $(srcdir)/specs/math_spec.yaml \ - $(srcdir)/specs/object_spec.yaml \ - $(srcdir)/specs/package_spec.yaml \ - $(srcdir)/specs/set_spec.yaml \ - $(srcdir)/specs/strbuf_spec.yaml \ - $(srcdir)/specs/string_spec.yaml \ - $(srcdir)/specs/table_spec.yaml \ - $(srcdir)/specs/tree_spec.yaml \ - $(srcdir)/specs/std_spec.yaml \ - $(NOTHING_ELSE) - -EXTRA_DIST += \ - $(srcdir)/specs/spec_helper.lua \ - $(NOTHING_ELSE) - -include build-aux/specl.mk diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index 7c70e16..cfcb4a6 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -1,21 +1,41 @@ package = "stdlib" version = "git-1" + description = { - detailed = "stdlib is a library of modules for common programming tasks, including list and table operations, objects, pickling and pretty-printing.", + summary = "General Lua Libraries", + detailed = [[ + stdlib is a library of modules for common programming tasks, + including list and table operations, objects, and pretty-printing. + ]], homepage = "http://lua-stdlib.github.io/lua-stdlib", license = "MIT/X11", - summary = "General Lua Libraries", } + source = { url = "git://github.com/lua-stdlib/lua-stdlib.git", } + dependencies = { "lua >= 5.1, < 5.4", } -external_dependencies = nil + build = { - build_command = "LUA='$(LUA)' ./bootstrap && ./configure LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' --prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' --datarootdir='$(PREFIX)' && make clean all", - copy_directories = {}, - install_command = "make install luadir='$(LUADIR)' luaexecdir='$(LIBDIR)'", - type = "command", + type = "builtin", + modules = { + std = ["lib/std/init.lua"], + ["std.base"] = ["lib/std/base.lua"], + ["std.container"] = ["lib/std/container.lua"], + ["std.debug"] = ["lib/std/debug.lua"], + ["std.debug_init"] = ["lib/std/debug_init/init.lua"], + ["std.io"] = ["lib/std/io.lua"], + ["std.list"] = ["lib/std/list.lua"], + ["std.math"] = ["lib/std/math.lua"], + ["std.object"] = ["lib/std/object.lua"], + ["std.package"] = ["lib/std/package.lua"], + ["std.set"] = ["lib/std/set.lua"], + ["std.strbuf"] = ["lib/std/strbuf.lua"], + ["std.string"] = ["lib/std/string.lua"], + ["std.table"] = ["lib/std/table.lua"], + ["std.tree"] = ["lib/std/tree.lua"], + }, } From 0460d18d1f7edebb66f92195a31182b10f83f149 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 22:11:48 +0000 Subject: [PATCH 676/703] base: rename unexported file to _base.lua. * lib/std/base.lua: Move from here... * lib/std/_base.lua: ...to here. Adjust all clients. Signed-off-by: Gary V. Vaughan --- Makefile | 2 +- lib/std/{base.lua => _base.lua} | 2 -- lib/std/container.lua | 2 +- lib/std/debug.lua | 2 +- lib/std/init.lua | 2 +- lib/std/io.lua | 2 +- lib/std/list.lua | 2 +- lib/std/math.lua | 2 +- lib/std/object.lua | 2 +- lib/std/package.lua | 2 +- lib/std/set.lua | 2 +- lib/std/strbuf.lua | 2 +- lib/std/string.lua | 2 +- lib/std/table.lua | 2 +- lib/std/tree.lua | 2 +- specs/container_spec.yaml | 2 +- 16 files changed, 15 insertions(+), 17 deletions(-) rename lib/std/{base.lua => _base.lua} (99%) diff --git a/Makefile b/Makefile index 5094bc3..04b4d4c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ SPECL = specl luadir = lib/std SOURCES = \ - $(luadir)/base.lua \ + $(luadir)/_base.lua \ $(luadir)/container.lua \ $(luadir)/debug.lua \ $(luadir)/debug_init/init.lua \ diff --git a/lib/std/base.lua b/lib/std/_base.lua similarity index 99% rename from lib/std/base.lua rename to lib/std/_base.lua index 03cafc4..c0a5602 100644 --- a/lib/std/base.lua +++ b/lib/std/_base.lua @@ -18,8 +18,6 @@ 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. - - @module std.base ]] diff --git a/lib/std/container.lua b/lib/std/container.lua index a8b479d..b213c40 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -40,7 +40,7 @@ local table_concat = table.concat local _ = { debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local Module = _.std.object.Module diff --git a/lib/std/debug.lua b/lib/std/debug.lua index cbfc287..0be7396 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -24,7 +24,7 @@ local table_concat = table.concat local _ = { debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local _DEBUG = _.debug_init._DEBUG diff --git a/lib/std/init.lua b/lib/std/init.lua index 19aad44..fa296e5 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -31,7 +31,7 @@ local string_match = string.match local _ = { debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local _DEBUG = _.debug_init._DEBUG diff --git a/lib/std/io.lua b/lib/std/io.lua index 8cfc16a..a1addd1 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -36,7 +36,7 @@ local table_insert = table.insert local _ = { debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local _DEBUG = _.debug_init._DEBUG diff --git a/lib/std/list.lua b/lib/std/list.lua index 1dc84fd..45bdd32 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -25,7 +25,7 @@ local table_unpack = table.unpack or unpack local _ = { debug_init = require "std.debug_init", object = require "std.object", - std = require "std.base", + std = require "std._base", } local Module = _.std.object.Module diff --git a/lib/std/math.lua b/lib/std/math.lua index 673bb20..0b8eedd 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -18,7 +18,7 @@ local math_floor = math.floor local _ = { debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local _DEBUG = _.debug_init._DEBUG diff --git a/lib/std/object.lua b/lib/std/object.lua index fde76c9..6059d92 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -29,7 +29,7 @@ local getmetatable = getmetatable local _ = { debug_init = require "std.debug_init", container = require "std.container", - std = require "std.base", + std = require "std._base", } local Container = _.container.prototype diff --git a/lib/std/package.lua b/lib/std/package.lua index c44ab38..565e25c 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -50,7 +50,7 @@ local table_unpack = table.unpack or unpack local _ = { debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local _DEBUG = _.debug_init._DEBUG diff --git a/lib/std/set.lua b/lib/std/set.lua index 3c48211..3b199cb 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -37,7 +37,7 @@ local table_sort = table.sort local _ = { container = require "std.container", debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local Container = _.container.prototype diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 99be646..7b7548f 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -37,7 +37,7 @@ local table_concat = table.concat local _ = { debug_init = require "std.debug_init", object = require "std.object", - std = require "std.base", + std = require "std._base", } local Module = _.std.object.Module diff --git a/lib/std/string.lua b/lib/std/string.lua index 95ab2e9..1f1b8b1 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -28,7 +28,7 @@ local string_format = string.format local _ = { debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", strbuf = require "std.strbuf", } diff --git a/lib/std/table.lua b/lib/std/table.lua index 6427103..c9eccbb 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -26,7 +26,7 @@ local table_unpack = table.unpack or unpack local _ = { debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local _DEBUG = _.debug_init._DEBUG diff --git a/lib/std/tree.lua b/lib/std/tree.lua index 1930284..bd6d8c8 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -40,7 +40,7 @@ local table_unpack = table.unpack or unpack local _ = { container = require "std.container", debug_init = require "std.debug_init", - std = require "std.base", + std = require "std._base", } local Container = _.container.prototype diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml index 7e76ef8..dbadc91 100644 --- a/specs/container_spec.yaml +++ b/specs/container_spec.yaml @@ -60,7 +60,7 @@ specify std.container: expect (objtype (v)).to_be (objtype (Container)) - context with module functions: - before: - Bag = require "std.base".object.Module { + Bag = require "std._base".object.Module { prototype = Container { _type = "Bag" }, count = function (bag) local n = 0 From 88e4a239a5c2cd3ccdd56914739c511139dab0f1 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 22:16:01 +0000 Subject: [PATCH 677/703] rockspec: fix typo. * stdlib-git-1.rockspec (build.modules): Use valid Lua syntax. Signed-off-by: Gary V. Vaughan --- stdlib-git-1.rockspec | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index cfcb4a6..10d6c8a 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -22,20 +22,20 @@ dependencies = { build = { type = "builtin", modules = { - std = ["lib/std/init.lua"], - ["std.base"] = ["lib/std/base.lua"], - ["std.container"] = ["lib/std/container.lua"], - ["std.debug"] = ["lib/std/debug.lua"], - ["std.debug_init"] = ["lib/std/debug_init/init.lua"], - ["std.io"] = ["lib/std/io.lua"], - ["std.list"] = ["lib/std/list.lua"], - ["std.math"] = ["lib/std/math.lua"], - ["std.object"] = ["lib/std/object.lua"], - ["std.package"] = ["lib/std/package.lua"], - ["std.set"] = ["lib/std/set.lua"], - ["std.strbuf"] = ["lib/std/strbuf.lua"], - ["std.string"] = ["lib/std/string.lua"], - ["std.table"] = ["lib/std/table.lua"], - ["std.tree"] = ["lib/std/tree.lua"], + std = "lib/std/init.lua", + ["std.base"] = "lib/std/_base.lua", + ["std.container"] = "lib/std/container.lua", + ["std.debug"] = "lib/std/debug.lua", + ["std.debug_init"] = "lib/std/debug_init/init.lua", + ["std.io"] = "lib/std/io.lua", + ["std.list"] = "lib/std/list.lua", + ["std.math"] = "lib/std/math.lua", + ["std.object"] = "lib/std/object.lua", + ["std.package"] = "lib/std/package.lua", + ["std.set"] = "lib/std/set.lua", + ["std.strbuf"] = "lib/std/strbuf.lua", + ["std.string"] = "lib/std/string.lua", + ["std.table"] = "lib/std/table.lua", + ["std.tree"] = "lib/std/tree.lua", }, } From 0c17940e3b6098f26916abedcc6d6cf138106c96 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 31 Jan 2016 23:38:16 +0000 Subject: [PATCH 678/703] travis: really don't uninstall the stdlib rock specl depends on! * .travis.yml (install): Don't force uninstall the previous stdlib rock and break Specl! Signed-off-by: Gary V. Vaughan --- .travis.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 95ea11f..56a264b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -115,21 +115,13 @@ install: sudo luarocks install --server=http://rocks.moonscript.org/manifests/gvvaughan $rock; done' - # Fudge timestamps on release branches. - - 'if test -f configure; then - test -f aclocal.m4 && touch aclocal.m4; - sleep 1; touch Makefile.in; - sleep 1; test -f config.h.in && touch config.h.in; - sleep 1; touch configure; - fi' - # Build from rockspec, forcing uninstall of older luarocks installed # above when testing the git rockspec, both for enforcing backwards # compatibility by default, and for ease of maintenance. - if test -f "stdlib-$_VERSION-1.rockspec"; then sudo luarocks make "stdlib-$_VERSION-1.rockspec" LUA="$LUA"; else - sudo luarocks make --force 'stdlib-git-1.rockspec' LUA="$LUA"; + sudo luarocks make 'stdlib-git-1.rockspec' LUA="$LUA"; fi # Clean up files created by root From 958295fa8243925228dd6c71867abe5fd0fa004e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 1 Feb 2016 10:00:36 +0000 Subject: [PATCH 679/703] refactor: move common init code to shared std._base module. * lib/std/_base.lua (_DEBUG): Fetch std.debug_init._DEBUG. (strict): Set according to _DEBUG.strict and whether "strict" module is available. (typecheck): Likewise for _DEBUG.argcheck and "typecheck" module. * lib/std/container.lua, lib/std/debug.lua, lib/std/init.lua, lib/std/io.lua, lib/std/list.lua, lib/std/math.lua, lib/std/object.lua, lib/std/package.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/string.lua, lib/std/table.lua, lib/std/tree.lua: Reuse precalculated and preloaded values from std._base instead of copying boilerplate code. Signed-off-by: Gary V. Vaughan --- lib/std/_base.lua | 39 ++++++++++++++++++++++++++--- lib/std/container.lua | 58 +++++++++++++------------------------------ lib/std/debug.lua | 26 ++++++------------- lib/std/init.lua | 57 ++++++++++++------------------------------ lib/std/io.lua | 53 ++++++++++----------------------------- lib/std/list.lua | 29 +++------------------- lib/std/math.lua | 33 ++++-------------------- lib/std/object.lua | 28 +++------------------ lib/std/package.lua | 44 ++++++++------------------------ lib/std/set.lua | 28 +++------------------ lib/std/strbuf.lua | 33 ++++++------------------ lib/std/string.lua | 28 +++------------------ lib/std/table.lua | 55 +++++++++++----------------------------- lib/std/tree.lua | 29 +++------------------- 14 files changed, 142 insertions(+), 398 deletions(-) diff --git a/lib/std/_base.lua b/lib/std/_base.lua index c0a5602..2a49cc3 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -55,13 +55,40 @@ local table_sort = table.sort local table_unpack = table.unpack or unpack + +--[[ ================== ]]-- +--[[ Initialize _DEBUG. ]]-- +--[[ ================== ]]-- + + local _DEBUG = require "std.debug_init"._DEBUG +local strict, typecheck +do + local ok -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} + -- Unless strict was disabled (`_DEBUG = false`), or that module is not + -- available, check for use of undeclared variables in this module... + if _DEBUG.strict then + ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + else + -- ...otherwise, the strict function is not available at all! + _DEBUG.strict = false + strict = false + end + end + + -- Unless strict was disabled (`_DEBUG = false`), or that module is not + -- available, check for use of undeclared variables in this module... + if _DEBUG.argcheck then + ok, typecheck = pcall (require, "typecheck") + if not ok then + -- ...otherwise, the strict function is not available at all! + _DEBUG.argcheck = false + typecheck = false + end end end @@ -504,6 +531,10 @@ end -- public API here too, which means everything looks relatively normal -- when importing the functions into stdlib implementation modules. return { + _DEBUG = _DEBUG, + strict = strict, + typecheck = typecheck, + eval = eval, getmetamethod = getmetamethod, ielems = ielems, diff --git a/lib/std/container.lua b/lib/std/container.lua index b213c40..fd8b79e 100644 --- a/lib/std/container.lua +++ b/lib/std/container.lua @@ -28,7 +28,6 @@ @prototype std.container ]] -local _ENV = _ENV local getmetatable = getmetatable local next = next local select = select @@ -38,50 +37,24 @@ local type = type local table_concat = table.concat -local _ = { - debug_init = require "std.debug_init", - std = require "std._base", -} +local _ = require "std._base" -local Module = _.std.object.Module - -local _DEBUG = _.debug_init._DEBUG -local copy = _.std.base.copy -local mapfields = _.std.object.mapfields -local render = _.std.string.render -local sortkeys = _.std.base.sortkeys - - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argcheck, argerror, argscheck, extramsg_toomany -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argcheck = typecheck.argcheck - argerror = typecheck.argerror - argscheck = typecheck.argscheck - extramsg_toomany = typecheck.extramsg_toomany - else - _DEBUG.argcheck = false - end -end -argscheck = argscheck or function (decl, inner) return inner end +local Module = _.object.Module +local argcheck = _.typecheck and _.typecheck.argcheck +local argerror = _.argerror +local argscheck = _.typecheck and _.typecheck.argscheck +local copy = _.base.copy +local extramsg_toomany = _.typecheck and _.typecheck.extramsg_toomany +local mapfields = _.object.mapfields +local render = _.string.render +local sortkeys = _.base.sortkeys --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.strict and _.strict {} or {} _ = nil - --[[ ================= ]]-- --[[ Helper Functions. ]]-- --[[ ================= ]]-- @@ -249,7 +222,7 @@ local prototype = { } -if _DEBUG.argcheck then +if argcheck then local __call = prototype.__call prototype.__call = function (self, ...) @@ -272,6 +245,10 @@ if _DEBUG.argcheck then end +local function X (decl, fn) + return argscheck and argscheck ("std.container." .. decl, fn) or fn +end + return Module { prototype = setmetatable ({}, prototype), @@ -308,6 +285,5 @@ return Module { -- } -- local groceries = Bag ("apple", "banana", "banana") -- local purse = Bag {_type = "Purse"} ("cards", "cash", "id") - mapfields = argscheck ( - "std.container.mapfields (table, table|object, ?table)", mapfields), + mapfields = X ("mapfields (table, table|object, ?table)", mapfields), } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 0be7396..0ccb27a 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -11,7 +11,6 @@ ]] -local _ENV = _ENV local debug = debug local setmetatable = setmetatable local type = type @@ -22,25 +21,16 @@ local math_max = math.max local table_concat = table.concat -local _ = { - debug_init = require "std.debug_init", - std = require "std._base", -} - -local _DEBUG = _.debug_init._DEBUG -local _getfenv = _.std.debug.getfenv -local _pairs = _.std.pairs -local _setfenv = _.std.debug.setfenv -local _tostring = _.std.tostring -local merge = _.std.base.merge +local _ = require "std._base" +local _DEBUG = _._DEBUG +local _getfenv = _.debug.getfenv +local _pairs = _.pairs +local _setfenv = _.debug.setfenv +local _tostring = _.tostring +local merge = _.base.merge -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.strict and _.strict {} or {} _ = nil diff --git a/lib/std/init.lua b/lib/std/init.lua index fa296e5..92712ba 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -14,7 +14,6 @@ ]] -local _ENV = _ENV local error = error local ipairs = ipairs local pairs = pairs @@ -29,52 +28,28 @@ local string_format = string.format local string_match = string.match -local _ = { - debug_init = require "std.debug_init", - std = require "std._base", -} +local _ = require "std._base" -local _DEBUG = _.debug_init._DEBUG -local _ipairs = _.std.ipairs -local _pairs = _.std.pairs -local _tostring = _.std.tostring -local compare = _.std.list.compare -local copy = _.std.base.copy -local eval = _.std.eval -local getmetamethod = _.std.getmetamethod -local ielems = _.std.ielems -local maxn = _.std.table.maxn -local merge = _.std.base.merge -local ripairs = _.std.ripairs -local split = _.std.string.split - - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end +local _ipairs = _.ipairs +local _pairs = _.pairs +local _tostring = _.tostring +local argscheck = _.typecheck and _.typecheck.argscheck +local compare = _.list.compare +local copy = _.base.copy +local eval = _.eval +local getmetamethod = _.getmetamethod +local ielems = _.ielems +local maxn = _.table.maxn +local merge = _.base.merge +local ripairs = _.ripairs +local split = _.string.split - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.strict and _.strict {} or {} _ = nil - --[[ =============== ]]-- --[[ Implementation. ]]-- --[[ =============== ]]-- @@ -154,7 +129,7 @@ end local function X (decl, fn) - return argscheck ("std." .. decl, fn) + return argscheck and argscheck ("std." .. decl, fn) or fn end M = { diff --git a/lib/std/io.lua b/lib/std/io.lua index a1addd1..ce0fbfa 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -11,7 +11,6 @@ ]] -local _ENV = _ENV local _G = _G local arg = arg local error = error @@ -34,46 +33,20 @@ local table_concat = table.concat local table_insert = table.insert -local _ = { - debug_init = require "std.debug_init", - std = require "std._base", -} - -local _DEBUG = _.debug_init._DEBUG -local _ipairs = _.std.ipairs -local _tostring = _.std.tostring -local catfile = _.std.io.catfile -local dirsep = _.std.package.dirsep -local leaves = _.std.tree.leaves -local len = _.std.operator.len -local merge = _.std.base.merge -local split = _.std.string.split - - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argerror, argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argerror = typecheck.argerror - argscheck = typecheck.argscheck - else - _DEBUG.argcheck = false - end -end -argerror = argerror or _.std.debug.argerror -argscheck = argscheck or function (decl, inner) return inner end +local _ = require "std._base" +local _ipairs = _.ipairs +local _tostring = _.tostring +local argerror = _.debug.argerror +local argscheck = _.typecheck and _.typecheck.argscheck +local catfile = _.io.catfile +local dirsep = _.package.dirsep +local leaves = _.tree.leaves +local len = _.operator.len +local merge = _.base.merge +local split = _.string.split --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.strict and _.strict {} or {} _ = nil @@ -186,7 +159,7 @@ end local function X (decl, fn) - return argscheck ("std.io." .. decl, fn) + return argscheck and argscheck ("std.io." .. decl, fn) or fn end diff --git a/lib/std/list.lua b/lib/std/list.lua index 45bdd32..b69a065 100644 --- a/lib/std/list.lua +++ b/lib/std/list.lua @@ -17,13 +17,10 @@ ]] -local _ENV = _ENV - local table_unpack = table.unpack or unpack local _ = { - debug_init = require "std.debug_init", object = require "std.object", std = require "std._base", } @@ -31,33 +28,13 @@ local _ = { local Module = _.std.object.Module local Object = _.object.prototype -local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs +local argscheck = _.std.typecheck and _.std.typecheck.argscheck local compare = _.std.list.compare local len = _.std.operator.len - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end - - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.std.strict and _.std.strict {} or {} _ = nil @@ -123,7 +100,7 @@ end local function X (decl, fn) - return argscheck ("std.list." .. decl, fn) + return argscheck and argscheck ("std.list." .. decl, fn) or fn end diff --git a/lib/std/math.lua b/lib/std/math.lua index 0b8eedd..d90c030 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -11,40 +11,17 @@ ]] -local _ENV = _ENV local math = math local math_floor = math.floor -local _ = { - debug_init = require "std.debug_init", - std = require "std._base", -} - -local _DEBUG = _.debug_init._DEBUG -local merge = _.std.base.merge +local _ = require "std._base" --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argcheck, argscheck, extramsg_toomany -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end +local argscheck = _.typecheck and _.typecheck.argscheck +local merge = _.base.merge - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.strict and _.strict {} or {} _ = nil @@ -81,7 +58,7 @@ end local function X (decl, fn) - return argscheck ("std.math." .. decl, fn) + return argscheck and argscheck ("std.math." .. decl, fn) or fn end diff --git a/lib/std/object.lua b/lib/std/object.lua index 6059d92..3f9f41d 100644 --- a/lib/std/object.lua +++ b/lib/std/object.lua @@ -22,12 +22,10 @@ ]] -local _ENV = _ENV local getmetatable = getmetatable local _ = { - debug_init = require "std.debug_init", container = require "std.container", std = require "std._base", } @@ -35,32 +33,12 @@ local _ = { local Container = _.container.prototype local Module = _.std.object.Module -local _DEBUG = _.debug_init._DEBUG +local argscheck = _.std.typecheck and _.std.typecheck.argscheck local getmetamethod = _.std.getmetamethod local mapfields = _.std.object.mapfields local merge = _.std.base.merge - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end - - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.std.strict and _.std.strict {} or {} _ = nil @@ -72,7 +50,7 @@ _ = nil local function X (decl, fn) - return argscheck ("std.object." .. decl, fn) + return argscheck and argscheck ("std.object." .. decl, fn) or fn end diff --git a/lib/std/package.lua b/lib/std/package.lua index 565e25c..cad3ccf 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -36,7 +36,6 @@ ]] -local _ENV = _ENV local ipairs = ipairs local package = package @@ -48,40 +47,17 @@ local table_remove = table.remove local table_unpack = table.unpack or unpack -local _ = { - debug_init = require "std.debug_init", - std = require "std._base", -} - -local _DEBUG = _.debug_init._DEBUG -local catfile = _.std.io.catfile -local escape_pattern = _.std.string.escape_pattern -local invert = _.std.table.invert -local len = _.std.operator.len -local merge = _.std.base.merge -local split = _.std.string.split - - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end +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 len = _.operator.len +local merge = _.base.merge +local split = _.string.split --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.strict and _.strict {} or {} _ = nil @@ -200,7 +176,7 @@ end local function X (decl, fn) - return argscheck ("std.package." .. decl, fn) + return argscheck and argscheck ("std.package." .. decl, fn) or fn end diff --git a/lib/std/set.lua b/lib/std/set.lua index 3b199cb..974cf08 100644 --- a/lib/std/set.lua +++ b/lib/std/set.lua @@ -22,7 +22,6 @@ ]] -local _ENV = _ENV local getmetatable = getmetatable local next = next local rawget = rawget @@ -36,39 +35,18 @@ local table_sort = table.sort local _ = { container = require "std.container", - debug_init = require "std.debug_init", std = require "std._base", } local Container = _.container.prototype local Module = _.std.object.Module -local _DEBUG = _.debug_init._DEBUG local _pairs = _.std.pairs local _tostring = _.std.tostring +local argscheck = _.std.typecheck and _.std.typecheck.argscheck local pickle = _.std.string.pickle - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end - - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.std.strict and _.std.strict {} or {} _ = nil @@ -182,7 +160,7 @@ end local function X (decl, fn) - return argscheck ("std.set." .. decl, fn) + return argscheck and argscheck ("std.set." .. decl, fn) or fn end diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua index 7b7548f..12aa6ec 100644 --- a/lib/std/strbuf.lua +++ b/lib/std/strbuf.lua @@ -27,7 +27,6 @@ ]] -local _ENV = _ENV local ipairs = ipairs local tostring = tostring @@ -35,7 +34,6 @@ local table_concat = table.concat local _ = { - debug_init = require "std.debug_init", object = require "std.object", std = require "std._base", } @@ -43,30 +41,10 @@ local _ = { local Module = _.std.object.Module local Object = _.object.prototype -local _DEBUG = _.debug_init._DEBUG +local argscheck = _.std.typecheck and _.std.typecheck.argscheck local merge = _.std.base.merge - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end - - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.std.strict and _.std.strict {} or {} _ = nil @@ -95,6 +73,11 @@ end --[[ ================= ]]-- +local function X (decl, fn) + return argscheck and argscheck ("std.strbuf." .. decl, fn) or fn +end + + local methods = { --- Methods -- @section methods @@ -108,7 +91,7 @@ local methods = { -- @treturn prototype modified buffer -- @usage -- c = StrBuf {} :concat "append this" :concat (StrBuf {" and", " this"}) - concat = argscheck ("std.strbuf.concat (StrBuf, any)", __concat), + concat = X ("concat (StrBuf, any)", __concat), } diff --git a/lib/std/string.lua b/lib/std/string.lua index 1f1b8b1..576f24a 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -11,7 +11,6 @@ ]] -local _ENV = _ENV local assert = assert local getmetatable = getmetatable local string = string @@ -27,15 +26,14 @@ local string_format = string.format local _ = { - debug_init = require "std.debug_init", std = require "std._base", strbuf = require "std.strbuf", } local StrBuf = _.strbuf.prototype -local _DEBUG = _.debug_init._DEBUG local _tostring = _.std.tostring +local argscheck = _.std.typecheck and _.std.typecheck.argscheck local copy = _.std.base.copy local escape_pattern = _.std.string.escape_pattern local len = _.std.operator.len @@ -44,27 +42,7 @@ local render = _.std.string.render local sortkeys = _.std.base.sortkeys local split = _.std.string.split - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end - - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.std.strict and _.std.strict {} or _ENV _ = nil @@ -277,7 +255,7 @@ end local function X (decl, fn) - return argscheck ("std.string." .. decl, fn) + return argscheck and argscheck ("std.string." .. decl, fn) or fn end M = { diff --git a/lib/std/table.lua b/lib/std/table.lua index c9eccbb..d7843ed 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -11,7 +11,6 @@ ]] -local _ENV = _ENV local getmetatable = getmetatable local next = next local setmetatable = setmetatable @@ -24,47 +23,21 @@ local table_insert = table.insert local table_unpack = table.unpack or unpack -local _ = { - debug_init = require "std.debug_init", - std = require "std._base", -} +local _ = require "std._base" -local _DEBUG = _.debug_init._DEBUG -local _ipairs = _.std.ipairs -local _pairs = _.std.pairs -local copy = _.std.base.copy -local getmetamethod = _.std.getmetamethod -local invert = _.std.table.invert -local len = _.std.operator.len -local maxn = _.std.table.maxn -local merge = _.std.base.merge -local pack = _.std.table.pack - - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argerror, argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argerror = typecheck.argerror - argscheck = typecheck.argscheck - else - _DEBUG.argcheck = false - end -end -argerror = argerror or _.std.debug.argerror -argscheck = argscheck or function (decl, inner) return inner end +local _ipairs = _.ipairs +local _pairs = _.pairs +local argerror = _.debug.argerror +local argscheck = _.typecheck and _.typecheck.argscheck +local copy = _.base.copy +local getmetamethod = _.getmetamethod +local invert = _.table.invert +local len = _.operator.len +local maxn = _.table.maxn +local merge = _.base.merge +local pack = _.table.pack - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.strict and _.strict {} or _ENV _ = nil @@ -218,7 +191,7 @@ end local function X (decl, fn) - return argscheck ("std.table." .. decl, fn) + return argscheck and argscheck ("std.table." .. decl, fn) or fn end M = { diff --git a/lib/std/tree.lua b/lib/std/tree.lua index bd6d8c8..0e170a0 100644 --- a/lib/std/tree.lua +++ b/lib/std/tree.lua @@ -24,7 +24,6 @@ ]] -local _ENV = _ENV local getmetatable = getmetatable local rawget = rawget local rawset = rawset @@ -39,42 +38,22 @@ local table_unpack = table.unpack or unpack local _ = { container = require "std.container", - debug_init = require "std.debug_init", std = require "std._base", } local Container = _.container.prototype local Module = _.std.object.Module -local _DEBUG = _.debug_init._DEBUG local _ipairs = _.std.ipairs local _pairs = _.std.pairs +local argscheck = _.std.typecheck and _.std.typecheck.argscheck local ielems = _.std.ielems local last = _.std.base.last local leaves = _.std.tree.leaves local len = _.std.operator.len +local pack = _.std.table.pack - --- Perform typechecking with functions exported from this module, unless --- disabled in `_DEBUG` or the "typecheck" module is not loadable. -local argscheck -if _DEBUG.argcheck then - local ok, typecheck = pcall (require, "typecheck") - if ok then - argscheck = typecheck.argscheck - end -end -argscheck = argscheck or function (decl, inner) return inner end - - --- Use a strict environment for the rest of this module, unless disabled --- in `_DEBUG` or the "strict" module is not loadable. -if _DEBUG.strict then - local ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - end -end +local _ENV = _.std.strict and _.std.strict {} or {} _ = nil @@ -203,7 +182,7 @@ end local function X (decl, fn) - return argscheck ("std.tree." .. decl, fn) + return argscheck and argscheck ("std.tree." .. decl, fn) or fn end From 014123447f948b08b25efca5c99fafb1373dcab0 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 3 Feb 2016 22:07:41 +0000 Subject: [PATCH 680/703] LICENSE: make sure this is a valid markdown format file. * LICENSE.md: Trim superfluous header, and incorrect H1 underlines. Signed-off-by: Gary V. Vaughan --- LICENSE.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 43c6b1e..047c509 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,9 +1,3 @@ -This software comprises files that are copyright their respective -authors (see the AUTHORS file for details), and distributed under -the terms of the MIT license (the same license as Lua itself), -unless noted otherwise in the body of that file. - -==================================================================== Copyright (C) 2002-2016 stdlib authors Permission is hereby granted, free of charge, to any person @@ -24,4 +18,3 @@ 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. -==================================================================== From 93186df9cd91aadabe20c73a73b4c737f2d0d7af Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Thu, 4 Feb 2016 22:33:33 +0000 Subject: [PATCH 681/703] refactor: split object system out into its own package. * lib/std/container.lua, lib/std/list.lua, lib/std/object.lua, lib/std/set.lua, lib/std/strbuf.lua, lib/std/tree.lua: Delete. * lib/std/string.lua, lib/std/table.lua: Adjust accordingly. * Makefile (SOURCES): Likewise. * config.ld.in (file): Likewise. * specs/container_spec.yaml, specs/list_spec.yaml, specs/object_spec.yaml, specs/set_spec.yaml, specs/strbuf_spec.yaml, specs/tree_spec.yaml: Delete. * stdlib-git-1.rockspec: Adjust accordingly. * NEWS.md: Update. Signed-off-by: Gary V. Vaughan --- Makefile | 6 - NEWS.md | 93 +-------- doc/config.ld.in | 19 -- lib/std/container.lua | 289 -------------------------- lib/std/list.lua | 239 --------------------- lib/std/object.lua | 138 ------------- lib/std/set.lua | 390 ----------------------------------- lib/std/strbuf.lua | 143 ------------- lib/std/string.lua | 36 ++-- lib/std/table.lua | 2 - lib/std/tree.lua | 366 --------------------------------- specs/container_spec.yaml | 131 ------------ specs/list_spec.yaml | 392 ----------------------------------- specs/object_spec.yaml | 295 -------------------------- specs/set_spec.yaml | 307 --------------------------- specs/std_spec.yaml | 22 +- specs/strbuf_spec.yaml | 100 --------- specs/tree_spec.yaml | 422 -------------------------------------- stdlib-git-1.rockspec | 8 +- 19 files changed, 28 insertions(+), 3370 deletions(-) delete mode 100644 lib/std/container.lua delete mode 100644 lib/std/list.lua delete mode 100644 lib/std/object.lua delete mode 100644 lib/std/set.lua delete mode 100644 lib/std/strbuf.lua delete mode 100644 lib/std/tree.lua delete mode 100644 specs/container_spec.yaml delete mode 100644 specs/list_spec.yaml delete mode 100644 specs/object_spec.yaml delete mode 100644 specs/set_spec.yaml delete mode 100644 specs/strbuf_spec.yaml delete mode 100644 specs/tree_spec.yaml diff --git a/Makefile b/Makefile index 04b4d4c..cac5913 100644 --- a/Makefile +++ b/Makefile @@ -7,20 +7,14 @@ SPECL = specl luadir = lib/std SOURCES = \ $(luadir)/_base.lua \ - $(luadir)/container.lua \ $(luadir)/debug.lua \ $(luadir)/debug_init/init.lua \ $(luadir)/init.lua \ $(luadir)/io.lua \ - $(luadir)/list.lua \ $(luadir)/math.lua \ - $(luadir)/object.lua \ $(luadir)/package.lua \ - $(luadir)/set.lua \ - $(luadir)/strbuf.lua \ $(luadir)/string.lua \ $(luadir)/table.lua \ - $(luadir)/tree.lua \ $(luadir)/version.lua \ $(NOTHING_ELSE) diff --git a/NEWS.md b/NEWS.md index 03df22d..5300ec5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -24,31 +24,6 @@ - All support for deprecated APIs has been removed, reducing the install size even further. - - Objects and Modules are no longer conflated - what you get back from - a `require "std.something"` is now ALWAYS a module: - - ```lua - local object = require "std.object" - assert (object.type (object) == "Module") - ``` - - And the modules that provide objects have a new `prototype` field - that contains the prototye for that kind of object: - - ```lua - local Object = object.prototype - assert (object.type (Object) == "Object") - ``` - - For backwards compatibility, if you call the module with a - constructor table, the previous recommended way to disambiguate - between a module and the object it prototyped, that table is passed - through to that module's object prototype. - - - Now that we have proper separation of concerns between module tables - and object prototype tables, the central `std.object.mapfields` - instantiation function is much cleaner and faster. - - `std.string.render` now takes a table of named arguments as documented; the `pairs` function is now supplied with the key and value of the preceding key/value pair. There is also support for two new named @@ -75,20 +50,6 @@ - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. - - We used to have an object module method, `std.object.type`, which - often got imported using: - - ```lua - local prototype = require "std.object".type - ``` - - So we renamed it to `std.object.prototype` to avoid a name clash with - the `type` symbol, and subsequently deprecated the earlier equivalent - `type` method; but that was a mistake, because core Lua provides `type`, - and `io.type` (and in recent releases, `math.type`). So now, for - orthogonality with core Lua, we're going back to using `std.object.type`, - because that just makes more sense. Sorry! - - `std.table.okeys` has been removed for lack of utility. If you still need it, use this instead: @@ -96,10 +57,6 @@ local okeys = std.functional.compose (std.table.keys, std.table.sort) ``` - - `std.string.render` function arguments have been replaced by a table - of named functions backed by defaults. - - ### Bug fixes - `std.string.wrap` doesn't throw a StrBuf deprecation warning any more. @@ -108,18 +65,15 @@ correctly, rather than `nil` as in previous releases. It's also considerably faster now that it doesn't use `pcall` any more. - - You can now derive other types from `std.set` by passing a `_type` - field in the init argument, just like the other table argument - objects. - - `table.pack` now sets `n` field to number of arguments packed, even in Lua 5.1. ### Incompatible changes - - `std.functional`, `std.maturity`, `std.operator`, `std.optparse`, - `std.strict` and `std.tuple` have been moved to their own packages, - and are no longer shipped as part of stdlib. + - `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`, @@ -136,41 +90,10 @@ removed. At some point these will resurface in a new standalone package. - - Deprecated methods `list:depair`, `list:elems`, `list:enpair`, - `list:filter`, `list:flatten`, `list:foldl`, `list:foldr`, - `list:index_key`, `list:index_value`, `list:map`, `list:map_with`, - `list:project`, `list:relems`, `list:reverse`, `list:shape`, - `list:transpose` and `list:zip_with` have been removed. - - - Deprecated functions `list.depair`, `list.elems`, `list.enpair`, - `list.filter`, `list.flatten`, `list.foldl`, `list.foldr`, - `list.index_key`, `list.index_value`, `list.map`, `list.map_with`, - `list.project`, `list.relems`, `list.reverse`, `list.shape`, - `list.transpose`, `list.zip_with`, `string.assert`, - `string.require_version`, `string.tostring`, `table.clone_rename`, - `table.metamethod`, `table.ripairs` and `table.totable` have been - removed. See previous entries below for what they were replaced - by. - - - Now that the `prototype` field is used to reference a module's - object prototype, `std.object.prototype` no longer return the object - type of an argument. Additionally, for orthogonality with the way Lua - itself uses `io.type` and `math.type` to get more detail about certain - objects than `type` itself, `std.object.type` now operates purely on - stdlib objects with a `_type` metatable field, and returns `nil` for - anything else. - - To replicate the old behaviour, use this: - - ```lua - local std = require "std" - local object_type = std.functional.any (std.object.type, io.type, type) - ``` - - - Objects no longer honor mangling and stripping `_functions` tables - from objects during instantiation, instead move your actual object - into the module `prototype` field, and add the module functions to - the parent table returned when the module is required. + - 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` diff --git a/doc/config.ld.in b/doc/config.ld.in index 2891ec4..9f80f0c 100644 --- a/doc/config.ld.in +++ b/doc/config.ld.in @@ -13,10 +13,6 @@ LuaJIT), 5.2 and 5.3 written in pure Lua, comprising: 2. Enhanced versions of some core Lua libraries: @{std.debug}, @{std.io}, @{std.math}, @{std.package}, @{std.string} and @{std.table}; -3. A straight forward prototype-based object system, and a selection of - useful objects built on it: @{std.container}, @{std.object}, - @{std.list}, @{std.set}, @{std.strbuf} and @{std.tree}. - ## LICENSE The code is copyright by its respective authors, and released under the @@ -26,41 +22,26 @@ MIT license (the same license as Lua itself). There is no warranty. dir = "." file = { - -- Core Functions "../lib/std/init.lua", - - -- Core Libraries "../lib/std/debug.lua", "../lib/std/io.lua", "../lib/std/math.lua", "../lib/std/package.lua", "../lib/std/string.lua", "../lib/std/table.lua", - - -- Object System - "../lib/std/container.lua", - "../lib/std/object.lua", - "../lib/std/list.lua", - "../lib/std/set.lua", - "../lib/std/strbuf.lua", - "../lib/std/tree.lua", } new_type ("corefunction", "Core_Functions", true) new_type ("corelibrary", "Core_Libraries", true) -new_type ("prototype", "Object_System", true) function postprocess_html(s) s = s:gsub("

    %s*Corefunction (.-)

    ", '

    Module %1

    ') s = s:gsub("

    %s*Corelibrary (.-)

    ", '

    Module %1

    ') - s = s:gsub("

    %s*Prototype (.-)

    ", '

    Module %1

    ') s = s:gsub("

    Core_Functions

    ", '

    Core Functions

    ') s = s:gsub("

    Core_Libraries

    ", '

    Core Libraries

    ') - s = s:gsub("

    Object_System

    ", '

    Object System

    ') return s end -new_type ("object", "Objects", false, "Fields") new_type ("init", "Initialisation", false, "Parameters") format = "markdown" diff --git a/lib/std/container.lua b/lib/std/container.lua deleted file mode 100644 index fd8b79e..0000000 --- a/lib/std/container.lua +++ /dev/null @@ -1,289 +0,0 @@ ---[[-- - Container prototype. - - This module supplies the root prototype object from which every other - object is descended. There are no classes as such, rather new objects - are created by cloning an existing object, and then changing or adding - to the clone. Further objects can then be made by cloning the changed - object, and so on. - - The functionality of a container based object is entirely defined by its - *meta*methods. However, since we can store *any* object in a container, - we cannot rely on the `__index` metamethod, because it is only a - fallback for when that key is not already in the container itself. Of - course that does not entirely preclude the use of `__index` with - containers, so long as this limitation is observed. - - When making your own prototypes, derive from @{std.container.prototype} - if you want to access the contents of your containers with the `[]` - operator, otherwise from @{std.object.prototype} if you want to access - the functionality of your objects with named object methods. - - Prototype Chain - --------------- - - table - `-> Container - - @prototype std.container -]] - -local getmetatable = getmetatable -local next = next -local select = select -local setmetatable = setmetatable -local type = type - -local table_concat = table.concat - - -local _ = require "std._base" - -local Module = _.object.Module - -local argcheck = _.typecheck and _.typecheck.argcheck -local argerror = _.argerror -local argscheck = _.typecheck and _.typecheck.argscheck -local copy = _.base.copy -local extramsg_toomany = _.typecheck and _.typecheck.extramsg_toomany -local mapfields = _.object.mapfields -local render = _.string.render -local sortkeys = _.base.sortkeys - -local _ENV = _.strict and _.strict {} or {} - -_ = nil - - ---[[ ================= ]]-- ---[[ Helper Functions. ]]-- ---[[ ================= ]]-- - - ---- Instantiate a new object based on *proto*. --- --- This is equivalent to: --- --- table.merge (table.clone (proto), t or {}) --- --- Except that, by not typechecking arguments or checking for metatables, --- it is slightly faster. --- @tparam table proto base object to copy from --- @tparam[opt={}] table t additional fields to merge in --- @treturn table a new table with fields from proto and t merged in. -local function instantiate (proto, t) - local obj = {} - local k, v = next (proto) - while k do - obj[k] = v - k, v = next (proto, k) - end - - t = t or {} - k, v = next (t) - while k do - obj[k] = v - k, v = next (t, k) - end - return obj -end - - -local tostring_vtable = { - pair = function (x, kp, vp, k, v, kstr, vstr) - if k == 1 or type (k) == "number" and k -1 == kp then return vstr end - return kstr .. "=" .. vstr - end, - - sep = function (x, kp, vp, kn, vn) - if kp == nil or kn == nil then return "" end - if type (kp) == "number" and kn ~= kp + 1 then return "; " end - return ", " - end, - - sort = sortkeys, -} - - - ---[[ ================= ]]-- ---[[ Container Object. ]]-- ---[[ ================= ]]-- - - ---- Container prototype. --- @object prototype --- @string[opt="Container"] _type object name --- @tfield[opt] table|function _init object initialisation --- @usage --- local Container = require "std.container".prototype --- local Graph = Container { _type = "Graph" } --- local function nodes (graph) --- local n = 0 --- for _ in std.pairs (graph) do n = n + 1 end --- return n --- end --- local g = Graph { "node1", "node2" } --- assert (nodes (g) == 2) -local prototype = { - _type = "Container", - - --- Metamethods - -- @section metamethods - - --- Return a clone of this container and its metatable. - -- - -- Like any Lua table, a container is essentially a collection of - -- `field_n = value_n` pairs, except that field names beginning with - -- an underscore `_` are usually kept in that container's metatable - -- where they define the behaviour of a container object rather than - -- being part of its actual contents. In general, cloned objects - -- also clone the behaviour of the object they cloned, unless... - -- - -- When calling @{std.container.prototype}, you pass a single table - -- argument with additional fields (and values) to be merged into the - -- clone. Any field names beginning with an underscore `_` are copied - -- to the clone's metatable, and all other fields to the cloned - -- container itself. For instance, you can change the name of the - -- cloned object by setting the `_type` field in the argument table. - -- - -- The `_init` private field is also special: When set to a sequence of - -- field names, unnamed fields in the call argument table are assigned - -- to those field names in subsequent clones, like the example below. - -- - -- Alternatively, you can set the `_init` private field of a cloned - -- container object to a function instead of a sequence, in which case - -- all the arguments passed when *it* is called/cloned (including named - -- and unnamed fields in the initial table argument, if there is one) - -- are passed through to the `_init` function, following the nascent - -- cloned object. See the @{mapfields} usage example below. - -- @function prototype:__call - -- @param ... arguments to prototype's *\_init*, often a single table - -- @treturn prototype clone of this container, with shared or - -- merged metatable as appropriate - -- @usage - -- local Cons = Container {_type="Cons", _init={"car", "cdr"}} - -- local list = Cons {"head", Cons {"tail", nil}} - __call = function (self, ...) - local mt = getmetatable (self) - local obj_mt = mt - local obj = {} - - -- This is the slowest part of cloning for any objects that have - -- a lot of fields to test and copy. - local k, v = next (self) - while (k) do - obj[k] = v - k, v = next (self, k) - end - - if type (mt._init) == "function" then - obj = mt._init (obj, ...) - else - obj = (self.mapfields or mapfields) (obj, (...), mt._init) - end - - -- If a metatable was set, then merge our fields and use it. - if next (getmetatable (obj) or {}) then - obj_mt = instantiate (mt, getmetatable (obj)) - - -- Merge object methods. - if type (obj_mt.__index) == "table" and - type ((mt or {}).__index) == "table" - then - obj_mt.__index = instantiate (mt.__index, obj_mt.__index) - end - end - - return setmetatable (obj, obj_mt) - end, - - - --- Return a compact string representation of this object. - -- - -- First the container name, and then between { and } an ordered list - -- of the array elements of the contained values with numeric keys, - -- followed by asciibetically sorted remaining public key-value pairs. - -- - -- This metamethod doesn't recurse explicitly, but relies upon - -- suitable `__tostring` metamethods for non-primitive content objects. - -- @function prototype:__tostring - -- @treturn string stringified object representation - -- @see tostring - -- @usage - -- assert (tostring (list) == 'Cons {car="head", cdr=Cons {car="tail"}}') - __tostring = function (self) - return table_concat { - -- Pass a shallow copy to render to avoid triggering __tostring - -- again and blowing the stack. - getmetatable (self)._type, " ", render (copy (self), tostring_vtable), - } - end, -} - - -if argcheck then - local __call = prototype.__call - - prototype.__call = function (self, ...) - local mt = getmetatable (self) - - -- A function initialised object can be passed arguments of any - -- type, so only argcheck non-function initialised objects. - if type (mt._init) ~= "function" then - local name, n = mt._type, select ("#", ...) - -- Don't count `self` as an argument for error messages, because - -- it just refers back to the object being called: `prototype {"x"}. - argcheck (name, 1, "table", (...)) - if n > 1 then - argerror (name, 2, extramsg_toomany ("argument", 1, n), 2) - end - end - - return __call (self, ...) - end -end - - -local function X (decl, fn) - return argscheck and argscheck ("std.container." .. decl, fn) or fn -end - -return Module { - prototype = setmetatable ({}, prototype), - - --- Functions - -- @section functions - - --- Return *new* with references to the fields of *src* merged in. - -- - -- This is the function used to instantiate the contents of a newly - -- cloned container, as called by @{__call} above, to split the - -- fields of a @{__call} argument table into private "_" prefixed - -- field namess, -- which are merged into the *new* metatable, and - -- public (everything else) names, which are merged into *new* itself. - -- - -- You might want to use this function from `_init` functions of your - -- own derived containers. - -- @function mapfields - -- @tparam table new partially instantiated clone container - -- @tparam table src @{__call} argument table that triggered cloning - -- @tparam[opt={}] table map key renaming specification in the form - -- `{old_key=new_key, ...}` - -- @treturn table merged public fields from *new* and *src*, with a - -- metatable of private fields (if any), both renamed according to - -- *map* - -- @usage - -- local Bag = Container { - -- _type = "Bag", - -- _init = function (new, ...) - -- if type (...) == "table" then - -- return container.mapfields (new, (...)) - -- end - -- return functional.reduce (operator.set, new, ipairs, {...}) - -- end, - -- } - -- local groceries = Bag ("apple", "banana", "banana") - -- local purse = Bag {_type = "Purse"} ("cards", "cash", "id") - mapfields = X ("mapfields (table, table|object, ?table)", mapfields), -} diff --git a/lib/std/list.lua b/lib/std/list.lua deleted file mode 100644 index b69a065..0000000 --- a/lib/std/list.lua +++ /dev/null @@ -1,239 +0,0 @@ ---[[-- - List prototype. - - In addition to the functionality described here, List objects also - have all the methods and metamethods of the @{std.object.prototype} - (except where overridden here), - - Prototype Chain - --------------- - - table - `-> Container - `-> Object - `-> List - - @prototype std.list -]] - - -local table_unpack = table.unpack or unpack - - -local _ = { - object = require "std.object", - std = require "std._base", -} - -local Module = _.std.object.Module -local Object = _.object.prototype - -local _ipairs = _.std.ipairs -local _pairs = _.std.pairs -local argscheck = _.std.typecheck and _.std.typecheck.argscheck -local compare = _.std.list.compare -local len = _.std.operator.len - -local _ENV = _.std.strict and _.std.strict {} or {} - -_ = nil - - - ---[[ ================= ]]-- ---[[ Implementatation. ]]-- ---[[ ================= ]]-- - - -local List - - -local function append (l, x) - local r = l {} - r[#r + 1] = x - return r -end - - -local function concat (l, ...) - local r = List {} - for _, e in _ipairs {l, ...} do - for _, v in _ipairs (e) do - r[#r + 1] = v - end - end - return r -end - - -local function rep (l, n) - local r = List {} - for i = 1, n do - r = concat (r, l) - end - return r -end - - -local function sub (l, from, to) - local r = List {} - local lenl = len (l) - from = from or 1 - to = to or lenl - if from < 0 then - from = from + lenl + 1 - end - if to < 0 then - to = to + lenl + 1 - end - for i = from, to do - r[#r + 1] = l[i] - end - return r -end - - - ---[[ ================== ]]-- ---[[ Type Declarations. ]]-- ---[[ ================== ]]-- - - -local function X (decl, fn) - return argscheck and argscheck ("std.list." .. decl, fn) or fn -end - - -local methods = { - --- Methods - -- @section methods - - --- Append an item to a list. - -- @function prototype:append - -- @param x item - -- @treturn prototype new list with *x* appended - -- @usage - -- --> List {"shorter", "longer"} - -- longer = (List {"shorter"}):append "longer" - append = X ("append (List, any)", append), - - --- Compare two lists element-by-element, from left-to-right. - -- @function prototype:compare - -- @tparam prototype|table m another list, or table - -- @return -1 if *l* is less than *m*, 0 if they are the same, and 1 - -- if *l* is greater than *m* - -- @usage - -- if list1:compare (list2) == 0 then print "same" end - compare = X ("compare (List, List|table)", compare), - - --- Concatenate the elements from any number of lists. - -- @function prototype:concat - -- @tparam prototype|table ... additional lists, or list-like tables - -- @treturn prototype new list with elements from arguments - -- @usage - -- --> List {"shorter", "short", "longer", "longest"} - -- longest = (List {"shorter"}):concat ({"short", "longer"}, {"longest"}) - concat = X ("concat (List, List|table...)", concat), - - --- Prepend an item to a list. - -- @function prototype:cons - -- @param x item - -- @treturn prototype new list with *x* followed by elements of *l* - -- @usage - -- --> List {"x", 1, 2, 3} - -- consed = (List {1, 2, 3}):cons "x" - cons = X ("cons (List, any)", function (l, x) return List {x, table_unpack (l, 1, len (l))} end), - - --- Repeat a list. - -- @function prototype:rep - -- @int n number of times to repeat - -- @treturn prototype *n* copies of *l* appended together - -- @usage - -- --> List {1, 2, 3, 1, 2, 3, 1, 2, 3} - -- repped = (List {1, 2, 3}):rep (3) - rep = X ("rep (List, int)", rep), - - --- Return a sub-range of a list. - -- (The equivalent of @{string.sub} on strings; negative list indices - -- count from the end of the list.) - -- @function prototype:sub - -- @int[opt=1] from start of range - -- @int[opt=#l] to end of range - -- @treturn prototype new list containing elements between *from* and *to* - -- inclusive - -- @usage - -- --> List {3, 4, 5} - -- subbed = (List {1, 2, 3, 4, 5, 6}):sub (3, 5) - sub = X ("sub (List, ?int, ?int)", sub), - - --- Return a list with its first element removed. - -- @function prototype:tail - -- @treturn prototype new list with all but the first element of *l* - -- @usage - -- --> List {3, {4, 5}, 6, 7} - -- tailed = (List {{1, 2}, 3, {4, 5}, 6, 7}):tail () - tail = X ("tail (List)", function (l) return sub (l, 2) end), -} - - ---- List prototype object. --- @object prototype --- @string[opt="List"] _type object name --- @tfield[opt] table|function _init object initialisation --- @see std.object.prototype --- @usage --- local List = require "std.list".prototype --- assert (std.type (List) == "List") -List = Object { - _type = "List", - - --- Metamethods - -- @section metamethods - - --- Concatenate lists. - -- @function prototype:__concat - -- @tparam prototype|table m another list, or table (hash part is ignored) - -- @see concat - -- @usage - -- new = alist .. {"append", "these", "elements"} - __concat = concat, - - --- Append element to list. - -- @function prototype:__add - -- @param e element to append - -- @see append - -- @usage - -- list = list + "element" - __add = append, - - --- List order operator. - -- @function prototype:__lt - -- @tparam prototype m another list - -- @see compare - -- @usage - -- max = list1 > list2 and list1 or list2 - __lt = function (list1, list2) return compare (list1, list2) < 0 end, - - --- List equality or order operator. - -- @function prototype:__le - -- @tparam prototype m another list - -- @see compare - -- @usage - -- min = list1 <= list2 and list1 or list2 - __le = function (list1, list2) return compare (list1, list2) <= 0 end, - - __index = methods, -} - - -return Module { - prototype = List, - - append = methods.append, - compare = methods.compare, - concat = methods.concat, - cons = methods.cons, - rep = methods.rep, - sub = methods.sub, - tail = methods.tail, -} diff --git a/lib/std/object.lua b/lib/std/object.lua deleted file mode 100644 index 3f9f41d..0000000 --- a/lib/std/object.lua +++ /dev/null @@ -1,138 +0,0 @@ ---[[-- - Object prototype. - - This module provides a specialization of the @{std.container.prototype} - with the addition of object methods. In addition to the functionality - described here, object prototypes also have all the methods and - metamethods of the @{std.container.prototype}. - - Note that object methods are stored in the `__index` field of their - metatable, and so cannot also use the `__index` metamethod to lookup - references with square brackets. Use a @{std.container.prototype} based - object if you want to do that. - - Prototype Chain - --------------- - - table - `-> Container - `-> Object - - @prototype std.object -]] - - -local getmetatable = getmetatable - - -local _ = { - container = require "std.container", - std = require "std._base", -} - -local Container = _.container.prototype -local Module = _.std.object.Module - -local argscheck = _.std.typecheck and _.std.typecheck.argscheck -local getmetamethod = _.std.getmetamethod -local mapfields = _.std.object.mapfields -local merge = _.std.base.merge - -local _ENV = _.std.strict and _.std.strict {} or {} - -_ = nil - - - ---[[ ================= ]]-- ---[[ Implementatation. ]]-- ---[[ ================= ]]-- - - -local function X (decl, fn) - return argscheck and argscheck ("std.object." .. decl, fn) or fn -end - - ---- Methods --- @section methods - -local methods = { - --- Return a clone of this object and its metatable. - -- - -- This function is useful if you need to override the normal use of - -- the `__call` metamethod for object cloning, without losing the - -- ability to clone an object. - -- @function prototype:clone - -- @param ... arguments to prototype's *\_init*, often a single table - -- @treturn prototype a clone of this object, with shared or merged - -- metatable as appropriate - -- @see std.container.__call - -- @usage - -- local Node = Object { _type = "Node" } - -- -- A trivial FSA to recognize powers of 10, either "0" or a "1" - -- -- followed by zero or more "0"s can transition to state 'finish' - -- local states; states = { - -- start = Node { ["1"] = states[1], ["0"] = states.finish }, - -- [1] = Node { ["0"] = states[1], [""] = states.finish }, - -- finish = Node {}, - -- } - clone = getmetamethod (Container, "__call"), - - - --- Object Functions - -- @section objfunctions - - --- Return *new* with references to the fields of *src* merged in. - -- - -- You can change the value of this function in an object, and that - -- new function will be called during cloning instead of the - -- standard @{std.container.mapfields} implementation. - -- @function prototype.mapfields - -- @tparam table new partially instantiated clone container - -- @tparam table src @{clone} argument table that triggered cloning - -- @tparam[opt={}] table map key renaming specification in the form - -- `{old_key=new_key, ...}` - -- @treturn table merged public fields from *new* and *src*, with a - -- metatable of private fields (if any), both renamed according to - -- *map* - -- @see std.container.mapfields - mapfields = X ("mapfields (table, table|object, ?table)", mapfields), -} - - ---- Object prototype. --- @object prototype --- @string[opt="Object"] _type object name --- @tfield[opt] table|function _init object initialisation --- @usage --- local Object = require "std.object".prototype --- local Process = Object { --- _type = "Process", --- _init = { "status", "out", "err" }, --- } --- local process = Process { --- procs[pid].status, procs[pid].out, procs[pid].err, -- auto assigned --- command = pipeline[pid], -- manual assignment --- } -local prototype = Container { - _type = "Object", - - --- Metamethods - -- @section metamethods - - --- Return an in-order iterator over public object fields. - -- @function prototype:__pairs - -- @treturn function iterator function - -- @treturn Object *self* - -- @usage - -- for k, v in std.pairs (anobject) do process (k, v) end - - __index = methods, -} - - -return Module { - prototype = prototype, - type = function (x) return (getmetatable (x) or {})._type end, -} diff --git a/lib/std/set.lua b/lib/std/set.lua deleted file mode 100644 index 974cf08..0000000 --- a/lib/std/set.lua +++ /dev/null @@ -1,390 +0,0 @@ ---[[-- - Set container prototype. - - This module returns a table of set operators, as well as the prototype - for a Set container object. - - Every possible object or primitive value is always present in any Set - container exactly zero or one times. - - In addition to the functionality described here, Set containers also - have all the methods and metamethods of the @{std.container.prototype} - (except where overridden here). - - Prototype Chain - --------------- - - table - `-> Container - `-> Set - - @prototype std.set -]] - - -local getmetatable = getmetatable -local next = next -local rawget = rawget -local rawset = rawset -local setmetatable = setmetatable -local type = type - -local table_concat = table.concat -local table_sort = table.sort - - -local _ = { - container = require "std.container", - std = require "std._base", -} - -local Container = _.container.prototype -local Module = _.std.object.Module - -local _pairs = _.std.pairs -local _tostring = _.std.tostring -local argscheck = _.std.typecheck and _.std.typecheck.argscheck -local pickle = _.std.string.pickle - -local _ENV = _.std.strict and _.std.strict {} or {} - -_ = nil - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - -local prototype -- forward declaration - - - ---[[ ==================== ]]-- ---[[ Primitive Functions. ]]-- ---[[ ==================== ]]-- - - --- These functions know about internal implementatation. --- The representation is a table whose tags are the elements, and --- whose values are true. - - -local elems = _pairs - - -local function insert (set, e) - return rawset (set, e, true) -end - - -local function member (set, e) - return rawget (set, e) == true -end - - - ---[[ ===================== ]]-- ---[[ High Level Functions. ]]-- ---[[ ===================== ]]-- - - --- These functions are independent of the internal implementation. - - -local difference, symmetric_difference, intersection, union, subset, - proper_subset, equal - - -function difference (set1, set2) - local r = prototype {} - for e in elems (set1) do - if not member (set2, e) then - insert (r, e) - end - end - return r -end - - -function symmetric_difference (set1, set2) - return difference (union (set1, set2), intersection (set2, set1)) -end - - -function intersection (set1, set2) - local r = prototype {} - for e in elems (set1) do - if member (set2, e) then - insert (r, e) - end - end - return r -end - - -function union (set1, set2) - local r = set1 {} - for e in elems (set2) do - insert (r, e) - end - return r -end - - -function subset (set1, set2) - for e in elems (set1) do - if not member (set2, e) then - return false - end - end - return true -end - - -function proper_subset (set1, set2) - return subset (set1, set2) and not subset (set2, set1) -end - - -function equal (set1, set2) - return subset (set1, set2) and subset (set2, set1) -end - - - ---[[ =========== ]]-- ---[[ Set Object. ]]-- ---[[ =========== ]]-- - - -local function X (decl, fn) - return argscheck and argscheck ("std.set." .. decl, fn) or fn -end - - ---- Set prototype object. --- @object prototype --- @string[opt="Set"] _type object name --- @see std.container.prototype --- @usage --- local Set = require "std.set".prototype --- assert (std.type (Set) == "Set") -prototype = Container { - _type = "Set", - - --- Set object initialisation. - -- - -- Returns partially initialised Set container with contents - -- from *t*. - -- @init prototype._init - -- @tparam table new uninitialised Set container object - -- @tparam table t initialisation table from `__call` - _init = function (new, t) - local mt = {} - for k, v in _pairs (t) do - local type_k = type (k) - if type_k == "number" then - insert (new, v) - elseif type_k == "string" and k:sub (1, 1) == "_" then - mt[k] = v - end - -- non-underscore-prefixed string keys are discarded! - end - return next (mt) and setmetatable (new, mt) or new - end, - - --- Metamethods - -- @section metamethods - - --- Union operation. - -- @function prototype:__add - -- @tparam prototype s another set - -- @treturn prototype everything from *this* set plus everything from *s* - -- @see union - -- @usage - -- union = this + s - __add = union, - - --- Difference operation. - -- @function prototype:__sub - -- @tparam prototype s another set - -- @treturn prototype everything from *this* set that is not also in *s* - -- @see difference - -- @usage - -- difference = this - s - __sub = difference, - - --- Intersection operation. - -- @function prototype:__mul - -- @tparam prototype s another set - -- @treturn prototype anything in both *this* set and in *s* - -- @see intersection - -- @usage - -- intersection = this * s - __mul = intersection, - - --- Symmetric difference operation. - -- @function prototype:__div - -- @tparam prototype s another set - -- @treturn prototype everything in *this* set or in *s* but not in both - -- @see symmetric_difference - -- @usage - -- symmetric_difference = this / s - __div = symmetric_difference, - - --- Subset operation. - -- @static - -- @function prototype:__le - -- @tparam prototype s another set - -- @treturn boolean `true` if everything in *this* set is also in *s* - -- @see subset - -- @usage - -- issubset = this <= s - __le = subset, - - --- Proper subset operation. - -- @function prototype:__lt - -- @tparam prototype s another set - -- @treturn boolean `true` if *s* is not equal to *this* set, but does - -- contain everything from *this* set - -- @see proper_subset - -- @usage - -- ispropersubset = this < s - __lt = proper_subset, - - --- Return a string representation of this set. - -- @function prototype:__tostring - -- @treturn string string representation of a set. - -- @see std.tostring - __tostring = function (self) - local keys = {} - for k in _pairs (self) do - keys[#keys + 1] = _tostring (k) - end - table_sort (keys) - return getmetatable (self)._type .. " {" .. table_concat (keys, ", ") .. "}" - end, -} - - -return Module { - prototype = prototype, - - --- Functions - -- @section functions - - --- Delete an element from a set. - -- @function delete - -- @tparam prototype set a set - -- @param e element - -- @treturn prototype the modified *set* - -- @usage - -- set.delete (available, found) - delete = X ("delete (Set, any)", - function (set, e) return rawset (set, e, nil) end), - - --- Find the difference of two sets. - -- @function difference - -- @tparam prototype set1 a set - -- @tparam prototype set2 another set - -- @treturn prototype a copy of *set1* with elements of *set2* removed - -- @usage - -- all = set.difference (all, Set {32, 49, 56}) - difference = X ("difference (Set, Set)", difference), - - --- Iterator for sets. - -- @function elems - -- @tparam prototype set a set - -- @return *set* iterator - -- @todo Make the iterator return only the key - -- @usage - -- for code in set.elems (isprintable) do print (code) end - elems = X ("elems (Set)", elems), - - --- Find whether two sets are equal. - -- @function equal - -- @tparam prototype set1 a set - -- @tparam prototype set2 another set - -- @treturn boolean `true` if *set1* and *set2* each contain identical - -- elements, `false` otherwise - -- @usage - -- if set.equal (keys, Set {META, CTRL, "x"}) then process (keys) end - equal = X ( "equal (Set, Set)", equal), - - --- Insert an element into a set. - -- @function insert - -- @tparam prototype set a set - -- @param e element - -- @treturn prototype the modified *set* - -- @usage - -- for byte = 32,126 do - -- set.insert (isprintable, string.char (byte)) - -- end - insert = X ("insert (Set, any)", insert), - - --- Find the intersection of two sets. - -- @function intersection - -- @tparam prototype set1 a set - -- @tparam prototype set2 another set - -- @treturn prototype a new set with elements in both *set1* and *set2* - -- @usage - -- common = set.intersection (a, b) - intersection = X ("intersection (Set, Set)", intersection), - - --- Say whether an element is in a set. - -- @function difference - -- @tparam prototype set a set - -- @param e element - -- @return `true` if *e* is in *set*, otherwise `false` - -- otherwise - -- @usage - -- if not set.member (keyset, pressed) then return nil end - member = X ("member (Set, any)", member), - - --- Find whether one set is a proper subset of another. - -- @function proper_subset - -- @tparam prototype set1 a set - -- @tparam prototype set2 another set - -- @treturn boolean `true` if *set2* contains all elements in *set1* - -- but not only those elements, `false` otherwise - -- @usage - -- if set.proper_subset (a, b) then - -- for e in set.elems (set.difference (b, a)) do - -- set.delete (b, e) - -- end - -- end - -- assert (set.equal (a, b)) - proper_subset = X ("proper_subset (Set, Set)", proper_subset), - - --- Find whether one set is a subset of another. - -- @function subset - -- @tparam prototype set1 a set - -- @tparam prototype set2 another set - -- @treturn boolean `true` if all elements in *set1* are also in *set2*, - -- `false` otherwise - -- @usage - -- if set.subset (a, b) then a = b end - subset = X ("subset (Set, Set)", subset), - - --- Find the symmetric difference of two sets. - -- @function symmetric_difference - -- @tparam prototype set1 a set - -- @tparam prototype set2 another set - -- @treturn prototype a new set with elements that are in *set1* or *set2* - -- but not both - -- @usage - -- unique = set.symmetric_difference (a, b) - symmetric_difference = X ("symmetric_difference (Set, Set)", - symmetric_difference), - - --- Find the union of two sets. - -- @function union - -- @tparam prototype set1 a set - -- @tparam prototype set2 another set - -- @treturn prototype a copy of *set1* with elements in *set2* merged in - -- @usage - -- all = set.union (a, b) - union = X ("union (Set, Set)", union), -} diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua deleted file mode 100644 index 12aa6ec..0000000 --- a/lib/std/strbuf.lua +++ /dev/null @@ -1,143 +0,0 @@ ---[[-- - String buffer prototype. - - Buffers are mutable by default, but being based on objects, they can - also be used in a functional style: - - local StrBuf = require "std.strbuf".prototype - local a = StrBuf {"a"} - local b = a:concat "b" -- mutate *a* - print (a, b) --> ab ab - local c = a {} .. "c" -- copy and append - print (a, c) --> ab abc - - In addition to the functionality described here, StrBuf objects also - have all the methods and metamethods of the @{std.object.prototype} - (except where overridden here), - - Prototype Chain - --------------- - - table - `-> Container - `-> Object - `-> StrBuf - - @prototype std.strbuf -]] - - -local ipairs = ipairs -local tostring = tostring - -local table_concat = table.concat - - -local _ = { - object = require "std.object", - std = require "std._base", -} - -local Module = _.std.object.Module -local Object = _.object.prototype - -local argscheck = _.std.typecheck and _.std.typecheck.argscheck -local merge = _.std.base.merge - -local _ENV = _.std.strict and _.std.strict {} or {} - -_ = nil - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - -local function __concat (self, x) - self[#self + 1] = x - return self -end - - -local function __tostring (self) - local strs = {} - for _, e in ipairs (self) do strs[#strs + 1] = tostring (e) end - return table_concat (strs) -end - - ---[[ ================= ]]-- ---[[ Public Interface. ]]-- ---[[ ================= ]]-- - - -local function X (decl, fn) - return argscheck and argscheck ("std.strbuf." .. decl, fn) or fn -end - - -local methods = { - --- Methods - -- @section methods - - --- Add a object to a buffer. - -- Elements are stringified lazily, so if you add a table and then - -- change its contents, the contents of the buffer will be affected - -- too. - -- @function prototype:concat - -- @param x object to add to buffer - -- @treturn prototype modified buffer - -- @usage - -- c = StrBuf {} :concat "append this" :concat (StrBuf {" and", " this"}) - concat = X ("concat (StrBuf, any)", __concat), -} - - - ---[[ ================== ]]-- ---[[ Type Declarations. ]]-- ---[[ ================== ]]-- - - ---- StrBuf prototype object. --- @object prototype --- @string[opt="StrBuf"] _type object name --- @see std.object.prototype --- @usage --- local StrBuf = require "std.strbuf".prototype --- local a = StrBuf {1, 2, 3} --- local b = StrBuf {a, "five", "six"} --- a = a .. 4 --- b = b:concat "seven" --- print (a, b) --> 1234 1234fivesixseven --- os.exit (0) - -return Module { - prototype = Object { - _type = "StrBuf", - - --- Metamethods - -- @section metamethods - - __index = methods, - - --- Support concatenation to StrBuf objects. - -- @function prototype:__concat - -- @param x a string, or object that can be coerced to a string - -- @treturn prototype modified *buf* - -- @see concat - -- @usage - -- buf = buf .. x - __concat = __concat, - - --- Support fast conversion to Lua string. - -- @function prototype:__tostring - -- @treturn string concatenation of buffer contents - -- @see tostring - -- @usage - -- str = tostring (buf) - __tostring = __tostring, - }, -} diff --git a/lib/std/string.lua b/lib/std/string.lua index 576f24a..d9ee05b 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -21,28 +21,24 @@ local type = type local io_stderr = io.stderr local math_abs = math.abs local math_floor = math.floor +local table_concat = table.concat local table_insert = table.insert local string_format = string.format -local _ = { - std = require "std._base", - strbuf = require "std.strbuf", -} - -local StrBuf = _.strbuf.prototype +local _ = require "std._base" -local _tostring = _.std.tostring -local argscheck = _.std.typecheck and _.std.typecheck.argscheck -local copy = _.std.base.copy -local escape_pattern = _.std.string.escape_pattern -local len = _.std.operator.len -local merge = _.std.base.merge -local render = _.std.string.render -local sortkeys = _.std.base.sortkeys -local split = _.std.string.split +local _tostring = _.tostring +local argscheck = _.typecheck and _.std.typecheck.argscheck +local copy = _.base.copy +local escape_pattern = _.string.escape_pattern +local len = _.operator.len +local merge = _.base.merge +local render = _.string.render +local sortkeys = _.base.sortkeys +local split = _.string.split -local _ENV = _.std.strict and _.std.strict {} or _ENV +local _ENV = _.strict and _.strict {} or _ENV _ = nil @@ -142,7 +138,7 @@ local function wrap (s, w, ind, ind1) ind1 = ind1 or ind assert (ind1 < w and ind < w, "the indents must be less than the line width") - local r = StrBuf { string.rep (" ", ind1) } + local r = { string.rep (" ", ind1) } local i, lstart, lens = 1, ind1, len (s) while i <= lens do local j = i + w - lstart @@ -153,14 +149,14 @@ local function wrap (s, w, ind, ind1) while s[j] == " " do j = j - 1 end - r:concat (s:sub (i, j)) + table_insert (r, s:sub (i, j)) i = ni if i < lens then - r:concat ("\n" .. string.rep (" ", ind)) + table_insert (r, "\n" .. string.rep (" ", ind)) lstart = ind end end - return tostring (r) + return table_concat (r) end diff --git a/lib/std/table.lua b/lib/std/table.lua index d7843ed..f3d9679 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -265,8 +265,6 @@ M = { -- @section accessorfuncs --- Make a shallow copy of a table, including any metatable. - -- - -- To make deep copies, use @{std.tree.clone}. -- @function clone -- @tparam table t source table -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` diff --git a/lib/std/tree.lua b/lib/std/tree.lua deleted file mode 100644 index 0e170a0..0000000 --- a/lib/std/tree.lua +++ /dev/null @@ -1,366 +0,0 @@ ---[[-- - Tree container prototype. - - This module returns a table of tree operators, as well as the prototype - for a Tree container object. - - This is not a search tree, but rather a way to efficiently store and - retrieve values stored with a path as a key, such as a multi-key - keytable. Although it does have iterators for walking the tree with - various algorithms. - - In addition to the functionality described here, Tree containers also - have all the methods and metamethods of the @{std.container.prototype} - (except where overridden here), - - Prototype Chain - --------------- - - table - `-> Container - `-> Tree - - @prototype std.tree -]] - - -local getmetatable = getmetatable -local rawget = rawget -local rawset = rawset -local setmetatable = setmetatable -local type = type - -local coroutine_yield = coroutine.yield -local coroutine_wrap = coroutine.wrap -local table_remove = table.remove -local table_unpack = table.unpack or unpack - - -local _ = { - container = require "std.container", - std = require "std._base", -} - -local Container = _.container.prototype -local Module = _.std.object.Module - -local _ipairs = _.std.ipairs -local _pairs = _.std.pairs -local argscheck = _.std.typecheck and _.std.typecheck.argscheck -local ielems = _.std.ielems -local last = _.std.base.last -local leaves = _.std.tree.leaves -local len = _.std.operator.len -local pack = _.std.table.pack - -local _ENV = _.std.strict and _.std.strict {} or {} - -_ = nil - - - ---[[ =============== ]]-- ---[[ Implementation. ]]-- ---[[ =============== ]]-- - - -local prototype -- forward declaration - - - ---- Tree iterator. --- @tparam function it iterator function --- @tparam prototype|table tr tree container or tree-like table --- @treturn string type ("leaf", "branch" (pre-order) or "join" (post-order)) --- @treturn table path to node (`{i1, ...in}`) --- @treturn node node -local function _nodes (it, tr) - local p = {} - local function visit (n) - if type (n) == "table" then - coroutine_yield ("branch", p, n) - for i, v in it (n) do - p[#p + 1] = i - visit (v) - table_remove (p) - end - coroutine_yield ("join", p, n) - else - coroutine_yield ("leaf", p, n) - end - end - return coroutine_wrap (visit), tr -end - - --- 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 clone (t, nometa) - local r = {} - if not nometa then - setmetatable (r, getmetatable (t)) - end - local d = {[t] = r} - local function copy (o, x) - for i, v in _pairs (x) do - if type (v) == "table" then - if not d[v] then - d[v] = {} - if not nometa then - setmetatable (d[v], getmetatable (v)) - end - o[i] = copy (d[v], v) - else - o[i] = d[v] - end - else - o[i] = v - end - end - return o - end - return copy (r, t) -end - - -local function get (t, k) - return t and t[k] or nil -end - - -local function merge (t, u) - for ty, p, n in _nodes (_pairs, u) do - if ty == "leaf" then - t[p] = n - end - end - return t -end - - -local function reduce (fn, d, ifn, ...) - local argt - if not callable (ifn) then - ifn, argt = pairs, pack (ifn, ...) - else - argt = pack (...) - end - - local nextfn, state, k = ifn (table_unpack (argt, 1, argt.n)) - local t = pack (nextfn (state, k)) -- table of iteration 1 - - local r = d -- initialise accumulator - while t[1] ~= nil do -- until iterator returns nil - k = t[1] - r = fn (r, table_unpack (t, 1, t.n)) -- pass all iterator results to fn - t = pack (nextfn (state, k)) -- maintain loop invariant - end - return r -end - - - ---[[ ============ ]]-- ---[[ Tree Object. ]]-- ---[[ ============ ]]-- - - -local function X (decl, fn) - return argscheck and argscheck ("std.tree." .. decl, fn) or fn -end - - ---- Return the object type, if set, otherwise the Lua type. --- @param x item to act on --- @treturn string object type of *x*, otherwise `type (x)` -local function _type (x) - return (getmetatable (x) or {})._type or type (x) -end - - ---- Tree prototype object. --- @object prototype --- @string[opt="Tree"] _type object name --- @see std.container.prototype --- @usage --- local tree = require "std.tree" --- local Tree = tree.prototype --- local tr = Tree {} --- tr[{"branch1", 1}] = "leaf1" --- tr[{"branch1", 2}] = "leaf2" --- tr[{"branch2", 1}] = "leaf3" --- print (tr[{"branch1"}]) --> Tree {leaf1, leaf2} --- print (tr[{"branch1", 2}]) --> leaf2 --- print (tr[{"branch1", 3}]) --> nil --- --> leaf1 leaf2 leaf3 --- for leaf in tree.leaves (tr) do --- io.write (leaf .. "\t") --- end -prototype = Container { - _type = "Tree", - - --- Metamethods - -- @section metamethods - - --- Deep retrieval. - -- @function prototype:__index - -- @param i non-table, or list of keys `{i1, ...i_n}` - -- @return `tr[i1]...[i_n]` if *i* is a key list, `tr[i]` otherwise - -- @todo the following doesn't treat list keys correctly - -- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first? - -- @usage - -- del_other_window = keymap[{"C-x", "4", KEY_DELETE}] - __index = function (tr, i) - if _type (i) == "table" then - return reduce (get, tr, ielems, i) - else - return rawget (tr, i) - end - end, - - --- Deep insertion. - -- @function prototype:__newindex - -- @param i non-table, or list of keys `{i1, ...i_n}` - -- @param[opt] v value - -- @usage - -- function bindkey (keylist, fn) keymap[keylist] = fn end - __newindex = function (tr, i, v) - if _type (i) == "table" then - for n = 1, len (i) - 1 do - if _type (tr[i[n]]) ~= "Tree" then - rawset (tr, i[n], prototype {}) - end - tr = tr[i[n]] - end - rawset (tr, last (i), v) - else - rawset (tr, i, v) - end - end, -} - - -return Module { - prototype = prototype, - - --- Functions - -- @section functions - - --- Make a deep copy of a tree or table, including any metatables. - -- @function clone - -- @tparam table tr tree or tree-like table - -- @tparam boolean nometa if non-`nil` don't copy metatables - -- @treturn prototype|table a deep copy of *tr* - -- @see std.table.clone - -- @see std.object.clone - -- @usage - -- tr = {"one", {two=2}, {{"three"}, four=4}} - -- copy = clone (tr) - -- copy[2].two=5 - -- assert (tr[2].two == 2) - clone = X ("clone (table, ?boolean|:nometa)", clone), - - --- Tree iterator which returns just numbered leaves, in order. - -- @function ileaves - -- @tparam prototype|table tr tree or tree-like table - -- @treturn function iterator function - -- @treturn prototype|table the tree *tr* - -- @see inodes - -- @see leaves - -- @usage - -- --> t = {"one", "three", "five"} - -- for leaf in ileaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} - -- do - -- t[#t + 1] = leaf - -- end - ileaves = X ("ileaves (table)", function (t) return leaves (_ipairs, t) end), - - --- Tree iterator over numbered nodes, in order. - -- - -- The iterator function behaves like @{nodes}, but only traverses the - -- array part of the nodes of *tr*, ignoring any others. - -- @function inodes - -- @tparam prototype|table tr tree or tree-like table to iterate over - -- @treturn function iterator function - -- @treturn tree|table the tree, *tr* - -- @see nodes - inodes = X ("inodes (table)", function (t) return _nodes (_ipairs, t) end), - - --- Tree iterator which returns just leaves. - -- @function leaves - -- @tparam table t tree or tree-like table - -- @treturn function iterator function - -- @treturn table *t* - -- @see ileaves - -- @see nodes - -- @usage - -- for leaf in leaves {"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} - -- do - -- t[#t + 1] = leaf - -- end - -- --> t = {2, 4, "five", "foo", "one", "three"} - -- table.sort (t, lambda "=tostring(_1) < tostring(_2)") - leaves = X ("leaves (table)", function (t) return leaves (_pairs, t) end), - - --- Destructively deep-merge one tree into another. - -- @function merge - -- @tparam table t destination tree - -- @tparam table u table with nodes to merge - -- @treturn table *t* with nodes from *u* merged in - -- @see std.table.merge - -- @usage - -- merge (dest, {{exists=1}, {{not = {present = { inside = "dest" }}}}}) - merge = X ("merge (table, table)", merge), - - --- Tree iterator over all nodes. - -- - -- The returned iterator function performs a depth-first traversal of - -- `tr`, and at each node it returns `{node-type, tree-path, tree-node}` - -- where `node-type` is `branch`, `join` or `leaf`; `tree-path` is a - -- list of keys used to reach this node, and `tree-node` is the current - -- node. - -- - -- Note that the `tree-path` reuses the same table on each iteration, so - -- you must `table.clone` a copy if you want to take a snap-shot of the - -- current state of the `tree-path` list before the next iteration - -- changes it. - -- @function nodes - -- @tparam prototype|table tr tree or tree-like table to iterate over - -- @treturn function iterator function - -- @treturn prototype|table the tree, *tr* - -- @see inodes - -- @usage - -- -- tree = +-- node1 - -- -- | +-- leaf1 - -- -- | '-- leaf2 - -- -- '-- leaf 3 - -- tree = Tree { Tree { "leaf1", "leaf2"}, "leaf3" } - -- for node_type, path, node in nodes (tree) do - -- print (node_type, path, node) - -- end - -- --> "branch" {} {{"leaf1", "leaf2"}, "leaf3"} - -- --> "branch" {1} {"leaf1", "leaf2") - -- --> "leaf" {1,1} "leaf1" - -- --> "leaf" {1,2} "leaf2" - -- --> "join" {1} {"leaf1", "leaf2"} - -- --> "leaf" {2} "leaf3" - -- --> "join" {} {{"leaf1", "leaf2"}, "leaf3"} - -- os.exit (0) - nodes = X ("nodes (table)", function (t) return _nodes (_pairs, t) end), -} diff --git a/specs/container_spec.yaml b/specs/container_spec.yaml deleted file mode 100644 index dbadc91..0000000 --- a/specs/container_spec.yaml +++ /dev/null @@ -1,131 +0,0 @@ -before: - Container = require "std.container".prototype - -specify std.container: -- context when required: - - context by name: - - it does not touch the global table: - expect (show_apis {added_to="_G", by="std.container"}). - to_equal {} - -- describe construction: - - context with table _init: - - it diagnoses missing arguments: | - if have_typecheck then - expect (Container ()). - to_raise "bad argument #1 to 'Container' (table expected, got no value)" - end - - it diagnoses too many arguments: | - if have_typecheck then - expect (Container ({}, false)). - to_raise "bad argument #2 to 'Container' (no more than 1 argument expected, got 2)" - end - - context with function _init: - - before: - Thing = Container { _type = "Thing", _init = function (obj) return obj end } - - it doesn't diagnose missing arguments: - expect (Thing ()).not_to_raise "any error" - - it doesn't diagnose too many args: - expect (Thing ({}, false)).not_to_raise "any error" - - - context from Container prototype: - - before: - things = Container {"foo", "bar", baz="quux"} - - it constructs a new container: - expect (things).not_to_be (Container) - expect (type (things)).to_be "table" - expect (objtype (things)).to_be "Container" - - it reuses the container metatable: - o, p = things {"o"}, things {"p"} - expect (getmetatable (o)).to_be (getmetatable (p)) - - it sets container fields from arguments: - o = Container {"foo", "bar", baz="quux"} - expect (o).to_equal (things) - - it serves as a prototype for new instances: - o = things {} - expect (objtype (o)).to_be "Container" - expect (o).to_copy (things) - expect (getmetatable (o)).to_be (getmetatable (things)) - - it separates '_' prefixed fields: - expect (Container {foo="bar", _baz="quux"}). - to_equal (Container {foo="bar"}) - - it puts '_' prefixed fields in a new metatable: - things = Container {foo="bar", _baz="quux"} - expect (getmetatable (things)).not_to_be (getmetatable (Container)) - expect (getmetatable (things)._baz).to_be "quux" - - it propagates '_type' field: - things = Container {1} - u, v = things {"u"}, things {"v"} - expect (objtype (u)).to_be "Container" - expect (objtype (v)).to_be (objtype (Container)) - - context with module functions: - - before: - Bag = require "std._base".object.Module { - prototype = Container { _type = "Bag" }, - count = function (bag) - local n = 0 - for _, m in pairs (bag) do n = n + m end - return n - end, - } - - it does not propagate module functions: - things = Bag {} - expect (things.count).to_be (nil) - - it does not provide object methods: | - things = Bag {} - expect (things:count ()).to_raise.any_of { - "attempt to call method 'count'", - "attempt to call a nil value (method 'count'" - } - - it does retain module functions: - things = Bag { apples = 1, oranges = 3 } - expect (Bag.count (things)).to_be (4) - - it does allow elements named after module functions: - things = Bag { count = 1337 } - expect (Bag.count (things)).to_be (1337) - - it propagates '_type' field: - things = Bag { bananas=0 } - u, v = things { bananas=1 }, things { coconuts=0 } - expect (objtype (u)).to_be "Bag" - expect (objtype (v)).to_be (objtype (Bag.prototype)) - - -- describe field access: - - before: - things = Container {"foo", "bar", baz="quux"} - - context with bracket notation: - - it provides access to existing contents: - expect (things[1]).to_be "foo" - expect (things["baz"]).to_be "quux" - - it assigns new contents: - things["new"] = "value" - expect (things["new"]).to_be "value" - - context with dot notation: - - it provides access to existing contents: - expect (things.baz).to_be "quux" - - it assigns new contents: - things.new = "value" - expect (things.new).to_be "value" - - -- describe __tostring: - - before: - things = Container {_type = "Derived", "one", "two", "three"} - - it returns a string: - expect (type (tostring (things))).to_be "string" - - it contains the type: - expect (tostring (Container {})).to_contain "Container" - expect (tostring (things)).to_contain (objtype (things)) - - it contains the ordered array part elements: - expect (tostring (things)).to_contain "one, two, three" - - it contains the ordered dictionary part elements: - expect (tostring (Container {one = true, two = true, three = true})). - to_contain "one=true, three=true, two=true" - expect (tostring (things {one = true, two = true, three = true})). - to_contain "one=true, three=true, two=true" - - it contains a ';' separator only when container has array and dictionary parts: - expect (tostring (things)).not_to_contain ";" - expect (tostring (Container {one = true, two = true, three = true})). - not_to_contain ";" - expect (tostring (things {one = true, two = true, three = true})). - to_contain ";" diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml deleted file mode 100644 index 6a2c819..0000000 --- a/specs/list_spec.yaml +++ /dev/null @@ -1,392 +0,0 @@ -before: - this_module = "std.list" - M = require (this_module) - List = M.prototype - l = List {"foo", "bar", "baz"} - - -specify std.list: -- context when required: - - context by name: - - it does not perturb the global namespace: - expect (show_apis {added_to="_G", by="std.list"}).to_equal {} - - context via the std module: - - it does not perturb the global namespace: - expect (show_apis {added_to="_G", by="std"}).to_equal {} - - -- describe construction: - - context from List clone method: - - it constructs a new list: - l = List:clone {} - expect (l).not_to_be (List) - expect (objtype (l)).to_be "List" - - it reuses the List metatable: - l, m = List:clone {"l"}, List:clone {"m"} - expect (getmetatable (l)).to_be (getmetatable (m)) - - it initialises List with constructor parameters: - m = List:clone {"foo", "bar", "baz"} - expect (m).to_equal (l) - - it serves as a prototype for new instances: - m = l:clone {} - expect (objtype (m)).to_be "List" - expect (m).to_equal (l) - expect (getmetatable (m)).to_be (getmetatable (l)) - - # List {args} is just syntactic sugar for List:clone {args} - - context from List object prototype: - - it constructs a new List: - l = List {} - expect (l).not_to_be (List) - expect (objtype (l)).to_be "List" - - it reuses the List metatable: - l, m = List {"l"}, List {"m"} - expect (getmetatable (l)).to_be (getmetatable (m)) - - it initialises List with constructor parameters: - m = List {"foo", "bar", "baz"} - expect (m).to_equal (l) - - it serves as a prototype for new instances: - m = l {} - expect (objtype (m)).to_be "List" - expect (m).to_equal (l) - expect (getmetatable (m)).to_be (getmetatable (l)) - - -- describe metatable propagation: - - it reuses the metatable for List constructed objects: - m = List {"foo", "bar"} - expect (getmetatable (m)).to_be (getmetatable (l)) - - -- describe append: - - before: - f = M.append - - - context with bad arguments: - badargs.diagnose (f, "std.list.append (List, any)") - - - context as a module function: - - it returns a List object: - expect (objtype (f (l, "quux"))).to_be "List" - - it works for an empty List: - expect (f (List {}, "quux")).to_equal (List {"quux"}) - - it appends an item to a List: - expect (f (l, "quux")). - to_equal (List {"foo", "bar", "baz", "quux"}) - - - context as an object method: - - before: - f = l.append - - - it returns a List object: - expect (objtype (f (l, "quux"))).to_be "List" - - it works for an empty List: - expect (f (List {}, "quux")).to_equal (List {"quux"}) - - it appends an item to a List: - expect (f (l, "quux")). - to_equal (List {"foo", "bar", "baz", "quux"}) - - - context as a List metamethod: - - it returns a List object: - expect (objtype (l + "quux")).to_be "List" - - it works for an empty list: - expect (List {} + "quux").to_equal (List {"quux"}) - - it appends an item to a list: - expect (l + "quux"). - to_equal (List {"foo", "bar", "baz", "quux"}) - - -- describe compare: - - before: - a, b = List {"foo", "bar"}, List {"foo", "baz"} - - f = M.compare - - - context with bad arguments: - badargs.diagnose (f, "std.list.compare (List, List|table)") - - - context as a module function: - - it returns -1 when the first list is less than the second: - expect (f (a, {"foo", "baz"})).to_be (-1) - expect (f (a, List {"foo", "baz"})).to_be (-1) - - it returns -1 when the second list has additional elements: - expect (f (List {"foo"}, {"foo", "bar"})).to_be (-1) - expect (f (List {"foo"}, List {"foo", "bar"})).to_be (-1) - - it returns 0 when two lists are the same: - expect (f (a, {"foo", "bar"})).to_be (0) - expect (f (a, List {"foo", "bar"})).to_be (0) - - it returns +1 when the first list is greater than the second: - expect (f (a, {"baz", "quux"})).to_be (1) - expect (f (a, List {"baz", "quux"})).to_be (1) - - it returns +1 when the first list has additional elements: - expect (f (a, {"foo"})).to_be (1) - expect (f (a, List {"foo"})).to_be (1) - - it compares numerically when both arguments can be coerced: - a, b = List {"1", "2", "3"}, List {"1", "2", "10"} - expect (f (a, b)).to_be (-1) - - - context as an object method: - - before: - f = a.compare - - - it returns -1 when the first list is less than the second: - expect (f (a, {"foo", "baz"})).to_be (-1) - expect (f (a, List {"foo", "baz"})).to_be (-1) - - it returns -1 when the second list has additional elements: | - b = List {"foo"} - expect (f (b, {"foo", "bar"})).to_be (-1) - expect (List {"foo"}:compare (List {"foo", "bar"})).to_be (-1) - - it returns 0 when two lists are the same: - expect (f (a, {"foo", "bar"})).to_be (0) - expect (f (a, List {"foo", "bar"})).to_be (0) - - it returns +1 when the first list is greater than the second: - expect (f (a, {"baz", "quux"})).to_be (1) - expect (f (a, List {"baz", "quux"})).to_be (1) - - it returns +1 when the first list has additional elements: - expect (f (a, {"foo"})).to_be (1) - expect (f (a, List {"foo"})).to_be (1) - - it compares numerically when both arguments can be coerced: - a, b = List {"1", "2", "3"}, List {"1", "2", "10"} - expect (f (a, b)).to_be (-1) - - - context as a '<' List metamethod: - - it succeeds when the first list is less than the second: - expect (a < b).to_be (true) - - it fails when the first list is not less than the second: - expect (a < a).to_be (false) - expect (b < a).to_be (false) - - it compares numerically when both arguments can be coerced: - a, b = List {"1", "2", "3"}, List {"1", "2", "10"} - expect (a < b).to_be (true) - - - context as a '>' List metamethod: - - it succeeds when the first list is greater than the second: - expect (b > a).to_be (true) - - it fails when the first list is not greater than the second: - expect (b > b).to_be (false) - expect (a > b).to_be (false) - - it compares numerically when both arguments can be coerced: - a, b = List {"1", "2", "3"}, List {"1", "2", "10"} - expect (a > b).to_be (false) - - - context as a '<=' List metamethod: - - it succeeds when the first list is less than or equal to the second: - expect (a <= b).to_be (true) - expect (a <= a).to_be (true) - - it fails when the first list is not less than or equal to the second: - expect (b <= a).to_be (false) - - it compares numerically when both arguments can be coerced: - a, b = List {"1", "2", "3"}, List {"1", "2", "10"} - expect (a <= b).to_be (true) - - - context as a '>=' List metamethod: - - it succeeds when the first list is greater than or equal to the second: - expect (b >= a).to_be (true) - expect (b >= b).to_be (true) - - it fails when the first list is not greater than or equal to the second: - expect (a >= b).to_be (false) - - it compares numerically when both arguments can be coerced: - a, b = List {"1", "2", "3"}, List {"1", "2", "10"} - expect (a >= b).to_be (false) - - -- describe concat: - - before: - l = List {"foo", "bar"} - - f = M.concat - - - context with bad arguments: - badargs.diagnose (f, "std.list.concat (List, List|table*)") - - - context as a module function: - - it returns a List object: - expect (objtype (f (l, l))).to_be "List" - - it works for an empty List: - expect (f (List {}, {"baz"})).to_equal (List {"baz"}) - expect (f (List {}, List {"baz"})).to_equal (List {"baz"}) - - it concatenates Lists: - expect (f (l, {"baz", "quux"})). - to_equal (List {"foo", "bar", "baz", "quux"}) - expect (f (l, List {"baz", "quux"})). - to_equal (List {"foo", "bar", "baz", "quux"}) - expect (f (l, {"baz"}, {"quux"})). - to_equal (List {"foo", "bar", "baz", "quux"}) - expect (f (l, List {"baz"}, List {"quux"})). - to_equal (List {"foo", "bar", "baz", "quux"}) - - - context as an object method: - - before: - f = l.concat - - - it returns a List object: - expect (objtype (f (l, l))).to_be "List" - - it works for an empty List: - expect (f (List {}, {"baz"})).to_equal (List {"baz"}) - expect (f (List {}, List {"baz"})).to_equal (List {"baz"}) - - it concatenates Lists: - expect (f (l, {"baz", "quux"})). - to_equal (List {"foo", "bar", "baz", "quux"}) - expect (f (l, List {"baz", "quux"})). - to_equal (List {"foo", "bar", "baz", "quux"}) - expect (f (l, {"baz"}, {"quux"})). - to_equal (List {"foo", "bar", "baz", "quux"}) - expect (f (l, List {"baz"}, List {"quux"})). - to_equal (List {"foo", "bar", "baz", "quux"}) - - # Beware that .. operations are right associative - - context as a List metamethod: - - it returns a List object: - expect (objtype (l .. List {"baz"})).to_be "List" - - it works for an empty List: - expect (List {} .. {"baz"}).to_equal (List {"baz"}) - expect (List {} .. List {"baz"}).to_equal (List {"baz"}) - - it concatenates Lists: - expect (l .. {"baz", "quux"}). - to_equal (List {"foo", "bar", "baz", "quux"}) - expect (l .. List {"baz", "quux"}). - to_equal (List {"foo", "bar", "baz", "quux"}) - expect ({"baz"} .. {"quux"} .. l). - to_equal (List {"baz", "quux", "foo", "bar"}) - expect (l .. List {"baz"} .. List {"quux"}). - to_equal (List {"foo", "bar", "baz", "quux"}) - - -- describe cons: - - before: - f = M.cons - - - context with bad arguments: - badargs.diagnose (f, "std.list.cons (List, any)") - - - context as a module function: - - it returns a List object: - expect (objtype (f (l, "x"))).to_be "List" - - it prepends an item to a List: - expect (f (l, "x")).to_equal (List {"x", "foo", "bar", "baz"}) - - it works for empty Lists: - expect (f (List {}, "x")).to_equal (List {"x"}) - - - context as an object method: - - before: - f = l.cons - - - it returns a List object: - expect (objtype (f (l, "x"))).to_be "List" - - it prepends an item to a List: - expect (f (l, "x")).to_equal (List {"x", "foo", "bar", "baz"}) - - it works for empty Lists: - expect (f (List {}, "x")).to_equal (List {"x"}) - - -- describe rep: - - before: - l = List {"foo", "bar"} - - f = M.rep - - - context with bad arguments: - badargs.diagnose (f, "std.list.rep (List, int)") - - - context as a module function: - - it returns a List object: - expect (objtype (f (l, 3))).to_be "List" - - it works for an empty List: - expect (f (List {}, 99)).to_equal (List {}) - - it repeats the contents of a List: - expect (f (l, 3)). - to_equal (List {"foo", "bar", "foo", "bar", "foo", "bar"}) - - - context as an object method: - - before: - f = l.rep - - - it returns a List object: - expect (objtype (f (l, 3))).to_be "List" - - it works for an empty List: - expect (f (List {}, 99)).to_equal (List {}) - - it repeats the contents of a List: - expect (f (l, 3)). - to_equal (List {"foo", "bar", "foo", "bar", "foo", "bar"}) - - -- describe sub: - - before: - l = List {1, 2, 3, 4, 5, 6, 7} - - f = M.sub - - - context with bad arguments: - badargs.diagnose (f, "std.list.sub (List, ?int, ?int)") - - - context as a module function: - - it returns a List object: - expect (objtype (f (l, 1, 1))).to_be "List" - - it makes a List from a subrange of another List: - expect (f (l, 2, 5)).to_equal (List {2, 3, 4, 5}) - - it truncates the result if 'to' argument is too large: - expect (f (l, 5, 10)).to_equal (List {5, 6, 7}) - - it defaults 'to' to the end of the List: - expect (f (l, 5)).to_equal (List {5, 6, 7}) - - it defaults 'from' to the beginning of the List: - expect (f (l)).to_equal (l) - - it returns an empty List when 'from' is greater than 'to': - expect (f (l, 2, 1)).to_equal (List {}) - - it counts from the end of the List for a negative 'from' argument: - expect (f (l, -3)).to_equal (List {5, 6, 7}) - - it counts from the end of the List for a negative 'to' argument: - expect (f (l, -5, -2)).to_equal (List {3, 4, 5, 6}) - - - context as an object method: - - before: - f = l.sub - - - it returns a List object: - expect (objtype (f (l, 1, 1))).to_be "List" - - it makes a List from a subrange of another List: - expect (f (l, 2, 5)).to_equal (List {2, 3, 4, 5}) - - it truncates the result if 'to' argument is too large: - expect (f (l, 5, 10)).to_equal (List {5, 6, 7}) - - it defaults 'to' to the end of the List: - expect (f (l, 5)).to_equal (List {5, 6, 7}) - - it defaults 'from' to the beginning of the List: - expect (f (l)).to_equal (l) - - it returns an empty List when 'from' is greater than 'to': - expect (f (l, 2, 1)).to_equal (List {}) - - it counts from the end of the List for a negative 'from' argument: - expect (f (l, -3)).to_equal (List {5, 6, 7}) - - it counts from the end of the List for a negative 'to' argument: - expect (f (l, -5, -2)).to_equal (List {3, 4, 5, 6}) - - -- describe tail: - - before: - l = List {1, 2, 3, 4, 5, 6, 7} - - f = M.tail - - - context with bad arguments: - badargs.diagnose (f, "std.list.tail (List)") - - - context as a module function: - - it returns a List object: - expect (objtype (f (l))).to_be "List" - - it makes a new List with the first element removed: - expect (f (l)).to_equal (List {2, 3, 4, 5, 6, 7}) - - it works for an empty List: - expect (f (List {})).to_equal (List {}) - - it returns an empty List when passed a List with one element: - expect (f (List {1})).to_equal (List {}) - - - context as an object method: - - before: - f = l.tail - - - it returns a List object: - expect (objtype (f (l))).to_be "List" - - it makes a new List with the first element removed: - expect (f (l)).to_equal (List {2, 3, 4, 5, 6, 7}) - - it works for an empty List: - expect (f (List {})).to_equal (List {}) - - it returns an empty List when passed a List with one element: - expect (f (List {1})).to_equal (List {}) diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml deleted file mode 100644 index c6344a4..0000000 --- a/specs/object_spec.yaml +++ /dev/null @@ -1,295 +0,0 @@ -before: - this_module = "std.object" - - object = require (this_module) - Object = object.prototype - obj = Object {"foo", "bar", baz="quux"} - - function copy (t) - local r = {} - for k, v in pairs (t) do r[k] = v end - return r - end - -specify std.object: -- context when required: - - context by name: - - it does not touch the global table: - expect (show_apis {added_to="_G", by="std.object"}). - to_equal {} - -- describe construction: - - context from Object clone method: - - it constructs a new object: - o = Object:clone {} - expect (o).not_to_be (Object) - expect (type (o)).to_be "table" - expect (objtype (o)).to_be "Object" - - it reuses the Object metatable: - o = obj:clone {"o"} - p = o:clone {"p"} - expect (p).not_to_be (o) - expect (getmetatable (o)).to_be (getmetatable (p)) - - it sets object fields from arguments: - expect (obj:clone {}).to_copy (obj) - - it serves as a prototype for new instances: - o = obj:clone {} - expect (objtype (o)).to_be "Object" - expect (o).to_copy (obj) - expect (getmetatable (o)).to_be (getmetatable (obj)) - - it separates '_' prefixed fields: - expect (Object:clone {foo="bar", _baz="quux"}). - to_equal (Object:clone {foo="bar"}) - - it puts '_' prefixed fields in a new metatable: - o = Object:clone {foo="bar", _baz="quux"} - expect (getmetatable (o)).not_to_be (getmetatable (Object)) - expect (getmetatable (o)._baz).to_be "quux" - - -- describe type: - - before: - o = Object {} - fn = object.type - - - context when called from the object module: - - it reports the type stored in the object's metatable: - expect (fn (o)).to_be "Object" - - it reports the type of a cloned object: - expect (fn (o {})).to_be "Object" - - it reports the type of a derived object: - Example = Object {_type = "Example"} - expect (fn (Example)).to_be "Example" - - it reports the type of a cloned derived object: - Portal = Object {_type = "Demon"} - p = Portal {} - expect (fn (p)).to_be "Demon" - expect (fn (p {})).to_be "Demon" - - it returns nil for a primitive object: - expect (fn (nil)).to_be (nil) - expect (fn (0.0)).to_be (nil) - expect (fn ('0.0')).to_be (nil) - expect (fn (function () end)).to_be (nil) - expect (fn {}).to_be (nil) - - -- describe instantiation from a prototype: - - context when _init is nil: - - before: - Array = Object { - _type = "Array", - "foo", "bar", "baz", - } - Array._init = nil - - - it contains user-defined fields: - expect (copy (Array)). - to_equal {"foo", "bar", "baz"} - - it sets array part of instance object from positional parameters: - array = Array {"first", "second", "third"} - expect (copy (array)). - to_equal {"first", "second", "third"} - - it uses prototype values for missing positional parameters: - array = Array {"first", "second"} - expect (copy (array)). - to_equal {"first", "second", "baz"} - - it merges surplas positional parameters: - array = Array {"first", "second", "third", "fourth"} - expect (copy (array)). - to_equal {"first", "second", "third", "fourth"} - - - context when _init is an empty table: - - before: - Prototype = Object { - _type = "Prototype"; - _init = {}, - "first", "second", "third", - } - - it contains user-defined fields: - expect (copy (Prototype)). - to_equal {"first", "second", "third"} - - it ignores positional parameters: | - instance = Prototype {"foo", "bar"} - expect (instance).to_copy (Prototype) - - - context when _init is a table of field names: - - before: - Process = Object { - _type = "Process", - _init = {"status", "output", "errout"}, - status = -1, - output = "empty", - errout = "no errors", - } - - it contains user-defined fields: - expect (copy (Process)). - to_equal {status = -1, output = "empty", errout = "no errors"} - - it sets user-defined fields from positional parameters: - proc = Process {0, "output", "diagnostics"} - expect (copy (proc)). - to_equal {status = 0, output = "output", errout = "diagnostics"} - - it uses prototype values for missing positional parameters: - proc = Process {0, "output"} - expect (copy (proc)). - to_equal {status = 0, output = "output", errout = "no errors"} - - it discards surplus positional parameters: - proc = Process {0, "output", "diagnostics", "garbage"} - expect (copy (proc)). - to_equal { status = 0, output = "output", errout = "diagnostics" } - - - context when _init is a function: - - before: - Prototype = Object { - _type = "Prototype", - f1 = "proto1", f2 = "proto2", - _init = function (self, ...) - self.args = unpack {...} - self.count = select ("#", ...) - return self - end, - } - - it passes user defined fields to custom _init function: - instance = Prototype {"param1", "param2"} - expect ({instance.f1, instance.f2, instance.args}). - to_equal {"proto1", "proto2", {"param1", "param2"}} - - it propagates arguments correctly: - expect (Prototype ().count).to_be (0) - expect (Prototype ("one").count).to_be (1) - expect (Prototype ("one", "two").count).to_be (2) - - it propagates nil arguments correctly: - expect (Prototype (nil).count).to_be (1) - expect (Prototype (false, nil).count).to_be (2) - expect (Prototype (nil, false).count).to_be (2) - expect (Prototype (nil, nil).count).to_be (2) - - -- describe field access: - - before: - Prototype = Object { - _type = "Prototype", - _init = { "field", "method"}, - field = "in prototype", - method = function (self, ...) - return objtype (self) .. " class, " .. - table.concat ({...}, ", ") - end, - } - instance = Prototype {"in object", function (self, ...) - return objtype (self) .. " instance, " .. - table.concat ({...}, ", ") - end, - } - - - it provides object field access with dot notation: - expect (instance.field).to_be "in object" - - it provides class field acces with dot notation: - expect (Prototype.field).to_be "in prototype" - - it provides object method acces with colon notation: - expect (instance:method "object method call"). - to_be "Prototype instance, object method call" - - it provides class method access with class dot notation: - expect (Prototype.method (instance, "class method call")). - to_be "Prototype class, class method call" - - it allows new instance fields to be added: - instance.newfield = "new" - expect (instance.newfield).to_be "new" - - it allows new instance methods to be added: - instance.newmethod = function (self) - return objtype (self) .. ", new instance method" - end - expect (instance:newmethod ()).to_be "Prototype, new instance method" - - it allows new class methods to be added: - Prototype.newmethod = function (self) - return objtype (self) .. ", new class method" - end - expect (Prototype.newmethod (instance)). - to_be "Prototype, new class method" - - -- describe object method propagation: - - context with no custom instance methods: - # :type is a method defined by the root object - - it inherits prototype object methods: - instance = Object { type = object.type } - expect (instance:type ()).to_be "Object" - - it propagates prototype methods to derived instances: - Derived = Object { _type = "Derived", type = object.type } - instance = Derived {} - expect (instance:type ()).to_be "Derived" - - context with custom object methods: - - before: - bag = Object { - _type = "bag", - __index = { - add = function (self, item) - self[item] = (self[item] or 0) + 1 - return self - end, - type = object.type, - }, - } - - it inherits prototype object methods: - expect (bag:type ()).to_be "bag" - - it propagates prototype methods to derived instances: - instance = bag {} - expect (instance:type ()).to_be "bag" - - it supports method calls: - expect (bag:add "foo").to_be (bag) - expect (bag.foo).to_be (1) - - -# Metatable propagation is an important property of Object cloning, -# because Lua will only call __lt and __le metamethods when both -# arguments share the same metatable - i.e. the previous behaviour -# of making each object its own metatable precluded ever being able -# to use __lt and __le! -- describe object metatable propagation: - - before: root_mt = getmetatable (Object) - - - context with no custom metamethods: - - it inherits prototype object metatable: - instance = Object {} - expect (getmetatable (instance)).to_be (root_mt) - - it propagates prototype metatable to derived instances: - Derived = Object {_type = "Derived"} - instance = Derived {} - expect (getmetatable (Derived)).not_to_be (root_mt) - expect (getmetatable (instance)).to_be (getmetatable (Derived)) - - context with custom metamethods: - - before: - bag = Object { - _type = "bag", - __lt = function (a, b) return a[1] < b[1] end, - } - - it has it's own metatable: - expect (getmetatable (bag)).not_to_be (root_mt) - - it propagates prototype metatable to derived instances: - instance = bag {} - expect (getmetatable (instance)).to_be (getmetatable (bag)) - - it supports __lt calls: | - a, b = bag {"a"}, bag {"b"} - expect (a < b).to_be (true) - expect (a < a).to_be (false) - expect (a > b).to_be (false) - - -- describe __tostring: - - before: - o = Object {_type = "Derived", "one", "two", "three"} - - it returns a string: - expect (type (tostring (o))).to_be "string" - - it contains the type: - expect (tostring (Object {})).to_contain "Object" - expect (tostring (o)).to_contain (objtype (o)) - - it contains the ordered array part elements: - expect (tostring (o)).to_contain "one, two, three" - - it contains the ordered dictionary part elements: - expect (tostring (Object {one = true, two = true, three = true})). - to_contain "one=true, three=true, two=true" - expect (tostring (o {one = true, two = true, three = true})). - to_contain "one=true, three=true, two=true" - - it contains a ';' separator only when object has array and dictionary parts: - expect (tostring (o)).not_to_contain ";" - expect (tostring (Object {one = true, two = true, three = true})). - not_to_contain ";" - expect (tostring (o {one = true, two = true, three = true})). - to_contain ";" diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml deleted file mode 100644 index 48f981f..0000000 --- a/specs/set_spec.yaml +++ /dev/null @@ -1,307 +0,0 @@ -before: - set = require "std.set" - Set = set.prototype - s = Set {"foo", "bar", "bar"} - -specify std.set: -- describe require: - - it does not perturb the global namespace: - expect (show_apis {added_to="_G", by="std.set"}). - to_equal {} - - -- describe construction: - - it constructs a new set: - s = Set {} - expect (s).not_to_be (Set) - expect (objtype (s)).to_be "Set" - - it initialises set with constructor parameters: - t = Set {"foo", "bar", "bar"} - expect (t).to_equal (s) - - it serves as a prototype for new instances: - obj = s {} - expect (objtype (obj)).to_be "Set" - expect (obj).to_equal (s) - expect (getmetatable (obj)).to_be (getmetatable (s)) - - it serves as a prototype for new types: - Bag = Set { _type = "Bag" } - expect (objtype (Bag)).to_be "Bag" - bag = Bag {"goo", "gar", "gar"} - expect (objtype (bag)).to_be "Bag" - -- describe delete: - - context when called as a Set module function: - - before: - fn = set.delete - s = Set {"foo", "bar", "baz"} - - it returns a set object: - expect (objtype (fn (s, "foo"))).to_be "Set" - - it is destructive: - fn (s, "bar") - expect (s).not_to_have_member "bar" - - it returns the modified set: - expect (fn (s, "baz")).not_to_have_member "baz" - - it ignores removal of non-members: | - clone = s {} - expect (fn (s, "quux")).to_equal (clone) - - it deletes a member from the set: - expect (s).to_have_member "bar" - fn (s, "bar") - expect (s).not_to_have_member "bar" - - it works with an empty set: - expect (fn (Set {}, "quux")).to_equal (Set {}) - - -- describe difference: - - before: - fn = set.difference - r = Set {"foo", "bar", "baz"} - s = Set {"bar", "baz", "quux"} - - - context when called as a Set module function: - - it returns a set object: - expect (objtype (fn (r, s))).to_be "Set" - - it is non-destructive: - fn (r, s) - expect (r).to_equal (Set {"foo", "bar", "baz"}) - expect (s).to_equal (Set {"bar", "baz", "quux"}) - - it returns a set containing members of the first that are not in the second: - expect (fn (r, s)).to_equal (Set {"foo"}) - - context when called as a set metamethod: - - it returns a set object: - expect (objtype (r - s)).to_be "Set" - - it is non-destructive: - q = r - s - expect (r).to_equal (Set {"foo", "bar", "baz"}) - expect (s).to_equal (Set {"bar", "baz", "quux"}) - - it returns a set containing members of the first that are not in the second: - expect (r - s).to_equal (Set {"foo"}) - - -- describe elems: - - before: - fn = set.elems - s = Set {"foo", "bar", "baz"} - - - context when called as a Set module function: - - it is an iterator over set members: - t = {} - for e in fn (s) do table.insert (t, e) end - table.sort (t) - expect (t).to_equal {"bar", "baz", "foo"} - - it works for an empty set: - t = {} - for e in fn (Set {}) do table.insert (t, e) end - expect (t).to_equal {} - - -- describe insert: - - context when called as a Set module function: - - before: - fn = set.insert - s = Set {"foo"} - - it returns a set object: - expect (objtype (fn (s, "bar"))).to_be "Set" - - it is destructive: - fn (s, "bar") - expect (s).to_have_member "bar" - - it returns the modified set: - expect (fn (s, "baz")).to_have_member "baz" - - it ignores insertion of existing members: - expect (fn (s, "foo")).to_equal (Set {"foo"}) - - it inserts a new member into the set: - expect (s).not_to_have_member "bar" - fn (s, "bar") - expect (s).to_have_member "bar" - - it works with an empty set: - expect (fn (Set {}, "foo")).to_equal (s) - - context when called as a set metamethod: - - before: - s = Set {"foo"} - - it returns a set object: - s["bar"] = true - expect (objtype (s)).to_be "Set" - - it is destructive: - s["bar"] = true - expect (s).to_have_member "bar" - - it ignores insertion of existing members: - s["foo"] = true - expect (s).to_equal (Set {"foo"}) - - it inserts a new member into the set: - expect (s).not_to_have_member "bar" - s["bar"] = true - expect (s).to_have_member "bar" - - it works with an empty set: - s = Set {} - s.foo = true - expect (s).to_equal (s) - - -- describe intersection: - - before: - fn = set.intersection - r = Set {"foo", "bar", "baz"} - s = Set {"bar", "baz", "quux"} - - - context when called as a Set module function: - - it returns a set object: - expect (objtype (fn (r, s))).to_be "Set" - - it is non-destructive: - fn (r, s) - expect (r).to_equal (Set {"foo", "bar", "baz"}) - expect (s).to_equal (Set {"bar", "baz", "quux"}) - - it returns a set containing members common to both arguments: - expect (fn (r, s)). - to_equal (Set {"bar", "baz"}) - - context when called as a set metamethod: - - it returns a set object: - q = r * s - expect (objtype (q)).to_be "Set" - - it is non-destructive: - q = r * s - expect (r).to_equal (Set {"foo", "bar", "baz"}) - expect (s).to_equal (Set {"bar", "baz", "quux"}) - - it returns a set containing members common to both arguments: - expect (r * s).to_equal (Set {"bar", "baz"}) - - -- describe member: - - before: - fn = set.member - s = Set {"foo", "bar"} - - - context when called as a Set module function: - - it succeeds when set contains the given member: - expect (fn (s, "foo")).to_be (true) - - it fails when set does not contain the given member: - expect (fn (s, "baz")).not_to_be (true) - - it works with the empty set: - s = Set {} - expect (fn (s, "foo")).not_to_be (true) - - context when called as a set metamethod: - - it succeeds when set contains the given member: - expect (s["foo"]).to_be (true) - - it fails when set does not contain the given member: - expect (s["baz"]).not_to_be (true) - - it works with the empty set: - s = Set {} - expect (s["foo"]).not_to_be (true) - - -- describe proper_subset: - - before: - fn = set.proper_subset - r = Set {"foo", "bar", "baz"} - s = Set {"bar", "baz"} - - - context when called as a Set module function: - - it succeeds when set contains all elements of another: - expect (fn (s, r)).to_be (true) - - it fails when two sets are equal: - r = s {} - expect (fn (s, r)).to_be (false) - - it fails when set does not contain all elements of another: - s = s + Set {"quux"} - expect (fn (r, s)).to_be (false) - - context when called as a set metamethod: - - it succeeds when set contains all elements of another: - expect (s < r).to_be (true) - - it fails when two sets are equal: - r = s {} - expect (s < r).to_be (false) - - it fails when set does not contain all elements of another: - s = s + Set {"quux"} - expect (r < s).to_be (false) - - -- describe subset: - - before: - fn = set.subset - r = Set {"foo", "bar", "baz"} - s = Set {"bar", "baz"} - - - context when called as a Set module function: - - it succeeds when set contains all elements of another: - expect (fn (s, r)).to_be (true) - - it succeeds when two sets are equal: - r = s {} - expect (fn (s, r)).to_be (true) - - it fails when set does not contain all elements of another: - s = s + Set {"quux"} - expect (fn (r, s)).to_be (false) - - context when called as a set metamethod: - - it succeeds when set contains all elements of another: - expect (s <= r).to_be (true) - - it succeeds when two sets are equal: - r = s {} - expect (s <= r).to_be (true) - - it fails when set does not contain all elements of another: - s = s + Set {"quux"} - expect (r <= s).to_be (false) - - -- describe symmetric_difference: - - before: - fn = set.symmetric_difference - r = Set {"foo", "bar", "baz"} - s = Set {"bar", "baz", "quux"} - - - context when called as a Set module function: - - it returns a set object: - expect (objtype (fn (r, s))). - to_be "Set" - - it is non-destructive: - fn (r, s) - expect (r).to_equal (Set {"foo", "bar", "baz"}) - expect (s).to_equal (Set {"bar", "baz", "quux"}) - - it returns a set containing members in only one argument set: - expect (fn (r, s)). - to_equal (Set {"foo", "quux"}) - - context when called as a set metamethod: - - it returns a set object: - expect (objtype (r / s)).to_be "Set" - - it is non-destructive: - q = r / s - expect (r).to_equal (Set {"foo", "bar", "baz"}) - expect (s).to_equal (Set {"bar", "baz", "quux"}) - - it returns a set containing members in only one argument set: - expect (r / s).to_equal (Set {"foo", "quux"}) - - -- describe union: - - before: - fn = set.union - r = Set {"foo", "bar", "baz"} - s = Set {"bar", "baz", "quux"} - - - context when called as a Set module function: - - it returns a set object: - expect (objtype (fn (r, s))).to_be "Set" - - it is non-destructive: - fn (r, s) - expect (r).to_equal (Set {"foo", "bar", "baz"}) - expect (s).to_equal (Set {"bar", "baz", "quux"}) - - it returns a set containing members in only one argument set: - expect (fn (r, s)). - to_equal (Set {"foo", "bar", "baz", "quux"}) - - context when called as a set metamethod: - - it returns a set object: - expect (objtype (r + s)).to_be "Set" - - it is non-destructive: - q = r + s - expect (r).to_equal (Set {"foo", "bar", "baz"}) - expect (s).to_equal (Set {"bar", "baz", "quux"}) - - it returns a set containing members in only one argument set: - expect (r + s).to_equal (Set {"foo", "bar", "baz", "quux"}) - - -- describe __tostring: - - before: - s = Set {"foo", "bar", "baz"} - - - it returns a string: - expect (type (tostring (s))).to_be "string" - - it shows the type name: - expect (tostring (s)).to_contain "Set" - - it contains the ordered set elements: - expect (tostring (s)).to_contain "bar, baz, foo" diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 841b52d..d9037fb 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -45,11 +45,10 @@ specify std: expect (type (v)).not_to_be "table" end - it loads submodules on demand: - lazy = M.set - expect (lazy).to_be (require "std.set") + lazy = M.math + expect (lazy).to_be (require "std.math") - it loads submodule functions on demand: - expect (M.object.type (M.set {"Lazy"})). - to_be "Set" + expect (M.math.round (3.141592)).to_be (3) - describe assert: - before: @@ -144,21 +143,6 @@ specify std: expect (f (t, "_functor")).to_be (functor) expect (f (t, "_functor")()).to_be "called" - - context with an object: - - before: - Object = require "std.object" - objmethod = function () end - obj = Object { - _type = "DerivedObject", - _method = objmethod, - } - - it returns nil for missing metamethods: - expect (f (obj, "not a metamethod on obj")).to_be (nil) - - it returns nil for non-function metatable entries: - expect (f (obj, "_type")).to_be (nil) - - it returns a method from the metatable: - expect (f (obj, "_method")).to_be (objmethod) - - describe ielems: - before: diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml deleted file mode 100644 index 6c07d12..0000000 --- a/specs/strbuf_spec.yaml +++ /dev/null @@ -1,100 +0,0 @@ -before: - this_module = "std.strbuf" - - StrBuf = require (this_module).prototype - b = StrBuf {"foo", "bar"} - - -specify std.strbuf: -- describe require: - - it does not perturb the global namespace: - expect (show_apis {added_to="_G", by="std.strbuf"}). - to_equal {} - - -- describe construction: - - context from StrBuf clone method: - - it constructs a new strbuf: - b = StrBuf:clone {} - expect (b).not_to_be (StrBuf) - expect (objtype (b)).to_be "StrBuf" - - it reuses the StrBuf metatable: - a, b = StrBuf:clone {"a"}, StrBuf:clone {"b"} - expect (getmetatable (a)).to_be (getmetatable (b)) - - it initialises strbuf with constructor parameters: - a = StrBuf:clone {"foo", "bar"} - expect (a).to_equal (b) - - it serves as a prototype for new instances: - obj = b:clone {} - expect (objtype (obj)).to_be "StrBuf" - expect (obj).to_equal (b) - expect (getmetatable (obj)).to_be (getmetatable (b)) - - # StrBuf {args} is just syntactic sugar for StrBuf:clone {args} - - context from StrBuf object prototype: - - it constructs a new strbuf: - b = StrBuf {} - expect (b).not_to_be (StrBuf) - expect (objtype (b)).to_be "StrBuf" - - it reuses the StrBuf metatable: - a, b = StrBuf {"a"}, StrBuf {"b"} - expect (getmetatable (a)).to_be (getmetatable (b)) - - it initialises strbuf with constructor parameters: - a = StrBuf:clone {"foo", "bar"} - expect (a).to_equal (b) - - it serves as a prototype for new instances: - obj = b {} - expect (objtype (obj)).to_be "StrBuf" - expect (obj).to_equal (b) - expect (getmetatable (obj)).to_be (getmetatable (b)) - - -- describe tostring: - - it returns buffered string: - expect (tostring (b)).to_be "foobar" - - -- describe concat: - - before: - a = StrBuf {"foo", "bar"} - b = StrBuf {"baz", "quux"} - - - context as a module function: - - it appends a string: - a = StrBuf.concat (a, "baz") - expect (objtype (a)).to_be "StrBuf" - expect (tostring (a)).to_be "foobarbaz" - - it appends a StrBuf: - a = StrBuf.concat (a, b) - expect (objtype (a)).to_be "StrBuf" - expect (tostring (a)).to_be "foobarbazquux" - - context as an object method: - - it appends a string: - a = a:concat "baz" - expect (objtype (a)).to_be "StrBuf" - expect (tostring(a)).to_be "foobarbaz" - - it appends a StrBuf: - a = a:concat (b) - expect (objtype (a)).to_be "StrBuf" - expect (tostring (a)).to_be "foobarbazquux" - - context as a metamethod: - - it appends a string: - a = a .. "baz" - expect (objtype (a)).to_be "StrBuf" - expect (tostring (a)).to_be "foobarbaz" - - it appends a StrBuf: - a = a .. b - expect (objtype (a)).to_be "StrBuf" - expect (tostring (a)).to_be "foobarbazquux" - - it stringifies lazily: - a = StrBuf {1} - b = StrBuf {a, "five"} - a = a:concat (2) - expect (tostring (b)).to_be "12five" - b = StrBuf {tostring (a), "five"} - a = a:concat (3) - expect (tostring (b)).to_be "12five" - - it can be non-destructive: - a = StrBuf {1} - b = a {} .. 2 - expect (tostring (a)).to_be "1" diff --git a/specs/tree_spec.yaml b/specs/tree_spec.yaml deleted file mode 100644 index 71e833f..0000000 --- a/specs/tree_spec.yaml +++ /dev/null @@ -1,422 +0,0 @@ -before: | - global_table = "_G" - this_module = "std.tree" - - tree = require "std.tree" - Tree = tree.prototype - -specify std.tree: -- before: - t = {foo="foo", fnord={branch={bar="bar", baz="baz"}}, quux="quux"} - tr = Tree (t) - -- 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 {} - - - context via the std module: - - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). - to_equal {} - -- describe construction: - - it constructs a new tree: - tr = Tree {} - expect (tr).not_to_be (Tree) - expect (objtype (tr)).to_be "Tree" - - it turns a table argument into a tree: - expect (objtype (Tree (t))).to_be "Tree" - - it does not turn table argument values into sub-Trees: - expect (objtype (tr["fnord"])).to_be "table" - - it understands branched nodes: - expect (tr).to_equal (Tree (t)) - expect (tr[{"fnord"}]).to_equal (t.fnord) - expect (tr[{"fnord", "branch", "bar"}]).to_equal (t.fnord.branch.bar) - - it serves as a prototype for new instances: - obj = tr {} - expect (objtype (obj)).to_be "Tree" - expect (obj).to_equal (tr) - expect (getmetatable (obj)).to_be (getmetatable (tr)) - - -- describe clone: - - before: - subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} } - f = tree.clone - - 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 makes a deep copy: - expect (f (subject).k1).not_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) - - it diagnoses non-table arguments: - if have_typecheck then - expect (f ()).to_raise ("table expected") - expect (f "foo").to_raise ("table expected") - end - - -- describe ileaves: - - before: - f = tree.ileaves - l = {} - - it iterates over array part of a table argument: - for v in f {"first", "second", "3rd"} do l[1+#l]=v end - expect (l).to_equal {"first", "second", "3rd"} - - it iterates over array parts of nested table argument: - for v in f {{"one", {"two"}, {{"three"}, "four"}}, "five"} do - l[1+#l]=v - end - expect (l).to_equal {"one", "two", "three", "four", "five"} - - it skips hash part of a table argument: - for v in f {"first", "second"; third = "2rd"} do l[1+#l]=v end - expect (l).to_equal {"first", "second"} - - it skips hash parts of nested table argument: - for v in f {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} do - l[1+#l]=v - end - expect (l).to_equal {"one", "three", "five"} - - it works on trees too: - for v in f (Tree {Tree {"one", - Tree {two=2}, - Tree {Tree {"three"}, four=4} - }, - foo="bar", "five"}) - do - l[1+#l]=v - end - expect (l).to_equal {"one", "three", "five"} - - it diagnoses non-table arguments: - if have_typecheck then - expect (f ()).to_raise ("table expected") - expect (f "string").to_raise ("table expected") - end - - -- describe inodes: - - before: | - f = tree.inodes - local tostring = (require "std.string").tostring - - function traverse (subject) - l = {} - for ty, p, n in f (subject) do - l[1+#l]={ty, tree.clone (p), n} - end - return l - end - - it iterates over array part of a table argument: | - subject = {"first", "second", "3rd"} - expect (traverse (subject)). - to_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {3}, subject[3]}, -- 3rd, - {"join", {}, subject}} -- } - - it iterates over array parts of nested table argument: | - subject = {{"one", {"two"}, {{"three"}, "four"}}, "five"} - expect (traverse (subject)). - to_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,1}, subject[1][2][1]}, -- two, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,2}, subject[1][3][2]}, -- four, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"join", {}, subject}} -- } - - it skips hash part of a table argument: | - subject = {"first", "second"; third = "3rd"} - expect (traverse (subject)). - to_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"join", {}, subject}} -- } - - it skips hash parts of nested table argument: | - subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} - expect (traverse (subject)). - to_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"join", {}, subject}} -- } - - it works on trees too: | - subject = Tree {Tree {"one", - Tree {two=2}, - Tree {Tree {"three"}, four=4}}, - foo="bar", - "five"} - expect (traverse (subject)). - to_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"join", {}, subject}} -- } - - it diagnoses non-table arguments: - if have_typecheck then - expect (f ()).to_raise ("table expected") - expect (f "string").to_raise ("table expected") - end - - -- describe leaves: - - before: - f = tree.leaves - l = {} - - it iterates over elements of a table argument: - for v in f {"first", "second", "3rd"} do l[1+#l]=v end - expect (l).to_equal {"first", "second", "3rd"} - - it iterates over elements of a nested table argument: - for v in f {{"one", {"two"}, {{"three"}, "four"}}, "five"} do - l[1+#l]=v - end - expect (l).to_equal {"one", "two", "three", "four", "five"} - - it includes the hash part of a table argument: - for v in f {"first", "second"; third = "3rd"} do l[1+#l]=v end - expect (l).to_equal {"first", "second", "3rd"} - - it includes hash parts of a nested table argument: - for v in f {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} do - l[1+#l]=v - end - expect (l).to_contain. - a_permutation_of {"one", 2, "three", 4, "bar", "five"} - - it works on trees too: - for v in f (Tree {Tree {"one", - Tree {two=2}, - Tree {Tree {"three"}, four=4} - }, - foo="bar", "five"}) - do - l[1+#l]=v - end - expect (l).to_contain. - a_permutation_of {"one", 2, "three", 4, "bar", "five"} - - it diagnoses non-table arguments: - if have_typecheck then - expect (f ()).to_raise ("table expected") - expect (f "string").to_raise ("table expected") - end - - -- describe merge: - - before: | - f = tree.merge - - -- Additional merge keys which are moderately unusual - t1 = Tree { k1 = "v1", k2 = "if", k3 = Tree {"?"} } - t2 = Tree { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = "v2" } - - target = tree.clone (t1) - for ty, p, n in tree.nodes (t2) do - if ty == "leaf" then target[p] = n end - 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, Tree {})).to_be (t1) - - it copies t2 when t1 is empty: | - expect (f (Tree {}, 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 = tree.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 diagnoses non-table arguments: - if have_typecheck then - expect (f (nil, {})).to_raise ("table expected") - expect (f ({}, nil)).to_raise ("table expected") - end - - -- describe nodes: - - before: - f = tree.nodes - - function traverse (subject) - l = {} - for ty, p, n in f (subject) do l[1+#l]={ty, tree.clone (p), n} end - return l - end - - it iterates over the elements of a table argument: | - subject = {"first", "second", "3rd"} - expect (traverse (subject)). - to_equal {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {3}, subject[3]}, -- 3rd, - {"join", {}, subject}} -- } - - it iterates over the elements of nested a table argument: | - subject = {{"one", {"two"}, {{"three"}, "four"}}, "five"} - expect (traverse (subject)). - to_equal {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,1}, subject[1][2][1]}, -- two, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,2}, subject[1][3][2]}, -- four, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"join", {}, subject}} -- } - - it includes the hash part of a table argument: | - -- like `pairs`, `nodes` can visit elements in any order, so we cannot - -- guarantee the array part is always visited before the hash part, or - -- even that the array elements are visited in order! - subject = {"first", "second"; third = "3rd"} - expect (traverse (subject)).to_contain. - a_permutation_of {{"branch", {}, subject}, -- { - {"leaf", {1}, subject[1]}, -- first, - {"leaf", {2}, subject[2]}, -- second, - {"leaf", {"third"}, subject["third"]}, -- 3rd - {"join", {}, subject}} -- } - - it includes hash parts of a nested table argument: | - -- like `pairs`, `nodes` can visit elements in any order, so we cannot - -- guarantee the array part is always visited before the hash part, or - -- even that the array elements are visited in order! - subject = {{"one", {two=2}, {{"three"}, four=4}}, foo="bar", "five"} - expect (traverse (subject)).to_contain. - a_permutation_of {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"leaf", {"foo"}, subject["foo"]}, -- bar, - {"join", {}, subject}} -- } - - it works on trees too: | - -- like `pairs`, `nodes` can visit elements in any order, so we cannot - -- guarantee the array part is always visited before the hash part, or - -- even that the array elements are visited in order! - subject = Tree {Tree {"one", - Tree {two=2}, - Tree {Tree {"three"}, four=4}}, - foo="bar", - "five"} - expect (traverse (subject)).to_contain. - a_permutation_of {{"branch", {}, subject}, -- { - {"branch", {1}, subject[1]}, -- { - {"leaf", {1,1}, subject[1][1]}, -- one, - {"branch", {1,2}, subject[1][2]}, -- { - {"leaf", {1,2,"two"}, subject[1][2]["two"]}, -- 2, - {"join", {1,2}, subject[1][2]}, -- }, - {"branch", {1,3}, subject[1][3]}, -- { - {"branch", {1,3,1}, subject[1][3][1]}, -- { - {"leaf", {1,3,1,1}, subject[1][3][1][1]}, -- three, - {"join", {1,3,1}, subject[1][3][1]}, -- }, - {"leaf", {1,3,"four"}, subject[1][3]["four"]}, -- 4, - {"join", {1,3}, subject[1][3]}, -- }, - {"join", {1}, subject[1]}, -- }, - {"leaf", {2}, subject[2]}, -- five, - {"leaf", {"foo"}, subject["foo"]}, -- bar, - {"join", {}, subject}} -- } - - it generates path key-lists that are valid __index arguments: | - subject = Tree {"first", Tree {"second"}, "3rd"} - expect (traverse (subject)). - to_equal {{"branch", {}, subject[{}]}, -- { - {"leaf", {1}, subject[{1}]}, -- first, - {"branch", {2}, subject[{2}]}, -- { - {"leaf", {2,1}, subject[{2,1}]}, -- second - {"join", {2}, subject[{2}]}, -- } - {"leaf", {3}, subject[{3}]}, -- 3rd, - {"join", {}, subject[{}]}} -- } - - it diagnoses non-table arguments: - if have_typecheck then - expect (f ()).to_raise ("table expected") - expect (f "string").to_raise ("table expected") - end - - -- describe __index: - - it returns nil for a missing key: - expect (tr["no such key"]).to_be (nil) - - it returns nil for missing single element key lists: - expect (tr[{"no such key"}]).to_be (nil) - - it returns nil for missing multi-element key lists: - expect (tr[{"fnord", "foo"}]).to_be (nil) - expect (tr[{"no", "such", "key"}]).to_be (nil) - - it returns a value for the given key: - expect (tr["foo"]).to_be "foo" - expect (tr["quux"]).to_be "quux" - - it returns tree root for empty key list: - expect (tr[{}]).to_be (tr) - - it returns values for single element key lists: - expect (tr[{"foo"}]).to_be "foo" - expect (tr[{"quux"}]).to_be "quux" - - it returns values for multi-element key lists: - expect (tr[{"fnord", "branch", "bar"}]).to_be "bar" - expect (tr[{"fnord", "branch", "baz"}]).to_be "baz" - - -- describe __newindex: - - before: - tr = Tree {} - - it stores values for simple keys: - tr["foo"] = "foo" - expect (tr).to_equal (Tree {foo="foo"}) - - it stores values for single element key lists: - tr[{"foo"}] = "foo" - expect (tr).to_equal (Tree {foo="foo"}) - - it stores values for multi-element key lists: - tr[{"foo", "bar"}] = "baz" - expect (tr).to_equal (Tree {foo=Tree {bar="baz"}}) - - it separates branches for diverging key lists: - tr[{"foo", "branch", "bar"}] = "leaf1" - tr[{"foo", "branch", "baz"}] = "leaf2" - expect (tr).to_equal (Tree {foo=Tree {branch=Tree {bar="leaf1", baz="leaf2"}}}) - - -- describe __tostring: - - it returns a string: - expect (objtype (tostring (tr))).to_be "string" - - it shows the type name: - expect (tostring (tr)).to_contain "Tree" - - it shows the contents in order: | - tr = Tree {foo = "foo", - fnord = Tree {branch = Tree {bar="bar", baz="baz"}}, - quux = "quux"} - expect (tostring (tr)). - to_contain 'fnord=Tree {branch=Tree {bar=bar, baz=baz}}, foo=foo, quux=quux' diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index 10d6c8a..0098e82 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -5,7 +5,7 @@ description = { summary = "General Lua Libraries", detailed = [[ stdlib is a library of modules for common programming tasks, - including list and table operations, objects, and pretty-printing. + including list and table operations, and pretty-printing. ]], homepage = "http://lua-stdlib.github.io/lua-stdlib", license = "MIT/X11", @@ -24,18 +24,12 @@ build = { modules = { std = "lib/std/init.lua", ["std.base"] = "lib/std/_base.lua", - ["std.container"] = "lib/std/container.lua", ["std.debug"] = "lib/std/debug.lua", ["std.debug_init"] = "lib/std/debug_init/init.lua", ["std.io"] = "lib/std/io.lua", - ["std.list"] = "lib/std/list.lua", ["std.math"] = "lib/std/math.lua", - ["std.object"] = "lib/std/object.lua", ["std.package"] = "lib/std/package.lua", - ["std.set"] = "lib/std/set.lua", - ["std.strbuf"] = "lib/std/strbuf.lua", ["std.string"] = "lib/std/string.lua", ["std.table"] = "lib/std/table.lua", - ["std.tree"] = "lib/std/tree.lua", }, } From e05c1b3d8ec9e7a185fec563f1e7c59c7e3be3fa Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 5 Feb 2016 22:43:23 +0000 Subject: [PATCH 682/703] refactor: move non-shared functions out of std._base. * lib/std/_base.lua (Module, last, mapfields): Remove unused functions. (eval, ielems, loadstring, ripairs): Move from here... * lib/std/init.lua (eval, ielems, loadstring, ripairs): ...to here. Signed-off-by: Gary V. Vaughan --- lib/std/_base.lua | 80 ----------------------------------------------- lib/std/init.lua | 35 +++++++++++++++++++-- 2 files changed, 32 insertions(+), 83 deletions(-) diff --git a/lib/std/_base.lua b/lib/std/_base.lua index 2a49cc3..0f99cd8 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -26,7 +26,6 @@ local dirsep = string.match (package.config, "^(%S+)\n") local error = error local getfenv = getfenv or false local getmetatable = getmetatable -local loadstring = loadstring or load local next = next local pairs = pairs local rawget = rawget @@ -209,11 +208,6 @@ local function escape_pattern (s) end -local function eval (s) - return loadstring ("return " .. s)() -end - - local function _getfenv (fn) fn = fn or 1 @@ -245,17 +239,6 @@ local function _getfenv (fn) 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 invert (t) local i = {} for k, v in pairs (t) do @@ -275,9 +258,6 @@ local function keysort (a, b) end -local function last (t) return t[len (t)] end - - local function leaves (it, tr) local function visit (n) if type (n) == "table" then @@ -292,53 +272,12 @@ local function leaves (it, tr) end -local function mapfields (obj, src, map) - local mt = getmetatable (obj) or {} - - -- Map key pairs. - -- Copy all pairs when `map == nil`, but discard unmapped src keys - -- when map is provided (i.e. if `map == {}`, copy nothing). - if map == nil or next (map) then - map = map or {} - local k, v = next (src) - while k do - local key, dst = map[k] or k, obj - local kind = type (key) - if kind == "string" and key:sub (1, 1) == "_" then - mt[key] = v - elseif next (map) and kind == "number" and len (dst) + 1 < key then - -- When map is given, but has fewer entries than src, stop copying - -- fields when map is exhausted. - break - else - dst[key] = v - end - k, v = next (src, k) - end - end - - -- Only set non-empty metatable. - if next (mt) then - setmetatable (obj, mt) - end - return obj -end - - local function merge (dest, src) for k, v in pairs (src) do dest[k] = dest[k] or v end return dest end -local function Module (t) - return setmetatable (t, { - _type = "Module", - __call = function (self, ...) return self.prototype (...) end, - }) -end - - local pack = table_pack or function (...) return {n = select ("#", ...), ...} end @@ -412,21 +351,6 @@ local function sortkeys (t) 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 _setfenv (fn, env) -- Unwrap functable: if type (fn) == "table" then @@ -535,18 +459,14 @@ return { strict = strict, typecheck = typecheck, - eval = eval, getmetamethod = getmetamethod, - ielems = ielems, ipairs = ipairs, pairs = pairs, - ripairs = ripairs, tostring = function (x) return render (x, tostring_vtable) end, base = { copy = copy, - last = last, merge = merge, sortkeys = sortkeys, toqstring = toqstring, diff --git a/lib/std/init.lua b/lib/std/init.lua index 92712ba..8204f3c 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -16,6 +16,7 @@ local error = error local ipairs = ipairs +local loadstring = loadstring or load local pairs = pairs local pcall = pcall local rawset = rawset @@ -36,12 +37,9 @@ local _tostring = _.tostring local argscheck = _.typecheck and _.typecheck.argscheck local compare = _.list.compare local copy = _.base.copy -local eval = _.eval local getmetamethod = _.getmetamethod -local ielems = _.ielems local maxn = _.table.maxn local merge = _.base.merge -local ripairs = _.ripairs local split = _.string.split local _ENV = _.strict and _.strict {} or {} @@ -75,6 +73,22 @@ local function elems (t) end +local function eval (s) + return loadstring ("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) @@ -86,6 +100,21 @@ local function npairs (t) 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 From 686562899542d7ffc1e1cf1c650b155c6a78aa69 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 5 Feb 2016 23:05:31 +0000 Subject: [PATCH 683/703] README.md: Fix a typo in the install instructions. * README.md: Copy the _std_ folder! Signed-off-by: Gary V. Vaughan --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2242e8..a3480a6 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ report for example): 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 `functional` +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" From a981040787edb0ac3df36d5800ef0523ef18f9d5 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Fri, 5 Feb 2016 23:10:00 +0000 Subject: [PATCH 684/703] rockspec: install _base module with the correct name. * stdlib-git-1.rockspec (build.modules): lib/std/_base.lua file should be installed as `std._base`, because that is how the other modules require it. Signed-off-by: Gary V. Vaughan --- stdlib-git-1.rockspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index 0098e82..63bd1b5 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -23,7 +23,7 @@ build = { type = "builtin", modules = { std = "lib/std/init.lua", - ["std.base"] = "lib/std/_base.lua", + ["std._base"] = "lib/std/_base.lua", ["std.debug"] = "lib/std/debug.lua", ["std.debug_init"] = "lib/std/debug_init/init.lua", ["std.io"] = "lib/std/io.lua", From c529fb910ea83f094b3ea1cc14169cc1e65b30fd Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Sat, 4 Jun 2016 16:41:24 +0100 Subject: [PATCH 685/703] std/table.lua: fix a comment typo --- lib/std/table.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/table.lua b/lib/std/table.lua index f3d9679..25563d3 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -247,7 +247,7 @@ M = { -- @function sort -- @tparam table t unsorted table -- @tparam[opt=std.operator.lt] comparator c ordering function callback - -- @return *t* with keys sorted accordind to *c* + -- @return *t* with keys sorted according to *c* -- @usage table.concat (sort (object)) sort = X ("sort (table, ?function)", sort), From 6196b9102e6c54175ca8c2d2c1f949fbb0397e15 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 13 Aug 2017 14:23:56 -0700 Subject: [PATCH 686/703] math: simplify floor implementation slightly. * specs/math_spec.yaml (round): Add examples of rounding negative arguments. * lib/std/math.lua (floor): Simplify implementation slightlyp Signed-off-by: Gary V. Vaughan --- lib/std/math.lua | 9 ++++----- specs/math_spec.yaml | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/std/math.lua b/lib/std/math.lua index d90c030..65e3b94 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -36,12 +36,11 @@ local M local function floor (n, p) - if p and p ~= 0 then - local e = 10 ^ p - return math_floor (n * e) / e - else + if (p or 0) == 0 then return math_floor (n) end + local e = 10 ^ p + return math_floor (n * e) / e end @@ -74,7 +73,7 @@ M = { -- @usage tenths = floor (magnitude, 1) floor = X ("floor (number, ?int)", floor), - --- Round a number to a given number of decimal places + --- 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 diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index a90d65f..fada420 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -79,3 +79,18 @@ specify std.math: 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) From 90473d0727a14e7d9e29979768dce89e15ef515f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 Sep 2017 20:53:36 -0700 Subject: [PATCH 687/703] maint: regenerate version.lua with the proper $VERSION. * .gitignore: Add lib/std/version.lua. * Makefile (VERSION): Keep the current version number here. (lib/std/version.lua): Regenerate on demand. * lib/std/version.lua: Remove generated file from version control. * lib/std/init.lua (vconvert): Be more robust to non-string version numbers. (vcompare): Use vconvert. * specs/std_spec.yaml (require): A more robust test for _VERSION fallback. Signed-off-by: Gary V. Vaughan --- .gitignore | 1 + Makefile | 12 ++++++++++++ lib/std/init.lua | 16 ++++++++++++++-- lib/std/version.lua | 1 - specs/std_spec.yaml | 11 ++++++----- 5 files changed, 33 insertions(+), 8 deletions(-) delete mode 100644 lib/std/version.lua diff --git a/.gitignore b/.gitignore index 847ee2b..c731682 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ /Makefile /doc !/doc/config.ld.in +/lib/std/version.lua /stdlib-*.rockspec !/stdlib-git-*.rockspec diff --git a/Makefile b/Makefile index cac5913..7f4a5f5 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ MKDIR = mkdir -p SED = sed SPECL = specl +VERSION = git + luadir = lib/std SOURCES = \ $(luadir)/_base.lua \ @@ -21,6 +23,14 @@ SOURCES = \ all: doc +$(luadir)/version.lua: .FORCE + @echo 'return "General Lua libraries / $(VERSION)"' > '$@T'; \ + if cmp -s '$@' '$@T'; then \ + rm -f '$@T'; \ + else \ + mv '$@T' '$@'; \ + fi + doc: doc/config.ld $(SOURCES) $(LDOC) -c doc/config.ld . @@ -33,3 +43,5 @@ CHECK_ENV = LUA=$(LUA) check: LUA=$(LUA) $(SPECL) $(SPECL_OPTS) specs/*_spec.yaml + +.FORCE: diff --git a/lib/std/init.lua b/lib/std/init.lua index 8204f3c..141e736 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -128,8 +128,20 @@ local function rnpairs (t) 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 (split (a, "%."), split (b, "%.")) + return compare (vconvert (a), vconvert (b)) end @@ -138,7 +150,7 @@ local function _require (module, min, too_big, pattern) local s, m = "", require (module) if type (m) == "table" then s = tostring (m.version or m._VERSION or "") end - local v = string_match (s, pattern) + local v = string_match (s, pattern) or 0 if min then _assert (vcompare (v, min) >= 0, "require '" .. module .. "' with at least version " .. min .. ", but found version " .. v) diff --git a/lib/std/version.lua b/lib/std/version.lua deleted file mode 100644 index e9a70bd..0000000 --- a/lib/std/version.lua +++ /dev/null @@ -1 +0,0 @@ -return "General Lua libraries / 42.0.0" diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index d9037fb..654c2d0 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -276,14 +276,15 @@ specify std: - it returns the module table: expect (f ("std", "0", "9999")).to_be (require "std") - it places no upper bound by default: - expect (f ("std", "41")).to_be (require "std") + 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: - std = require "std" - M._VERSION, M.version = M.version, nil - expect (f ("std", "41", "9999")).to_be (require "std") - M._VERSION, M.version = nil, M._VERSION + 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" From 7428ebf15077763d65c3d0bd661cf692f0f16419 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 Sep 2017 20:13:24 -0700 Subject: [PATCH 688/703] maint: modernize formatting - 3 space indents. * README.md: Document 3 space indent style. * lib/std/_base.lua, lib/std/debug.lua, lib/std/debug_init/init.lua, lib/std/init.lua, lib/std/io.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua, specs/spec_helper.lua: Use 3 space indentation. * specs/debug_spec.yaml, specs/io_spec.yaml, specs/math_spec.yaml, specs/package_spec.yaml, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Use 3 space indentation for Lua blocks. Signed-off-by: Gary V. Vaughan --- README.md | 3 +- lib/std/_base.lua | 628 ++++++++++++++--------------- lib/std/debug.lua | 217 ++++++----- lib/std/debug_init/init.lua | 42 +- lib/std/init.lua | 594 ++++++++++++++-------------- lib/std/io.lua | 480 ++++++++++++----------- lib/std/math.lua | 70 ++-- lib/std/package.lua | 323 +++++++-------- lib/std/string.lua | 759 ++++++++++++++++++------------------ lib/std/table.lua | 639 +++++++++++++++--------------- specs/debug_spec.yaml | 222 +++++------ specs/io_spec.yaml | 136 +++---- specs/math_spec.yaml | 10 +- specs/package_spec.yaml | 60 +-- specs/spec_helper.lua | 424 ++++++++++---------- specs/std_spec.yaml | 98 ++--- specs/string_spec.yaml | 132 +++---- specs/table_spec.yaml | 92 ++--- 18 files changed, 2485 insertions(+), 2444 deletions(-) diff --git a/README.md b/README.md index a3480a6..1f020ac 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ points when proposing changes: 0. Follow existing code. There are a lot of useful patterns and avoided traps there. -1. 2-character indentation using SPACES in Lua sources. +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. [issues]: http://github.com/lua-stdlib/lua-stdlib/issues diff --git a/lib/std/_base.lua b/lib/std/_base.lua index 0f99cd8..252f277 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -21,37 +21,37 @@ ]] -local _ENV = _ENV -local dirsep = string.match (package.config, "^(%S+)\n") -local error = error -local getfenv = getfenv or false -local getmetatable = getmetatable -local next = next -local pairs = pairs -local rawget = rawget -local select = select -local setmetatable = setmetatable -local tonumber = tonumber -local tostring = tostring -local type = type - -local coroutine_wrap = coroutine.wrap -local coroutine_yield = coroutine.yield -local debug_getinfo = debug.getinfo -local debug_getupvalue = debug.getupvalue -local debug_setfenv = debug.setfenv -local debug_setupvalue = debug.setupvalue -local debug_upvaluejoin = debug.upvaluejoin -local math_huge = math.huge -local math_min = math.min -local string_find = string.find -local string_format = string.format -local table_concat = table.concat -local table_insert = table.insert -local table_maxn = table.maxn -local table_pack = table.pack -local table_sort = table.sort -local table_unpack = table.unpack or unpack +local _ENV = _ENV +local dirsep = string.match (package.config, "^(%S+)\n") +local error = error +local getfenv = getfenv or false +local getmetatable = getmetatable +local next = next +local pairs = pairs +local rawget = rawget +local select = select +local setmetatable = setmetatable +local tonumber = tonumber +local tostring = tostring +local type = type + +local coroutine_wrap = coroutine.wrap +local coroutine_yield = coroutine.yield +local debug_getinfo = debug.getinfo +local debug_getupvalue = debug.getupvalue +local debug_setfenv = debug.setfenv +local debug_setupvalue = debug.setupvalue +local debug_upvaluejoin = debug.upvaluejoin +local math_huge = math.huge +local math_min = math.min +local string_find = string.find +local string_format = string.format +local table_concat = table.concat +local table_insert = table.insert +local table_maxn = table.maxn +local table_pack = table.pack +local table_sort = table.sort +local table_unpack = table.unpack or unpack @@ -60,35 +60,35 @@ local table_unpack = table.unpack or unpack --[[ ================== ]]-- -local _DEBUG = require "std.debug_init"._DEBUG +local _DEBUG = require "std.debug_init"._DEBUG local strict, typecheck do - local ok - - -- Unless strict was disabled (`_DEBUG = false`), or that module is not - -- available, check for use of undeclared variables in this module... - if _DEBUG.strict then - ok, strict = pcall (require, "strict") - if ok then - _ENV = strict {} - else - -- ...otherwise, the strict function is not available at all! - _DEBUG.strict = false - strict = false - end - end - - -- Unless strict was disabled (`_DEBUG = false`), or that module is not - -- available, check for use of undeclared variables in this module... - if _DEBUG.argcheck then - ok, typecheck = pcall (require, "typecheck") - if not ok then - -- ...otherwise, the strict function is not available at all! - _DEBUG.argcheck = false - typecheck = false - end - end + local ok + + -- Unless strict was disabled (`_DEBUG = false`), or that module is not + -- available, check for use of undeclared variables in this module... + if _DEBUG.strict then + ok, strict = pcall (require, "strict") + if ok then + _ENV = strict {} + else + -- ...otherwise, the strict function is not available at all! + _DEBUG.strict = false + strict = false + end + end + + -- Unless strict was disabled (`_DEBUG = false`), or that module is not + -- available, check for use of undeclared variables in this module... + if _DEBUG.argcheck then + ok, typecheck = pcall (require, "typecheck") + if not ok then + -- ...otherwise, the strict function is not available at all! + _DEBUG.argcheck = false + typecheck = false + end + end end @@ -109,12 +109,12 @@ local getmetamethod, len -- Iterate over keys 1..n, where n is the key before the first nil -- valued ordinal key (like Lua 5.3). local function ipairs (l) - return function (l, n) - n = n + 1 - if l[n] ~= nil then - return n, l[n] - end - end, l, 0 + return function (l, n) + n = n + 1 + if l[n] ~= nil then + return n, l[n] + end + end, l, 0 end @@ -122,16 +122,16 @@ local _pairs = pairs -- Respect __pairs metamethod, even in Lua 5.1. local function pairs (t) - return (getmetamethod (t, "__pairs") or _pairs) (t) + return (getmetamethod (t, "__pairs") or _pairs) (t) end 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 + local n = 0 + for k in pairs (t) do + if type (k) == "number" and k > n then n = k end + end + return n end @@ -142,12 +142,12 @@ end local function argerror (name, i, extramsg, level) - level = level or 1 - local s = string_format ("bad argument #%d to '%s'", i, name) - if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" - end - error (s, level + 1) + level = level or 1 + local s = string_format ("bad argument #%d to '%s'", i, name) + if extramsg ~= nil then + s = s .. " (" .. extramsg .. ")" + end + error (s, level + 1) end @@ -164,248 +164,248 @@ end -- --> stdin:1: in main chunk -- --> [C]: in ? local function callable (x) - if type (x) == "function" then return x end - return (getmetatable (x) or {}).__call + if type (x) == "function" then return x end + return (getmetatable (x) or {}).__call end local function catfile (...) - return table_concat ({...}, dirsep) + return table_concat ({...}, dirsep) end local function compare (l, m) - local lenl, lenm = len (l), len (m) - for i = 1, math_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 + local lenl, lenm = len (l), len (m) + for i = 1, math_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 li > mi then + elseif lenl > lenm then return 1 - end - end - if lenl < lenm then - return -1 - elseif lenl > lenm then - return 1 - end - return 0 + end + return 0 end local function copy (dest, src) - if src == nil then dest, src = {}, dest end - for k, v in pairs (src) do dest[k] = v end - return dest + if src == nil then dest, src = {}, dest end + for k, v in pairs (src) do dest[k] = v end + return dest end local function escape_pattern (s) - return (s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) + return (s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) end local function _getfenv (fn) - fn = fn or 1 - - -- Unwrap functable: - if type (fn) == "table" then - fn = fn.call or (getmetatable (fn) or {}).__call - end - - if getfenv then - if type (fn) == "number" then fn = fn + 1 end - - -- Stack frame count is critical here, so ensure we don't optimise one - -- away in LuaJIT... - return getfenv (fn), nil - - else - if type (fn) == "number" then - fn = debug_getinfo (fn + 1, "f").func - end - - local name, env - local up = 0 - repeat - up = up + 1 - name, env = debug_getupvalue (fn, up) - until name == '_ENV' or name == nil - return env - end + fn = fn or 1 + + -- Unwrap functable: + if type (fn) == "table" then + fn = fn.call or (getmetatable (fn) or {}).__call + end + + if getfenv then + if type (fn) == "number" then fn = fn + 1 end + + -- Stack frame count is critical here, so ensure we don't optimise one + -- away in LuaJIT... + return getfenv (fn), nil + + else + if type (fn) == "number" then + fn = debug_getinfo (fn + 1, "f").func + end + + local name, env + local up = 0 + repeat + up = up + 1 + name, env = debug_getupvalue (fn, up) + until name == '_ENV' or name == nil + return env + end end local function invert (t) - local i = {} - for k, v in pairs (t) do - i[v] = k - end - return i + local i = {} + for k, v in pairs (t) do + i[v] = k + end + return i end -- Sort numbers first then asciibetically local function keysort (a, b) - if type (a) == "number" then - return type (b) ~= "number" or a < b - else - return type (b) ~= "number" and tostring (a) < tostring (b) - end + if type (a) == "number" then + return type (b) ~= "number" or a < b + else + return type (b) ~= "number" and tostring (a) < tostring (b) + end end local function leaves (it, tr) - local function visit (n) - if type (n) == "table" then - for _, v in it (n) do - visit (v) + local function visit (n) + if type (n) == "table" then + for _, v in it (n) do + visit (v) + end + else + coroutine_yield (n) end - else - coroutine_yield (n) - end - end - return coroutine_wrap (visit), tr + end + return coroutine_wrap (visit), tr end local function merge (dest, src) - for k, v in pairs (src) do dest[k] = dest[k] or v end - return dest + for k, v in pairs (src) do dest[k] = dest[k] or v end + return dest end local pack = table_pack or function (...) - return {n = select ("#", ...), ...} + return {n = select ("#", ...), ...} end local fallbacks = { - __index = { - open = function (x) return "{" end, - close = function (x) return "}" end, - elem = tostring, - pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. "=" .. vstr end, - sep = function (x, kp, vp, kn, vn) - return kp ~= nil and kn ~= nil and "," or "" - end, - sort = function (keys) return keys end, - term = function (x) - return type (x) ~= "table" or getmetamethod (x, "__tostring") - end, - }, + __index = { + open = function (x) return "{" end, + close = function (x) return "}" end, + elem = tostring, + pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. "=" .. vstr end, + sep = function (x, kp, vp, kn, vn) + return kp ~= nil and kn ~= nil and "," or "" + end, + sort = function (keys) return keys end, + term = function (x) + return type (x) ~= "table" or getmetamethod (x, "__tostring") + end, + }, } -- Write pretty-printing based on: -- --- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators +-- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators -- --- Based on "The Design of a Pretty-printing Library in Advanced --- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925 --- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps --- Heavily modified by Simon Peyton Jones, Dec 96 +-- Based on "The Design of a Pretty-printing Library in Advanced +-- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925 +-- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps +-- Heavily modified by Simon Peyton Jones, Dec 96 local function render (x, fns, roots) - fns = setmetatable (fns or {}, fallbacks) - roots = roots or {} - - local function stop_roots (x) - return roots[x] or render (x, fns, copy (roots)) - end - - if fns.term (x) then - return fns.elem (x) - - else - local buf, keys = {fns.open (x)}, {} -- pre-buffer table open - roots[x] = fns.elem (x) -- recursion protection - - for k in pairs (x) do -- collect keys - keys[#keys + 1] = k - end - keys = fns.sort (keys) - - local pair, sep = fns.pair, fns.sep - local kp, vp -- previous key and value - for _, k in ipairs (keys) do - local v = x[k] - buf[#buf + 1] = sep (x, kp, vp, k, v) -- | buffer << separator - buf[#buf + 1] = pair (x, kp, vp, k, v, stop_roots (k), stop_roots (v)) + fns = setmetatable (fns or {}, fallbacks) + roots = roots or {} + + local function stop_roots (x) + return roots[x] or render (x, fns, copy (roots)) + end + + if fns.term (x) then + return fns.elem (x) + + else + local buf, keys = {fns.open (x)}, {} -- pre-buffer table open + roots[x] = fns.elem (x) -- recursion protection + + for k in pairs (x) do -- collect keys + keys[#keys + 1] = k + end + keys = fns.sort (keys) + + local pair, sep = fns.pair, fns.sep + local kp, vp -- previous key and value + for _, k in ipairs (keys) do + local v = x[k] + buf[#buf + 1] = sep (x, kp, vp, k, v) -- | buffer << separator + buf[#buf + 1] = pair (x, kp, vp, k, v, stop_roots (k), stop_roots (v)) -- | buffer << key/value pair - kp, vp = k, v - end - buf[#buf + 1] = sep (x, kp, vp) -- buffer << trailing separator - buf[#buf + 1] = fns.close (x) -- buffer << table close + kp, vp = k, v + end + buf[#buf + 1] = sep (x, kp, vp) -- buffer << trailing separator + buf[#buf + 1] = fns.close (x) -- buffer << table close - return table_concat (buf) -- stringify buffer - end + return table_concat (buf) -- stringify buffer + end end local function sortkeys (t) - table_sort (t, keysort) - return t + table_sort (t, keysort) + return t end local function _setfenv (fn, env) - -- Unwrap functable: - if type (fn) == "table" then - fn = fn.call or (getmetatable (fn) or {}).__call - end - - if debug_setfenv then - return debug_setfenv (fn, env) - - else - -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html - local name - local up = 0 - repeat - up = up + 1 - name = debug_getupvalue (fn, up) - until name == '_ENV' or name == nil - if name then - debug_upvaluejoin (fn, up, function () return name end, 1) - debug_setupvalue (fn, up, env) - end - - return fn - end + -- Unwrap functable: + if type (fn) == "table" then + fn = fn.call or (getmetatable (fn) or {}).__call + end + + if debug_setfenv then + return debug_setfenv (fn, env) + + else + -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html + local name + local up = 0 + repeat + up = up + 1 + name = debug_getupvalue (fn, up) + until name == '_ENV' or name == nil + if name then + debug_upvaluejoin (fn, up, function () return name end, 1) + debug_setupvalue (fn, up, env) + end + + return fn + end end local function split (s, sep) - local r, patt = {} - if sep == "" then - patt = "(.)" - table_insert (r, "") - else - patt = "(.-)" .. (sep or "%s+") - end - local b, slen = 0, len (s) - while b <= slen do - local e, n, m = string_find (s, patt, b + 1) - table_insert (r, m or s:sub (b + 1, slen)) - b = n or slen + 1 - end - return r + local r, patt = {} + if sep == "" then + patt = "(.)" + table_insert (r, "") + else + patt = "(.-)" .. (sep or "%s+") + end + local b, slen = 0, len (s) + while b <= slen do + local e, n, m = string_find (s, patt, b + 1) + table_insert (r, m or s:sub (b + 1, slen)) + b = n or slen + 1 + end + return r end local tostring_vtable = { - pair = function (x, kp, vp, k, v, kstr, vstr) - if k == 1 or type (k) == "number" and k -1 == kp then - return vstr - end - return kstr .. "=" .. vstr - end, - - -- need to sort numeric keys to be able to skip printing them. - sort = sortkeys, + pair = function (x, kp, vp, k, v, kstr, vstr) + if k == 1 or type (k) == "number" and k -1 == kp then + return vstr + end + return kstr .. "=" .. vstr + end, + + -- need to sort numeric keys to be able to skip printing them. + sort = sortkeys, } @@ -423,21 +423,21 @@ local tostring_vtable = { -- element with an immediately following nil valued element, which is -- non-deterministic for non-sequence tables. len = function (x) - local m = getmetamethod (x, "__len") - if m then return m (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 + local m = getmetamethod (x, "__len") + if m then return m (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 getmetamethod = function (x, n) - local m = (getmetatable (x) or {})[n] - if callable (m) then return m end + local m = (getmetatable (x) or {})[n] + if callable (m) then return m end end @@ -455,63 +455,63 @@ end -- public API here too, which means everything looks relatively normal -- when importing the functions into stdlib implementation modules. return { - _DEBUG = _DEBUG, - strict = strict, - typecheck = typecheck, - - getmetamethod = getmetamethod, - ipairs = ipairs, - pairs = pairs, - - tostring = function (x) return render (x, tostring_vtable) end, - - base = { - copy = copy, - merge = merge, - sortkeys = sortkeys, - toqstring = toqstring, - }, - - debug = { - argerror = argerror, - getfenv = _getfenv, - setfenv = _setfenv, - }, - - io = { - catfile = catfile, - }, - - list = { - compare = compare, - }, - - object = { - Module = Module, - mapfields = mapfields, - }, - - operator = { - len = len, - }, - - package = { - dirsep = dirsep, - }, - - string = { - escape_pattern = escape_pattern, - render = render, - split = split, - }, - - table = { - invert = invert, - maxn = maxn, - pack = pack, - }, - - tree = { - leaves = leaves, - }, + _DEBUG = _DEBUG, + strict = strict, + typecheck = typecheck, + + getmetamethod = getmetamethod, + ipairs = ipairs, + pairs = pairs, + + tostring = function (x) return render (x, tostring_vtable) end, + + base = { + copy = copy, + merge = merge, + sortkeys = sortkeys, + toqstring = toqstring, + }, + + debug = { + argerror = argerror, + getfenv = _getfenv, + setfenv = _setfenv, + }, + + io = { + catfile = catfile, + }, + + list = { + compare = compare, + }, + + object = { + Module = Module, + mapfields = mapfields, + }, + + operator = { + len = len, + }, + + package = { + dirsep = dirsep, + }, + + string = { + escape_pattern = escape_pattern, + render = render, + split = split, + }, + + table = { + invert = invert, + maxn = maxn, + pack = pack, + }, + + tree = { + leaves = leaves, + }, } diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 0ccb27a..6aee1f8 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -2,35 +2,35 @@ 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 + 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" + local debug = require "std.debug" @corelibrary std.debug ]] -local debug = debug -local setmetatable = setmetatable -local type = type +local debug = debug +local setmetatable = setmetatable +local type = type -local io_stderr = io.stderr -local math_huge = math.huge -local math_max = math.max -local table_concat = table.concat +local io_stderr = io.stderr +local math_huge = math.huge +local math_max = math.max +local table_concat = table.concat -local _ = require "std._base" +local _ = require "std._base" -local _DEBUG = _._DEBUG -local _getfenv = _.debug.getfenv -local _pairs = _.pairs -local _setfenv = _.debug.setfenv -local _tostring = _.tostring -local merge = _.base.merge +local _DEBUG = _._DEBUG +local _getfenv = _.debug.getfenv +local _pairs = _.pairs +local _setfenv = _.debug.setfenv +local _tostring = _.tostring +local merge = _.base.merge -local _ENV = _.strict and _.strict {} or {} +local _ENV = _.strict and _.strict {} or {} _ = nil @@ -50,112 +50,113 @@ _ = nil -- @tfield[opt=true] boolean argcheck honor argcheck and argscheck calls -- @tfield[opt=false] boolean call do call trace debugging -- @field[opt=nil] deprecate if `false`, deprecated APIs are defined, --- and do not issue deprecation warnings when used; if `nil` issue a --- deprecation warning each time a deprecated api is used; any other --- value causes deprecated APIs not to be defined at all +-- and do not issue deprecation warnings when used; if `nil` issue a +-- deprecation warning each time a deprecated api is used; any other +-- value causes deprecated APIs not to be defined at all -- @tfield[opt=1] int level debugging level -- @tfield[opt=true] boolean strict enforce strict variable declaration --- before use **in stdlib internals** (if `require "strict"` works) --- @usage _DEBUG = { argcheck = false, level = 9, strict = false } +-- before use **in stdlib internals** (if `require "strict"` works) +-- @usage +-- _DEBUG = { argcheck = false, level = 9, strict = false } local function say (n, ...) - local level, argt = n, {...} - if type (n) ~= "number" then - level, argt = 1, {n, ...} - end - if _DEBUG.level ~= math_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] = _tostring (v) end - io_stderr:write (table_concat (t, "\t") .. "\n") - end + local level, argt = n, {...} + if type (n) ~= "number" then + level, argt = 1, {n, ...} + end + if _DEBUG.level ~= math_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] = _tostring (v) end + io_stderr:write (table_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 = math_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 - io_stderr:write (s .. "\n") + 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 = math_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 + io_stderr:write (s .. "\n") end -- Set hooks according to _DEBUG if type (_DEBUG) == "table" and _DEBUG.call then - debug.sethook (trace, "cr") + 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 `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` - -- is a number greater than or equal to *n*. If `_DEBUG` 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_init"._DEBUG - -- _DEBUG.level = 3 - -- say (2, "_DEBUG table contents:", _DEBUG) - say = say, - - --- Trace function calls. - -- Use as debug.sethook (trace, "cr"), which is done automatically - -- when `_DEBUG.call` is set. - -- Based on test/trace-calls.lua from the Lua distribution. - -- @function trace - -- @string event event causing the call - -- @usage - -- _DEBUG = { call = true } - -- local debug = require "std.debug" - trace = trace, + --- 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 `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` + -- is a number greater than or equal to *n*. If `_DEBUG` 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_init"._DEBUG + -- _DEBUG.level = 3 + -- say (2, "_DEBUG table contents:", _DEBUG) + say = say, + + --- Trace function calls. + -- Use as debug.sethook (trace, "cr"), which is done automatically + -- when `_DEBUG.call` is set. + -- Based on test/trace-calls.lua from the Lua distribution. + -- @function trace + -- @string event event causing the call + -- @usage + -- _DEBUG = { call = true } + -- local debug = require "std.debug" + trace = trace, } @@ -166,12 +167,12 @@ local M = { -- @function __call -- @see say -- @usage --- local debug = require "std.debug" --- debug "oh noes!" +-- local debug = require "std.debug" +-- debug "oh noes!" local metatable = { - __call = function (self, ...) - M.say (1, ...) - end, + __call = function (self, ...) + M.say (1, ...) + end, } diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index 2117529..66d1cea 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -7,29 +7,29 @@ local function choose (t) - for k, v in pairs (t) do - if _DEBUG == false then - t[k] = v.fast - elseif _DEBUG == nil then - t[k] = v.default - elseif type(_DEBUG) ~= "table" then - t[k] = v.safe - elseif _DEBUG[k] ~= nil then - t[k] = _DEBUG[k] - else - t[k] = v.default - end - end - return t + for k, v in pairs (t) do + if _DEBUG == false then + t[k] = v.fast + elseif _DEBUG == nil then + t[k] = v.default + elseif type(_DEBUG) ~= "table" then + t[k] = v.safe + elseif _DEBUG[k] ~= nil then + t[k] = _DEBUG[k] + else + t[k] = v.default + end + end + return t end return { - _DEBUG = choose { - argcheck = { default = true, safe = true, fast = false}, - call = { default = false, safe = false, fast = false}, - deprecate = { default = nil, safe = true, fast = false}, - level = { default = 1, safe = 1, fast = math.huge}, - strict = { default = true, safe = true, fast = false}, - }, + _DEBUG = choose { + argcheck = { default = true, safe = true, fast = false}, + call = { default = false, safe = false, fast = false}, + deprecate = { default = nil, safe = true, fast = false}, + level = { default = 1, safe = 1, fast = math.huge}, + strict = { default = true, safe = true, fast = false}, + }, } diff --git a/lib/std/init.lua b/lib/std/init.lua index 141e736..d163f10 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -7,42 +7,42 @@ 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). + function and variable names); library functions should call + error, not die; OO vs non-OO (a thorny problem). @todo pre-compile. @corefunction std ]] -local error = error -local ipairs = ipairs -local loadstring = loadstring or load -local pairs = pairs -local pcall = pcall -local rawset = rawset -local require = require -local setmetatable = setmetatable -local tostring = tostring -local type = type +local error = error +local ipairs = ipairs +local loadstring = loadstring or load +local pairs = pairs +local pcall = pcall +local rawset = rawset +local require = require +local setmetatable = setmetatable +local tostring = tostring +local type = type -local string_format = string.format -local string_match = string.match +local string_format = string.format +local string_match = string.match -local _ = require "std._base" +local _ = require "std._base" -local _ipairs = _.ipairs -local _pairs = _.pairs -local _tostring = _.tostring -local argscheck = _.typecheck and _.typecheck.argscheck -local compare = _.list.compare -local copy = _.base.copy -local getmetamethod = _.getmetamethod -local maxn = _.table.maxn -local merge = _.base.merge -local split = _.string.split +local _ipairs = _.ipairs +local _pairs = _.pairs +local _tostring = _.tostring +local argscheck = _.typecheck and _.typecheck.argscheck +local compare = _.list.compare +local copy = _.base.copy +local getmetamethod = _.getmetamethod +local maxn = _.table.maxn +local merge = _.base.merge +local split = _.string.split -local _ENV = _.strict and _.strict {} or {} +local _ENV = _.strict and _.strict {} or {} _ = nil @@ -57,74 +57,74 @@ local M local function _assert (expect, fmt, arg1, ...) - local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or "" - return expect or error (msg, 2) + local msg = (arg1 ~= nil) and string_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 + -- 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 loadstring ("return " .. s)() + return loadstring ("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 + -- 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 + 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 + 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 + 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 @@ -141,25 +141,25 @@ local vconvert = setmetatable ({ local function vcompare (a, b) - return compare (vconvert (a), vconvert (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 = string_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 + 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 = string_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 @@ -170,204 +170,204 @@ end local function X (decl, fn) - return argscheck and argscheck ("std." .. decl, fn) or 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), - - --- Enhance core `tostring` to render table contents as a string. - -- @function tostring - -- @param x object to convert to string - -- @treturn string compact string rendering of *x* - -- @usage - -- -- {1=baz,foo=bar} - -- print (std.tostring {foo="bar","baz"}) - tostring = X ("tostring (?any)", _tostring), - - - --- 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), + --- 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), + + --- Enhance core `tostring` to render table contents as a string. + -- @function tostring + -- @param x object to convert to string + -- @treturn string compact string rendering of *x* + -- @usage + -- -- {1=baz,foo=bar} + -- print (std.tostring {foo="bar","baz"}) + tostring = X ("tostring (?any)", _tostring), + + + --- 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), } @@ -375,21 +375,21 @@ M = { -- @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, + --- 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/lib/std/io.lua b/lib/std/io.lua index ce0fbfa..254b108 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -2,51 +2,51 @@ 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, + 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" + local io = require "std.io" @corelibrary std.io ]] -local _G = _G -local arg = arg -local error = error -local getmetatable = getmetatable -local io = io -local rawget = rawget -local setmetatable = debug.setmetatable -local type = type - -local io_input = io.input -local io_open = io.open -local io_output = io.output -local io_popen = io.popen -local io_stderr = io.stderr -local io_stdin = io.stdin -local io_type = io.type -local io_write = io.write -local string_format = string.format -local table_concat = table.concat -local table_insert = table.insert - - -local _ = require "std._base" - -local _ipairs = _.ipairs -local _tostring = _.tostring -local argerror = _.debug.argerror -local argscheck = _.typecheck and _.typecheck.argscheck -local catfile = _.io.catfile -local dirsep = _.package.dirsep -local leaves = _.tree.leaves -local len = _.operator.len -local merge = _.base.merge -local split = _.string.split - -local _ENV = _.strict and _.strict {} or {} +local _G = _G +local arg = arg +local error = error +local getmetatable = getmetatable +local io = io +local rawget = rawget +local setmetatable = debug.setmetatable +local type = type + +local io_input = io.input +local io_open = io.open +local io_output = io.output +local io_popen = io.popen +local io_stderr = io.stderr +local io_stdin = io.stdin +local io_type = io.type +local io_write = io.write +local string_format = string.format +local table_concat = table.concat +local table_insert = table.insert + + +local _ = require "std._base" + +local _ipairs = _.ipairs +local _tostring = _.tostring +local argerror = _.debug.argerror +local argscheck = _.typecheck and _.typecheck.argscheck +local catfile = _.io.catfile +local dirsep = _.package.dirsep +local leaves = _.tree.leaves +local len = _.operator.len +local merge = _.base.merge +local split = _.string.split + +local _ENV = _.strict and _.strict {} or {} _ = nil @@ -61,94 +61,94 @@ local M local function input_handle (h) - if h == nil then - return io_input () - elseif type (h) == "string" then - return io_open (h) - end - return h + if h == nil then + return io_input () + elseif type (h) == "string" then + return io_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 + 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 + 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 - io_write (h, "\n") - h = io_output () - end - for v in leaves (_ipairs, {...}) do - h:write (v, "\n") - end + if io_type (h) ~= "file" then + io_write (h, "\n") + h = io_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 - table_insert (arg, "-") - end - for i, v in _ipairs (arg) do - if v == "-" then - io_input (io_stdin) - else - io_input (v) - end - fn (v, i) - end + -- N.B. "arg" below refers to the global array of command-line args + if len (arg) == 0 then + table_insert (arg, "-") + end + for i, v in _ipairs (arg) do + if v == "-" then + io_input (io_stdin) + else + io_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 .. _tostring (prog.line) .. ":" - end - elseif prog.file then - prefix = prog.file .. ":" - if prog.line then - prefix = prefix .. _tostring (prog.line) .. ":" - end - elseif opts.program then - prefix = opts.program .. ":" - if opts.line then - prefix = prefix .. _tostring (opts.line) .. ":" - end - end - if #prefix > 0 then prefix = prefix .. " " end - return prefix .. string_format (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 .. _tostring (prog.line) .. ":" + end + elseif prog.file then + prefix = prog.file .. ":" + if prog.line then + prefix = prefix .. _tostring (prog.line) .. ":" + end + elseif opts.program then + prefix = opts.program .. ":" + if opts.line then + prefix = prefix .. _tostring (opts.line) .. ":" + end + end + if #prefix > 0 then prefix = prefix .. " " end + return prefix .. string_format (msg, ...) end local function warn (msg, ...) - writelines (io_stderr, warnfmt (msg, ...)) + writelines (io_stderr, warnfmt (msg, ...)) end @@ -159,139 +159,149 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.io." .. decl, fn) or 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 (table_concat ({...}, dirsep):gsub("^$", 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 (path:gsub (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 (io_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), + --- 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 (table_concat ({...}, dirsep):gsub("^$", 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 (path:gsub (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 (io_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), } @@ -307,7 +317,7 @@ return merge (M, io) -- @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) +-- local fileprocessor = function (filename, i) +-- io.write (tostring (i) .. ":\n===\n" .. io.slurp (filename) .. "\n") +-- end +-- io.process_files (fileprocessor) diff --git a/lib/std/math.lua b/lib/std/math.lua index 65e3b94..0ed214f 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -2,26 +2,26 @@ 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 + 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" + local math = require "std.math" @corelibrary std.math ]] -local math = math +local math = math -local math_floor = math.floor +local math_floor = math.floor -local _ = require "std._base" +local _ = require "std._base" -local argscheck = _.typecheck and _.typecheck.argscheck -local merge = _.base.merge +local argscheck = _.typecheck and _.typecheck.argscheck +local merge = _.base.merge -local _ENV = _.strict and _.strict {} or {} +local _ENV = _.strict and _.strict {} or {} _ = nil @@ -36,17 +36,17 @@ local M local function floor (n, p) - if (p or 0) == 0 then - return math_floor (n) - end - local e = 10 ^ p - return math_floor (n * e) / e + if (p or 0) == 0 then + return math_floor (n) + end + local e = 10 ^ p + return math_floor (n * e) / e end local function round (n, p) - local e = 10 ^ (p or 0) - return math_floor (n * e + 0.5) / e + local e = 10 ^ (p or 0) + return math_floor (n * e + 0.5) / e end @@ -57,29 +57,31 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.math." .. decl, fn) or 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), + --- 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), } diff --git a/lib/std/package.lua b/lib/std/package.lua index cad3ccf..f268b04 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -2,62 +2,62 @@ 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 + 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" + 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.path_mark`, for easy addition of new paths. For example, instead of all this: - 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 + 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 - end - table.insert (paths, 1, lib) - package.path = table.concat (paths, package.pathsep) + 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) + package.path = package.normalize ("./lib/?.lua", package.path) @corelibrary std.package ]] -local ipairs = ipairs -local package = package +local ipairs = ipairs +local package = package -local package_config = package.config -local string_match = string.match -local table_concat = table.concat -local table_insert = table.insert -local table_remove = table.remove -local table_unpack = table.unpack or unpack +local package_config = package.config +local string_match = string.match +local table_concat = table.concat +local table_insert = table.insert +local table_remove = table.remove +local table_unpack = table.unpack or unpack -local _ = require "std._base" +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 len = _.operator.len -local merge = _.base.merge -local split = _.string.split +local argscheck = _.typecheck and _.typecheck.argscheck +local catfile = _.io.catfile +local escape_pattern = _.string.escape_pattern +local invert = _.table.invert +local len = _.operator.len +local merge = _.base.merge +local split = _.string.split -local _ENV = _.strict and _.strict {} or {} +local _ENV = _.strict and _.strict {} or {} _ = nil @@ -77,95 +77,94 @@ _ = nil -- @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 dirsep, pathsep, path_mark, execdir, igmark = - string_match (package_config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") + string_match (package_config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") local function pathsub (path) - return path:gsub ("%%?.", function (capture) - if capture == "?" then - return path_mark - elseif capture == "/" then - return dirsep - else - return capture:gsub ("^%%", "", 1) - end - end) + return path:gsub ("%%?.", function (capture) + if capture == "?" then + return path_mark + elseif capture == "/" then + return dirsep + else + return capture:gsub ("^%%", "", 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 paths[i]:find (patt) then return i, paths[i] end - end + 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 paths[i]:find (patt) then return i, paths[i] end + end end local function normalize (...) - local i, paths, pathstrings = 1, {}, table_concat ({...}, pathsep) - for _, path in ipairs (split (pathstrings, pathsep)) do - path = pathsub (path): - gsub (catfile ("^[^", "]"), catfile (".", "%0")): - gsub (catfile ("", "%.", ""), dirsep): - gsub (catfile ("", "%.$"), ""): - gsub (catfile ("^%.", "%..", ""), catfile ("..", "")): - gsub (catfile ("", "$"), "") - - -- Carefully remove redundant /foo/../ matches. - repeat - local again = false - path = path:gsub (catfile ("", "([^", "]+)", "%.%.", ""), - function (dir1) - if dir1 == ".." then -- don't remove /../../ - return catfile ("", "..", "..", "") - else - again = true - return dirsep - end - end): - gsub (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 table_concat (invert (paths), pathsep) + local i, paths, pathstrings = 1, {}, table_concat ({...}, pathsep) + for _, path in ipairs (split (pathstrings, pathsep)) do + path = pathsub (path): + gsub (catfile ("^[^", "]"), catfile (".", "%0")): + gsub (catfile ("", "%.", ""), dirsep): + gsub (catfile ("", "%.$"), ""): + gsub (catfile ("^%.", "%..", ""), catfile ("..", "")): + gsub (catfile ("", "$"), "") + + -- Carefully remove redundant /foo/../ matches. + repeat + local again = false + path = path:gsub (catfile ("", "([^", "]+)", "%.%.", ""), + function (dir1) + if dir1 == ".." then -- don't remove /../../ + return catfile ("", "..", "..", "") + else + again = true + return dirsep + end + end):gsub (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 table_concat (invert (paths), pathsep) end local function insert (pathstrings, ...) - local paths = split (pathstrings, pathsep) - table_insert (paths, ...) - return normalize (table_unpack (paths, 1, len (paths))) + local paths = split (pathstrings, pathsep) + table_insert (paths, ...) + return normalize (table_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 + 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 table_concat (paths, pathsep) + local paths = split (pathstrings, pathsep) + table_remove (paths, pos) + return table_concat (paths, pathsep) end @@ -176,73 +175,77 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.package." .. decl, fn) or 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 - -- `path_mark` (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), + --- 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 + -- `path_mark` (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), } -M.dirsep = dirsep -M.execdir = execdir -M.igmark = igmark +M.dirsep = dirsep +M.execdir = execdir +M.igmark = igmark M.path_mark = path_mark -M.pathsep = pathsep +M.pathsep = pathsep return merge (M, package) @@ -254,6 +257,6 @@ return merge (M, package) --- Function signature of a callback for @{mappath}. -- @function mappathcb -- @string element an element from a `pathsep` delimited string of --- paths +-- paths -- @param ... additional arguments propagated from @{mappath} -- @return non-nil to break, otherwise continue with the next element diff --git a/lib/std/string.lua b/lib/std/string.lua index d9ee05b..6e77777 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -2,43 +2,43 @@ 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 + 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" + local string = require "std.string" @corelibrary std.string ]] -local assert = assert -local getmetatable = getmetatable -local string = string -local tonumber = tonumber -local tostring = tostring -local type = type +local assert = assert +local getmetatable = getmetatable +local string = string +local tonumber = tonumber +local tostring = tostring +local type = type -local io_stderr = io.stderr -local math_abs = math.abs -local math_floor = math.floor -local table_concat = table.concat -local table_insert = table.insert -local string_format = string.format +local io_stderr = io.stderr +local math_abs = math.abs +local math_floor = math.floor +local table_concat = table.concat +local table_insert = table.insert +local string_format = string.format -local _ = require "std._base" +local _ = require "std._base" -local _tostring = _.tostring -local argscheck = _.typecheck and _.std.typecheck.argscheck -local copy = _.base.copy -local escape_pattern = _.string.escape_pattern -local len = _.operator.len -local merge = _.base.merge -local render = _.string.render -local sortkeys = _.base.sortkeys -local split = _.string.split +local _tostring = _.tostring +local argscheck = _.typecheck and _.std.typecheck.argscheck +local copy = _.base.copy +local escape_pattern = _.string.escape_pattern +local len = _.operator.len +local merge = _.base.merge +local render = _.string.render +local sortkeys = _.base.sortkeys +local split = _.string.split -local _ENV = _.strict and _.strict {} or _ENV +local _ENV = _.strict and _.strict {} or _ENV _ = nil @@ -53,194 +53,194 @@ local M local function __concat (s, o) - return _tostring (s) .. _tostring (o) + return _tostring (s) .. _tostring (o) end local function __index (s, i) - if type (i) == "number" then - return s:sub (i, i) - else - -- Fall back to module metamethods - return M[i] - end + if type (i) == "number" then + return s:sub (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 + return (arg1 ~= nil) and _format (f, arg1, ...) or f end local function tpack (from, to, ...) - return from, to, {...} + return from, to, {...} end local function tfind (s, ...) - return tpack (s:find (...)) + return tpack (s:find (...)) 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 - table_insert (l, {from, to, capt = r}) - i = to + 1 - end - until not from - return l + i = i or 1 + local l = {} + local from, to, r + repeat + from, to, r = tfind (s, p, i, ...) + if from ~= nil then + table_insert (l, {from, to, capt = r}) + i = to + 1 + end + until not from + return l end local function caps (s) - return (s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end)) + return (s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end)) end local function escape_shell (s) - return (s:gsub ("([ %(%)%\\%[%]\"'])", "\\%1")) + return (s:gsub ("([ %(%)%\\%[%]\"'])", "\\%1")) end local function ordinal_suffix (n) - n = math_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 + n = math_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 " ", math_abs (w)) - if w < 0 then - return string.sub (p .. s, w) - end - return string.sub (s .. p, 1, w) + p = string.rep (p or " ", math_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 - table_insert (r, s:sub (i, j)) - i = ni - if i < lens then - table_insert (r, "\n" .. string.rep (" ", ind)) - lstart = ind - end - end - return table_concat (r) + 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 + table_insert (r, s:sub (i, j)) + i = ni + if i < lens then + table_insert (r, "\n" .. string.rep (" ", ind)) + lstart = ind + end + end + return table_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 = t:find(".(.%...)e(.+)") - local man, exp = tonumber (m), tonumber (e) - local siexp = math_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 + 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 = t:find(".(.%...)e(.+)") + local man, exp = tonumber (m), tonumber (e) + local siexp = math_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 local function prettytostring (x, indent, spacing) - indent = indent or "\t" - spacing = spacing or "" - return render (x, { - open = function () - local s = spacing .. "{" - spacing = spacing .. indent - return s - end, - - close = function () - spacing = string.gsub (spacing, indent .. "$", "") - return spacing .. "}" - end, - - elem = function (x) - if type (x) ~= "string" then return tostring (x) end - return string_format ("%q", x) - end, - - pair = function (x, _, _, k, v, kstr, vstr) - local type_k = type (k) - local s = spacing - if type_k ~= "string" or k:match "[^%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, - - sort = sortkeys, - }) + indent = indent or "\t" + spacing = spacing or "" + return render (x, { + open = function () + local s = spacing .. "{" + spacing = spacing .. indent + return s + end, + + close = function () + spacing = string.gsub (spacing, indent .. "$", "") + return spacing .. "}" + end, + + elem = function (x) + if type (x) ~= "string" then return tostring (x) end + return string_format ("%q", x) + end, + + pair = function (x, _, _, k, v, kstr, vstr) + local type_k = type (k) + local s = spacing + if type_k ~= "string" or k:match "[^%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, + + sort = sortkeys, + }) end local function trim (s, r) - r = r or "%s+" - return (s:gsub ("^" .. r, ""):gsub (r .. "$", "")) + r = r or "%s+" + return (s:gsub ("^" .. r, ""):gsub (r .. "$", "")) end @@ -251,209 +251,223 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.string." .. decl, fn) or 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 `s:sub (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 (s:gsub ("\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 = inputstr:match (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 (s:gsub ("^" .. (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), - - --- Turn tables into strings with recursion detection. - -- N.B. Functions calling render should not recurse, or recursion - -- detection will not work. - -- @function render - -- @param x object to convert to string - -- @tparam[opt] rendercbs fns default rendering function overrides - -- @return string representation of *x* - -- @usage - -- function tostablestring (x) - -- return render (x, { - -- sort = function (keys) - -- table.sort (keys, lambda "=tostring (_1) < tostring (_2)") - -- return keys - -- end, - -- }) - -- end - render = X ("render (?any, ?table)", function (x, rendercbs, roots) + --- 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 `s:sub (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 (s:gsub ("\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 = inputstr:match (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 (s:gsub ("^" .. (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), + + --- Turn tables into strings with recursion detection. + -- N.B. Functions calling render should not recurse, or recursion + -- detection will not work. + -- @function render + -- @param x object to convert to string + -- @tparam[opt] rendercbs fns default rendering function overrides + -- @return string representation of *x* + -- @usage + -- function tostablestring (x) + -- return render (x, { + -- sort = function (keys) + -- table.sort (keys, lambda "=tostring (_1) < tostring (_2)") + -- return keys + -- end, + -- }) + -- end + render = X ("render (?any, ?table)", function (x, rendercbs, roots) return render (x, rendercbs, roots) - end - ), - - --- 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 (s:gsub ((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), + end), + + --- 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 (s:gsub ((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), } @@ -475,9 +489,9 @@ return merge (M, string) -- @tfield[opt] termcb term terminal predicate -- @see render -- @usage --- function tostringstable (x) --- return render (x, { sort = some_sequence_reordering_fn }) --- end +-- function tostringstable (x) +-- return render (x, { sort = some_sequence_reordering_fn }) +-- end --- Signature of @{render} open table callback. @@ -485,7 +499,8 @@ return merge (M, string) -- @tparam table t table about to be rendered -- @treturn string open table rendering -- @see render --- @usage function open (t) return "{" end +-- @usage +-- function open (t) return "{" end --- Signature of @{render} close table callback. @@ -493,7 +508,8 @@ return merge (M, string) -- @tparam table t table just rendered -- @treturn string close table rendering -- @see render --- @usage function close (t) return "}" end +-- @usage +-- function close (t) return "}" end --- Signature of @{render} element callback. @@ -501,7 +517,8 @@ return merge (M, string) -- @param x element to render -- @treturn string element rendering -- @see render --- @usage function element (e) return require "std".tostring (e) end +-- @usage +-- function element (e) return require "std".tostring (e) end --- Signature of @{render} pair callback. @@ -516,7 +533,7 @@ return merge (M, string) -- @treturn string pair rendering -- @see render -- @usage --- function pair (_, _, _, key, value) return key .. "=" .. value end +-- function pair (_, _, _, key, value) return key .. "=" .. value end --- Signature of @{render} separator callback. @@ -528,7 +545,7 @@ return merge (M, string) -- @param fv *t* value following separator, or `nil` for last value -- @treturn string separator rendering -- @usage --- function separator (_, _, _, fk) return fk and "," or "" end +-- function separator (_, _, _, fk) return fk and "," or "" end --- Signature of @{render} key sorting callback. @@ -536,7 +553,7 @@ return merge (M, string) -- @tparam sequence keys all keys from rendering table -- @treturn sequence *keys* in desired display order -- @usage --- function unsorted (keys) return keys end +-- function unsorted (keys) return keys end --- Signature of @{render} terminal predicate callback. @@ -544,6 +561,6 @@ return merge (M, string) -- @param x an element to be rendered -- @treturn boolean whether *x* can be rendered by @{elementcb} -- @usage --- function term (x) --- return type (x) ~= "table" or getmetamethod (x, "__tostring") --- end +-- function term (x) +-- return type (x) ~= "table" or getmetamethod (x, "__tostring") +-- end diff --git a/lib/std/table.lua b/lib/std/table.lua index 25563d3..e8cca56 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -2,42 +2,42 @@ 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 + 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" + local table = require "std.table" @corelibrary std.table ]] -local getmetatable = getmetatable -local next = next -local setmetatable = setmetatable -local table = table -local tonumber = tonumber -local type = type +local getmetatable = getmetatable +local next = next +local setmetatable = setmetatable +local table = table +local tonumber = tonumber +local type = type -local math_min = math.min -local table_insert = table.insert -local table_unpack = table.unpack or unpack +local math_min = math.min +local table_insert = table.insert +local table_unpack = table.unpack or unpack -local _ = require "std._base" +local _ = require "std._base" -local _ipairs = _.ipairs -local _pairs = _.pairs -local argerror = _.debug.argerror -local argscheck = _.typecheck and _.typecheck.argscheck -local copy = _.base.copy -local getmetamethod = _.getmetamethod -local invert = _.table.invert -local len = _.operator.len -local maxn = _.table.maxn -local merge = _.base.merge -local pack = _.table.pack +local _ipairs = _.ipairs +local _pairs = _.pairs +local argerror = _.debug.argerror +local argscheck = _.typecheck and _.typecheck.argscheck +local copy = _.base.copy +local getmetamethod = _.getmetamethod +local invert = _.table.invert +local len = _.operator.len +local maxn = _.table.maxn +local merge = _.base.merge +local pack = _.table.pack -local _ENV = _.strict and _.strict {} or _ENV +local _ENV = _.strict and _.strict {} or _ENV _ = nil @@ -52,95 +52,94 @@ 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 + 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 + 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 + 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 + local tt = {} + for i, v in _pairs (t) do + tt[#tt + 1] = {i, v} + end + return tt end 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 - table_insert (t, pos, v) - return t + 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 + table_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 + 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}) + 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 + 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 + local n = 0 + for _ in _pairs (t) do + n = n + 1 + end + return n end @@ -148,39 +147,39 @@ end local _sort = table.sort local function sort (t, c) - _sort (t, c) - return t + _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 < math_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) + local lent = len (t) + pos = pos or lent + if pos < math_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 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 table_unpack (t, tonumber (i) or 1, tonumber (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 table_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 + local l = {} + for _, v in _pairs (t) do + l[#l + 1] = v + end + return l end @@ -191,223 +190,231 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.table." .. decl, fn) or 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 (("ax1"):find "(%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 another 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), + --- 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 (("ax1"):find "(%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 another 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), } @@ -425,5 +432,5 @@ return merge (M, table) -- @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) +-- local reversor = function (a, b) return a > b end +-- sort (t, reversor) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 5588f3c..d1acb83 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -8,37 +8,37 @@ before: | M = require (this_module) - table_unpack = table.unpack or unpack + table_unpack = table.unpack or unpack - function map (mapfn, ...) - local argt, r = pack (...), {} + function map (mapfn, ...) + local argt, r = pack (...), {} - local nextfn, state, k = pairs (table_unpack (argt, 1, argt.n)) - local mapargs = pack (nextfn (state, k)) + local nextfn, state, k = pairs (table_unpack (argt, 1, argt.n)) + local mapargs = pack (nextfn (state, k)) - local arity = 1 - while mapargs[1] ~= nil do - local d, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) - if v ~= nil then - arity, r = 2, {} break + local arity = 1 + while mapargs[1] ~= nil do + local d, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) + if v ~= nil then + arity, r = 2, {} break + end + r[#r + 1] = d + mapargs = {nextfn (state, mapargs[1])} end - r[#r + 1] = d - mapargs = {nextfn (state, mapargs[1])} - end - if arity > 1 then - -- No need to start over here, because either: - -- (i) arity was never 1, and the original value of mapargs is correct - -- (ii) arity used to be 1, but we only consumed nil values, so the - -- current mapargs with arity > 1 is the correct next value to use - while mapargs[1] ~= nil do - local k, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) - r[k] = v - mapargs = pack (nextfn (state, mapargs[1])) + if arity > 1 then + -- No need to start over here, because either: + -- (i) arity was never 1, and the original value of mapargs is correct + -- (ii) arity used to be 1, but we only consumed nil values, so the + -- current mapargs with arity > 1 is the correct next value to use + while mapargs[1] ~= nil do + local k, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) + r[k] = v + mapargs = pack (nextfn (state, mapargs[1])) + end end - end - return r - end + return r + end specify std.debug: @@ -46,163 +46,163 @@ specify std.debug: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - to_equal {} + to_equal {} - it does not touch the core debug table: expect (show_apis {added_to=base_module, by=this_module}). - to_equal {} + 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) + 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 {} + to_equal {} - it does not touch the core debug table: expect (show_apis {added_to=base_module, by="std"}). - to_equal {} + to_equal {} - describe debug: - before: | function mkwrap (k, v) - local fmt = "%s" - if type (v) == "string" then fmt = "%q" end - return k, string.format (fmt, require "std".tostring (v)) + local fmt = "%s" + if type (v) == "string" then fmt = "%q" end + return k, string.format (fmt, require "std".tostring (v)) end function mkdebug (debugp, ...) - return string.format ([[ - _DEBUG = %s - require "std.debug" (%s) - ]], - require "std".tostring (debugp), - table.concat (map (mkwrap, {...}), ", ")) + return string.format ([[ + _DEBUG = %s + require "std.debug" (%s) + ]], + require "std".tostring (debugp), + table.concat (map (mkwrap, {...}), ", ")) end - it does nothing when _DEBUG is disabled: expect (luaproc (mkdebug (false, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" - it writes to stderr when _DEBUG is not set: expect (luaproc (mkdebug (nil, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it writes to stderr when _DEBUG is enabled: expect (luaproc (mkdebug (true, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it writes to stderr when _DEBUG.level is not set: expect (luaproc (mkdebug ({}, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it writes to stderr when _DEBUG.level is specified: expect (luaproc (mkdebug ({level = 0}, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mkdebug ({level = 1}, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mkdebug ({level = 2}, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - describe say: - before: | function mkwrap (k, v) - local fmt = "%s" - if type (v) == "string" then fmt = "%q" end - return k, string.format (fmt, require "std".tostring (v)) + local fmt = "%s" + if type (v) == "string" then fmt = "%q" end + return k, string.format (fmt, require "std".tostring (v)) end function mksay (debugp, ...) - return string.format ([[ - _DEBUG = %s - require "std.debug".say (%s) - ]], - require "std".tostring (debugp), - table.concat (map (mkwrap, {...}), ", ")) + return string.format ([[ + _DEBUG = %s + require "std.debug".say (%s) + ]], + require "std".tostring (debugp), + table.concat (map (mkwrap, {...}), ", ")) end f = M.say - it uses stdlib tostring: expect (luaproc [[require "std.debug".say {"debugging"}]]). - to_contain_error (require "std".tostring {"debugging"}) + to_contain_error (require "std".tostring {"debugging"}) - context when _DEBUG is disabled: - it does nothing when message level is not set: expect (luaproc (mksay (false, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" - it does nothing when message is set: expect (luaproc (mksay (false, -999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" expect (luaproc (mksay (false, 0, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" expect (luaproc (mksay (false, 1, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" expect (luaproc (mksay (false, 2, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" expect (luaproc (mksay (false, 999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" - context when _DEBUG is not set: - it writes to stderr when message level is not set: expect (luaproc (mksay (nil, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it writes to stderr when message level is 1 or lower: expect (luaproc (mksay (nil, -999, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mksay (nil, 0, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mksay (nil, 1, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it does nothing when message level is 2 or higher: expect (luaproc (mksay (nil, 2, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" expect (luaproc (mksay (nil, 999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" - context when _DEBUG is enabled: - it writes to stderr when message level is not set: expect (luaproc (mksay (true, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it writes to stderr when message level is 1 or lower: expect (luaproc (mksay (true, -999, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mksay (true, 0, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mksay (true, 1, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it does nothing when message level is 2 or higher: expect (luaproc (mksay (true, 2, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" expect (luaproc (mksay (true, 999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" - context when _DEBUG.level is not set: - it writes to stderr when message level is not set: expect (luaproc (mksay ({}, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it writes to stderr when message level is 1 or lower: expect (luaproc (mksay ({}, -999, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mksay ({}, 0, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mksay ({}, 1, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it does nothing when message level is 2 or higher: expect (luaproc (mksay ({}, 2, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" expect (luaproc (mksay ({}, 999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" - context when _DEBUG.level is specified: - it writes to stderr when message level is 1 or lower: expect (luaproc (mksay ({level = 0}, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mksay ({level = 1}, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" expect (luaproc (mksay ({level = 2}, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it does nothing when message level is higher than debug level: expect (luaproc (mksay ({level = 2}, 3, "nothing to see here"))). - not_to_contain_error "nothing to see here" + not_to_contain_error "nothing to see here" - it writes to stderr when message level equals debug level: expect (luaproc (mksay ({level = 2}, 2, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - it writes to stderr when message level is lower than debug level: expect (luaproc (mksay ({level = 2}, 1, "debugging"))). - to_contain_error "debugging" + to_contain_error "debugging" - describe trace: @@ -211,50 +211,50 @@ specify std.debug: - it does nothing when _DEBUG is disabled: expect (luaproc [[ - _DEBUG = false - require "std.debug" - os.exit (0) + _DEBUG = false + require "std.debug" + os.exit (0) ]]).to_succeed_with "" - it does nothing when _DEBUG is not set: expect (luaproc [[ - require "std.debug" - os.exit (0) + require "std.debug" + os.exit (0) ]]).to_succeed_with "" - it does nothing when _DEBUG is enabled: expect (luaproc [[ - _DEBUG = true - require "std.debug" - os.exit (0) + _DEBUG = true + require "std.debug" + os.exit (0) ]]).to_succeed_with "" - it enables automatically when _DEBUG.call is set: | expect (luaproc [[ - _DEBUG = {call = true} - local debug = require "std.debug" - os.exit (1) + _DEBUG = {call = true} + local debug = 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) + 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) + 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 + 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)) + 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/specs/io_spec.yaml b/specs/io_spec.yaml index ec2a5e1..8497f84 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -1,11 +1,11 @@ before: | - base_module = "io" - this_module = "std.io" - global_table = "_G" + base_module = "io" + this_module = "std.io" + global_table = "_G" - extend_base = { "catdir", "catfile", "die", "dirname", - "process_files", "readlines", "shell", "slurp", - "splitdir", "warn", "writelines" } + extend_base = { "catdir", "catfile", "die", "dirname", + "process_files", "readlines", "shell", "slurp", + "splitdir", "warn", "writelines" } dirsep = string.match (package.config, "^([^\n]+)\n") @@ -17,21 +17,21 @@ specify std.io: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - to_equal {} + to_equal {} - it does not touch the core io table: expect (show_apis {added_to=base_module, by=this_module}). - to_equal {} + 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) + 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 {} + to_equal {} - it does not touch the core io table: expect (show_apis {added_to=base_module, by="std"}). - to_equal {} + to_equal {} - describe catdir: @@ -50,7 +50,7 @@ specify std.io: - 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)) + to_be (table.concat ({"1", "2", "3", "4", "5"}, dirsep)) - describe catfile: @@ -69,7 +69,7 @@ specify std.io: - 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)) + to_be (table.concat ({"1", "2", "3", "4", "5"}, dirsep)) - describe die: @@ -103,25 +103,25 @@ specify std.io: 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" } + 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" } + 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" } + 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" } + 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: | @@ -149,16 +149,16 @@ specify std.io: name = "Makefile" names = {"LICENSE.md", "Makefile", "README.md"} ascript = [[ - require "std.io".process_files (function (a) print (a) end) + require "std.io".process_files (function (a) print (a) end) ]] lscript = [[ - require "std.io".process_files ("=print (_1)") + require "std.io".process_files ("=print (_1)") ]] iscript = [[ - require "std.io".process_files (function (_, i) print (i) end) + require "std.io".process_files (function (_, i) print (i) end) ]] catscript = [[ - require "std.io".process_files (function () io.write (io.input ():read "*a") end) + require "std.io".process_files (function () io.write (io.input ():read "*a") end) ]] f = M.process_files @@ -167,12 +167,12 @@ specify std.io: 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 'io_input' (not-an-existing-file:", -- Lua 5.1 - } - end + ["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 'io_input' (not-an-existing-file:", -- Lua 5.1 + } + end } - it defaults to `-` if no arguments were passed: @@ -180,13 +180,13 @@ specify std.io: - 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") + 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))) + 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" @@ -212,18 +212,18 @@ specify std.io: 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 - } + 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: @@ -267,18 +267,18 @@ specify std.io: 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 - } + 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: @@ -311,7 +311,7 @@ specify std.io: - 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"} + to_equal {"1", "2", "3", "4", "5"} - describe warn: @@ -341,25 +341,25 @@ specify std.io: 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" } + 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" } + 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" } + 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" } + 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: | @@ -386,20 +386,20 @@ specify std.io: - 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")) + 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")) + 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")) + 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")) + expect (f (closed)).to_raise (badarg (1, "?file|string|number", "closed file")) end - it does not close the file handle upon completion: diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index fada420..59abea2 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -13,21 +13,21 @@ specify std.math: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - to_equal {} + to_equal {} - it does not touch the core math table: expect (show_apis {added_to=base_module, by=this_module}). - to_equal {} + 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) + 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 {} + to_equal {} - it does not touch the core math table: expect (show_apis {added_to=base_module, by="std"}). - to_equal {} + to_equal {} - describe floor: diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index 170cd5b..2fb80e9 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -1,11 +1,11 @@ before: | - base_module = "package" - this_module = "std.package" - global_table = "_G" + base_module = "package" + this_module = "std.package" + global_table = "_G" - extend_base = { "dirsep", "execdir", "find", "igmark", "insert", - "mappath", "normalize", "pathsep", "path_mark", - "remove" } + extend_base = { "dirsep", "execdir", "find", "igmark", "insert", + "mappath", "normalize", "pathsep", "path_mark", + "remove" } M = require (this_module) @@ -20,21 +20,21 @@ specify std.package: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - to_equal {} + to_equal {} - it does not touch the core package table: expect (show_apis {added_to=base_module, by=this_module}). - to_equal {} + 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) + 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 {} + to_equal {} - it does not touch the core package table: expect (show_apis {added_to=base_module, by="std"}). - to_equal {} + to_equal {} - describe find: @@ -71,21 +71,21 @@ specify std.package: - it appends by default: expect (f (path, "new")). - to_be (M.normalize ("begin", "middle", "end", "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")) + 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")) + to_be (M.normalize ("begin", "new", "middle", "end")) expect (f (path, 3, "new")). - to_be (M.normalize ("begin", "middle", "new", "end")) + 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")) + to_be (M.normalize ("begin", "middle", "end", "new")) expect (f (path, 1, "./x/../end")). - to_be (M.normalize ("end", "begin", "middle")) + to_be (M.normalize ("end", "begin", "middle")) - describe mappath: @@ -104,7 +104,7 @@ specify std.package: - it passes additional arguments through: | reversed = {} for i = #expected, 1, -1 do - table.insert (reversed, expected[i]) + table.insert (reversed, expected[i]) end t = {} f (path, function (e, pos) table.insert (t, pos, e) end, 1) @@ -135,37 +135,37 @@ specify std.package: expect (f "/foo/bar").to_be (catfile ("", "foo", "bar")) - it normalizes ? to platform path_mark: expect (f "?.lua"). - to_be (catfile (".", M.path_mark .. ".lua")) + to_be (catfile (".", M.path_mark .. ".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")) + 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"))) + 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"))) + 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"))) + to_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) - it normalizes ? to platform path_mark: expect (f ("?.lua", "x")). - to_be (catpath (catfile (".", M.path_mark .. ".lua"), catfile (".", "x"))) + to_be (catpath (catfile (".", M.path_mark .. ".lua"), catfile (".", "x"))) - it strips redundant trailing /: expect (f ("/foo/bar/", "x")). - to_be (catpath (catfile ("", "foo", "bar"), catfile (".", "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"))) + 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")) + to_be (catpath ("./1", "./x", "./2")) - describe remove: @@ -184,10 +184,10 @@ specify std.package: - 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)) + 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.path_mark, M.execdir, M.igmark) + M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark) ).to_contain (package.config) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 64a36c4..f16658f 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -1,23 +1,23 @@ local typecheck -have_typecheck, typecheck = pcall (require, "typecheck") +have_typecheck, typecheck = pcall (require, "typecheck") -local inprocess = require "specl.inprocess" -local hell = require "specl.shell" -local std = require "specl.std" +local inprocess = require "specl.inprocess" +local hell = require "specl.shell" +local std = require "specl.std" -badargs = require "specl.badargs" +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 - ) + 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 @@ -32,31 +32,31 @@ setdebug = require "std.debug"._setdebug -- 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 + 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 + 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 ("#", ...), ...} + return {n = select ("#", ...), ...} end @@ -66,58 +66,58 @@ 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))) + 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 + if have_typecheck then + return _diagnose (...) + end end badargs.result = badargs.result or function (fname, i, want, got) - if want == nil then i, want = i - 1, i end -- numbers only for narg error - - 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") + if want == nil then i, want = i - 1, i end -- numbers only for narg error + + 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 + 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) + return (getmetatable (o) or {})._type or io.type (o) or type (o) end @@ -128,48 +128,48 @@ function nop () end -- 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 + 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 + 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. +-- 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 +-- 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 + 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 @@ -182,29 +182,29 @@ end -- @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 +-- object for object method deprecation check -- @treturn specl.shell.Process|nil status of resulting process if --- execution was successful, otherwise nil +-- execution was successful, otherwise nil function deprecation (deprecate, module, fname, args, objectinit) - args = args or "" - local script - if objectinit == nil then - script = string.format([[ - _DEBUG = { deprecate = %s } - M = require "%s" - P = M.prototype - print (M.%s (%s)) - ]], tostring (deprecate), module, fname, tostring (args)) - else - script = string.format([[ - _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) + args = args or "" + local script + if objectinit == nil then + script = string.format([[ + _DEBUG = { deprecate = %s } + M = require "%s" + P = M.prototype + print (M.%s (%s)) + ]], tostring (deprecate), module, fname, tostring (args)) + else + script = string.format([[ + _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 @@ -212,162 +212,162 @@ end -- @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) + 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 + 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"`: +-- arguments are given. Lists new keys in T1 after `require "import"`: -- --- show_apis {added_to=T1, by=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} +-- 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} +-- 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} +-- 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 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 + 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 - ]]) + 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 from = ]] .. from .. [[ - local M = require "]] .. not_in .. [[" + elseif from and not_in then + return tabulate_output ([[ + local from = ]] .. from .. [[ + local 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 - ]]) + 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 from = ]] .. from .. [[ - local M = require "]] .. enhanced_in .. [[" + elseif from and enhanced_in then + return tabulate_output ([[ + local from = ]] .. from .. [[ + local 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 - ]]) + 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 before, after = {}, {} - local from = ]] .. from .. [[ + elseif from and enhanced_after then + return tabulate_output ([[ + local before, after = {}, {} + local from = ]] .. from .. [[ - 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 (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 + 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") + 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 + function (f, arg) return nil, nil, f (unpack (arg or {})) end do - -- Custom matcher for set size and set membership. + -- Custom matcher for set size and set membership. - local util = require "specl.util" - local matchers = require "specl.matchers" + local util = require "specl.util" + local matchers = require "specl.matchers" - local Matcher, matchers, q = - matchers.Matcher, matchers.matchers, matchers.stringify + 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, + 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", + actual = "table", - format_expect = function (self, expect) - return " a table containing " .. expect .. " elements, " - end, + 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, - } + 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, + matchers.have_member = Matcher { + function (self, actual, expect) + return actual[expect] ~= nil + end, - actual = "set", + actual = "set", - format_expect = function (self, expect) - return " a set containing " .. q (expect) .. ", " - end, + 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, - } + 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 + -- Alias that doesn't tickle sc_error_message_uppercase. + matchers.raise = matchers.error end diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 654c2d0..495531a 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -1,6 +1,6 @@ before: | - this_module = "std" - global_table = "_G" + this_module = "std" + global_table = "_G" exported_apis = { "assert", "elems", "eval", "getmetamethod", "ielems", "ipairs", "npairs", "pairs", @@ -8,22 +8,22 @@ before: | -- 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, - }) + __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, - }) + __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 @@ -33,7 +33,7 @@ specify std: - context when required: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - to_equal {} + to_equal {} - it exports the documented apis: t = {} for k in pairs (M) do t[#t + 1] = k end @@ -42,7 +42,7 @@ specify std: - context when lazy loading: - it has no submodules on initial load: for _, v in pairs (M) do - expect (type (v)).not_to_be "table" + expect (type (v)).not_to_be "table" end - it loads submodules on demand: lazy = M.math @@ -77,7 +77,7 @@ specify std: 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")) + to_raise (string.format ("%s %d: %q", "here", 42, "a string")) - describe elems: @@ -90,14 +90,14 @@ specify std: - it is an iterator over table values: t = {} for e in f {"foo", bar = "baz", 42} do - t[#t + 1] = e + 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"} + 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 @@ -130,7 +130,7 @@ specify std: method = function () return "called" end functor = setmetatable ({}, {__call = method}) t = setmetatable ({}, { - _type = "table", _method = method, _functor = functor, + _type = "table", _method = method, _functor = functor, }) - it returns nil for missing metamethods: expect (f (t, "not a metamethod on t")).to_be (nil) @@ -154,13 +154,13 @@ specify std: - it is an iterator over integer-keyed table values: t = {} for e in f {"foo", 42} do - t[#t + 1] = e + 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 + t[#t + 1] = e end expect (t).to_equal {"foo", 42} - it respects __len metamethod: @@ -183,13 +183,13 @@ specify std: - it is an iterator over integer-keyed table values: t = {} for i, v in f {"foo", 42} do - t[i] = v + 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 + t[i] = v end expect (t).to_equal {"foo", 42} - it respects __len metamethod: @@ -212,19 +212,19 @@ specify std: - it is an iterator over integer-keyed table values: t = {} for i, v in f {"foo", 42, nil, nil, "five"} do - t[i] = v + 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 + 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) + t[#t + 1] = tostring (v) end expect (table.concat (t, ",")).to_be "nil,false,nil,nil" - it works for an empty list: @@ -243,14 +243,14 @@ specify std: - it is an iterator over all table values: t = {} for k, v in f {"foo", bar = "baz", 42} do - t[k] = v + 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"} + 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 @@ -268,10 +268,10 @@ specify std: 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," + 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," + 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") @@ -294,24 +294,24 @@ specify std: 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," + 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," + 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," + to_raise "require 'std' with at least version 2.1.2," expect (f ("std", "2")). - to_raise "require 'std' with at least version 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," + 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," + 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," + 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," + 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," + 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) @@ -380,7 +380,7 @@ specify std: - 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"} + 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 @@ -392,7 +392,7 @@ specify std: - it respects __len metamethod: t = {} for _, v in f (setmetatable ({[2]=false}, {__len = function (self) return 4 end})) do - t[#t + 1] = tostring (v) + t[#t + 1] = tostring (v) end expect (table.concat (t, ",")).to_be "nil,nil,false,nil" - it works with the empty list: @@ -418,14 +418,14 @@ specify std: expect (f {}).to_be ("{}") - it renders table array part compactly: expect (f {"one", "two", "five"}). - to_be '{one,two,five}' + to_be '{one,two,five}' - it renders a table dictionary part compactly: expect (f { one = true, two = 2, three = {3}}). - to_be '{one=true,three={3},two=2}' + to_be '{one=true,three={3},two=2}' - it renders table keys in table.sort order: expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }). - to_be '{five=1,four=2,one=3,three=4,two=5}' + to_be '{five=1,four=2,one=3,three=4,two=5}' - it renders keys with invalid symbol names compactly: expect (f { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }). - to_be '{?=1,[]=1,_=0,a-key=1,word=0}' + to_be '{?=1,[]=1,_=0,a-key=1,word=0}' diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index b3f4f3e..9f422ae 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -1,14 +1,14 @@ before: - base_module = "string" - this_module = "std.string" - global_table = "_G" + 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", "render", "rtrim", "split", - "tfind", "trim", "wrap" } + extend_base = { "__concat", "__index", + "caps", "chomp", "escape_pattern", "escape_shell", + "finds", "format", "ltrim", + "numbertosi", "ordinal_suffix", "pad", + "prettytostring", "render", "rtrim", "split", + "tfind", "trim", "wrap" } M = require (this_module) getmetatable ("").__concat = M.__concat @@ -22,21 +22,21 @@ specify std.string: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - to_equal {} + to_equal {} - it does not touch the core string table: expect (show_apis {added_to=base_module, by=this_module}). - to_equal {} + 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) + 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 {} + to_equal {} - it does not touch the core string table: expect (show_apis {added_to=base_module, by="std"}). - to_equal {} + to_equal {} - describe ..: - it concatenates string arguments: @@ -45,11 +45,11 @@ specify std.string: - it stringifies non-string arguments: argument = { "a table" } expect (subject .. argument). - to_be (string.format ("%s%s", subject, require "std".tostring (argument))) + to_be (string.format ("%s%s", subject, require "std".tostring (argument))) - it stringifies nil arguments: argument = nil expect (subject .. argument). - to_be (string.format ("%s%s", subject, require "std".tostring (argument))) + to_be (string.format ("%s%s", subject, require "std".tostring (argument))) - it does not perturb the original subject: original = subject newstring = subject .. " concatenate something" @@ -102,7 +102,7 @@ specify std.string: magic = {} meta = "^$()%.[]*+-?" for i = 1, string.len (meta) do - magic[meta:sub (i, i)] = true + magic[meta:sub (i, i)] = true end f = M.escape_pattern @@ -113,10 +113,10 @@ specify std.string: - 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 + 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) @@ -139,10 +139,10 @@ specify std.string: - 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 + 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) @@ -154,8 +154,8 @@ specify std.string: 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") + expect (f ()).to_raise ("string expected") + expect (f {"a table"}).to_raise ("string expected") end @@ -244,8 +244,8 @@ specify std.string: "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)) + m = 10 * (10 ^ n) + table.insert (subject, f (m)) end expect (subject).to_equal (target) - it coerces string arguments to a number: @@ -262,14 +262,14 @@ specify std.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)) + 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: @@ -344,19 +344,19 @@ specify std.string: - it renders an array prettily: a = {"one", "two", "three"} expect (f (a, "")). - to_be '{\n[1] = "one",\n[2] = "two",\n[3] = "three",\n}' + 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}' + 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}' + 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}' + to_be '{\n["?"] = 1,\n["[]"] = 1,\n_ = 0,\n["a-key"] = 1,\nword = 0,\n}' - describe rtrim: @@ -395,7 +395,7 @@ specify std.string: - it falls back to "%s+" when no pattern is given: expect (f (subject)). - to_equal {"first,", "the", "second", "one,", "final", "entry"} + 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: @@ -417,14 +417,14 @@ specify std.string: - 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"} + 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"} + to_equal {"first,", "the", "second", "one,", "final", "entry"} - describe tfind: @@ -484,10 +484,10 @@ specify std.string: - 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." + "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 @@ -496,32 +496,32 @@ specify std.string: - 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." + "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." + "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." + "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." + ".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: @@ -535,6 +535,6 @@ specify std.string: 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") + expect (f ()).to_raise ("string expected") + expect (f {"a table"}).to_raise ("string expected") end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index d513208..5694558 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -1,13 +1,13 @@ before: | - base_module = "table" - this_module = "std.table" - global_table = "_G" + 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" } + 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) @@ -17,19 +17,19 @@ specify std.table: - context by name: - it does not touch the global table: expect (show_apis {added_to=global_table, by=this_module}). - to_equal {} + to_equal {} - it does not touch the core table table: expect (show_apis {added_to=base_module, by=this_module}). - to_equal {} + to_equal {} - it contains apis from the core table table: apis = {} for _, v in ipairs (extend_base) do - if M[v] ~= table[v] then - apis[#apis + 1] = v - end + if M[v] ~= table[v] then + apis[#apis + 1] = v + end end expect (show_apis {from=base_module, not_in=this_module}). - to_contain.a_permutation_of (apis) + to_contain.a_permutation_of (apis) - context via the std module: - it does not touch the global table: @@ -175,17 +175,17 @@ specify std.table: 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 + ["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 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: @@ -283,10 +283,10 @@ specify std.table: 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 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: @@ -330,10 +330,10 @@ specify std.table: - 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" } + 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 @@ -344,10 +344,10 @@ specify std.table: 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 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: @@ -442,9 +442,9 @@ specify std.table: - describe project: - before: l = { - {first = false, second = true, third = true}, - {first = 1, second = 2, third = 3}, - {first = "1st", second = "2nd", third = "3rd"}, + {first = false, second = true, third = true}, + {first = 1, second = 2, third = 3}, + {first = "1st", second = "2nd", third = "3rd"}, } f = M.project @@ -470,11 +470,11 @@ specify std.table: 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 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: @@ -520,8 +520,8 @@ specify std.table: - 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 + target = { 0, 1, 2, 3, 4, 5 } + cmp = function (a, b) return a < b end f = M.sort @@ -545,7 +545,7 @@ specify std.table: expect ({f (t)}).to_equal (t) - it respects __len metamethod: function two (t) - return setmetatable (t, { __len = function () return 2 end}) + 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) From c3c90d6e68f58e6214b61564e556e932a263b06c Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 24 Sep 2017 22:38:51 -0700 Subject: [PATCH 689/703] maint: modernize formating - single quote marks. * README.md: Document single quote mark style. * lib/std/_base.lua, lib/std/debug.lua, lib/std/debug_init/init.lua, lib/std/init.lua, lib/std/io.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua, specs/spec_helper.lua: Use single quote marks whenever possible. * specs/debug_spec.yaml, specs/io_spec.yaml, specs/math_spec.yaml, specs/package_spec.yaml, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Use single quote marks wherever possible in Lua code. Signed-off-by: Gary V. Vaughan --- README.md | 3 + lib/std/_base.lua | 70 ++++---- lib/std/debug.lua | 54 +++--- lib/std/debug_init/init.lua | 2 +- lib/std/init.lua | 84 ++++----- lib/std/io.lua | 96 +++++------ lib/std/math.lua | 10 +- lib/std/package.lua | 58 +++---- lib/std/string.lua | 194 ++++++++++----------- lib/std/table.lua | 96 +++++------ specs/debug_spec.yaml | 230 ++++++++++++------------- specs/io_spec.yaml | 258 ++++++++++++++-------------- specs/math_spec.yaml | 16 +- specs/package_spec.yaml | 148 ++++++++-------- specs/spec_helper.lua | 120 ++++++------- specs/std_spec.yaml | 222 ++++++++++++------------ specs/string_spec.yaml | 332 ++++++++++++++++++------------------ specs/table_spec.yaml | 266 ++++++++++++++--------------- 18 files changed, 1131 insertions(+), 1128 deletions(-) diff --git a/README.md b/README.md index 1f020ac..012ad25 100644 --- a/README.md +++ b/README.md @@ -79,4 +79,7 @@ points when proposing changes: 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. + [issues]: http://github.com/lua-stdlib/lua-stdlib/issues diff --git a/lib/std/_base.lua b/lib/std/_base.lua index 252f277..d4ba92d 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -22,7 +22,7 @@ local _ENV = _ENV -local dirsep = string.match (package.config, "^(%S+)\n") +local dirsep = string.match (package.config, '^(%S+)\n') local error = error local getfenv = getfenv or false local getmetatable = getmetatable @@ -60,7 +60,7 @@ local table_unpack = table.unpack or unpack --[[ ================== ]]-- -local _DEBUG = require "std.debug_init"._DEBUG +local _DEBUG = require 'std.debug_init'._DEBUG local strict, typecheck do @@ -69,7 +69,7 @@ do -- Unless strict was disabled (`_DEBUG = false`), or that module is not -- available, check for use of undeclared variables in this module... if _DEBUG.strict then - ok, strict = pcall (require, "strict") + ok, strict = pcall (require, 'strict') if ok then _ENV = strict {} else @@ -82,7 +82,7 @@ do -- Unless strict was disabled (`_DEBUG = false`), or that module is not -- available, check for use of undeclared variables in this module... if _DEBUG.argcheck then - ok, typecheck = pcall (require, "typecheck") + ok, typecheck = pcall (require, 'typecheck') if not ok then -- ...otherwise, the strict function is not available at all! _DEBUG.argcheck = false @@ -122,14 +122,14 @@ local _pairs = pairs -- Respect __pairs metamethod, even in Lua 5.1. local function pairs (t) - return (getmetamethod (t, "__pairs") or _pairs) (t) + return (getmetamethod (t, '__pairs') or _pairs) (t) end 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 + if type (k) == 'number' and k > n then n = k end end return n end @@ -145,7 +145,7 @@ local function argerror (name, i, extramsg, level) level = level or 1 local s = string_format ("bad argument #%d to '%s'", i, name) if extramsg ~= nil then - s = s .. " (" .. extramsg .. ")" + s = s .. ' (' .. extramsg .. ')' end error (s, level + 1) end @@ -153,10 +153,10 @@ end -- No need to recurse because functables are second class citizens in -- Lua: --- func=function () print "called" end --- func() --> "called" +-- func=function () print 'called' end +-- func() --> 'called' -- functable=setmetatable ({}, {__call=func}) --- functable() --> "called" +-- functable() --> 'called' -- nested=setmetatable ({}, {__call=functable}) -- nested() -- --> stdin:1: attempt to call a table value (global 'd') @@ -164,7 +164,7 @@ end -- --> stdin:1: in main chunk -- --> [C]: in ? local function callable (x) - if type (x) == "function" then return x end + if type (x) == 'function' then return x end return (getmetatable (x) or {}).__call end @@ -204,7 +204,7 @@ end local function escape_pattern (s) - return (s:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")) + return (s:gsub ('[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) end @@ -212,20 +212,20 @@ local function _getfenv (fn) fn = fn or 1 -- Unwrap functable: - if type (fn) == "table" then + if type (fn) == 'table' then fn = fn.call or (getmetatable (fn) or {}).__call end if getfenv then - if type (fn) == "number" then fn = fn + 1 end + if type (fn) == 'number' then fn = fn + 1 end -- Stack frame count is critical here, so ensure we don't optimise one -- away in LuaJIT... return getfenv (fn), nil else - if type (fn) == "number" then - fn = debug_getinfo (fn + 1, "f").func + if type (fn) == 'number' then + fn = debug_getinfo (fn + 1, 'f').func end local name, env @@ -250,17 +250,17 @@ end -- Sort numbers first then asciibetically local function keysort (a, b) - if type (a) == "number" then - return type (b) ~= "number" or a < b + if type (a) == 'number' then + return type (b) ~= 'number' or a < b else - return type (b) ~= "number" and tostring (a) < tostring (b) + return type (b) ~= 'number' and tostring (a) < tostring (b) end end local function leaves (it, tr) local function visit (n) - if type (n) == "table" then + if type (n) == 'table' then for _, v in it (n) do visit (v) end @@ -279,22 +279,22 @@ end local pack = table_pack or function (...) - return {n = select ("#", ...), ...} + return {n = select ('#', ...), ...} end local fallbacks = { __index = { - open = function (x) return "{" end, - close = function (x) return "}" end, + open = function (x) return '{' end, + close = function (x) return '}' end, elem = tostring, - pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. "=" .. vstr end, + pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. '=' .. vstr end, sep = function (x, kp, vp, kn, vn) - return kp ~= nil and kn ~= nil and "," or "" + return kp ~= nil and kn ~= nil and ',' or '' end, sort = function (keys) return keys end, term = function (x) - return type (x) ~= "table" or getmetamethod (x, "__tostring") + return type (x) ~= 'table' or getmetamethod (x, '__tostring') end, }, } @@ -353,7 +353,7 @@ end local function _setfenv (fn, env) -- Unwrap functable: - if type (fn) == "table" then + if type (fn) == 'table' then fn = fn.call or (getmetatable (fn) or {}).__call end @@ -380,11 +380,11 @@ end local function split (s, sep) local r, patt = {} - if sep == "" then - patt = "(.)" - table_insert (r, "") + if sep == '' then + patt = '(.)' + table_insert (r, '') else - patt = "(.-)" .. (sep or "%s+") + patt = '(.-)' .. (sep or '%s+') end local b, slen = 0, len (s) while b <= slen do @@ -398,10 +398,10 @@ end local tostring_vtable = { pair = function (x, kp, vp, k, v, kstr, vstr) - if k == 1 or type (k) == "number" and k -1 == kp then + if k == 1 or type (k) == 'number' and k -1 == kp then return vstr end - return kstr .. "=" .. vstr + return kstr .. '=' .. vstr end, -- need to sort numeric keys to be able to skip printing them. @@ -423,9 +423,9 @@ local tostring_vtable = { -- element with an immediately following nil valued element, which is -- non-deterministic for non-sequence tables. len = function (x) - local m = getmetamethod (x, "__len") + local m = getmetamethod (x, '__len') if m then return m (x) end - if type (x) ~= "table" then return #x end + if type (x) ~= 'table' then return #x end local n = #x for i = 1, n do diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 6aee1f8..b12316a 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -5,7 +5,7 @@ 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" + local debug = require 'std.debug' @corelibrary std.debug ]] @@ -21,7 +21,7 @@ local math_max = math.max local table_concat = table.concat -local _ = require "std._base" +local _ = require 'std._base' local _DEBUG = _._DEBUG local _getfenv = _.debug.getfenv @@ -55,22 +55,22 @@ _ = nil -- value causes deprecated APIs not to be defined at all -- @tfield[opt=1] int level debugging level -- @tfield[opt=true] boolean strict enforce strict variable declaration --- before use **in stdlib internals** (if `require "strict"` works) +-- before use **in stdlib internals** (if `require 'strict'` works) -- @usage -- _DEBUG = { argcheck = false, level = 9, strict = false } local function say (n, ...) local level, argt = n, {...} - if type (n) ~= "number" then + if type (n) ~= 'number' then level, argt = 1, {n, ...} end if _DEBUG.level ~= math_huge and - ((type (_DEBUG.level) == "number" and _DEBUG.level >= level) or level <= 1) + ((type (_DEBUG.level) == 'number' and _DEBUG.level >= level) or level <= 1) then local t = {} for k, v in _pairs (argt) do t[k] = _tostring (v) end - io_stderr:write (table_concat (t, "\t") .. "\n") + io_stderr:write (table_concat (t, '\t') .. '\n') end end @@ -79,35 +79,35 @@ local level = 0 local function trace (event) local t = debug.getinfo (3) - local s = " >>> " - for i = 1, level do s = s .. " " end + 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 .. " " + s = s .. t.short_src .. ':' .. t.currentline .. ' ' end t = debug.getinfo (2) - if event == "call" then + if event == 'call' then level = level + 1 else level = math_max (level - 1, 0) end - if t.what == "main" then - if event == "call" then - s = s .. "begin " .. t.short_src + if t.what == 'main' then + if event == 'call' then + s = s .. 'begin ' .. t.short_src else - s = s .. "end " .. t.short_src + s = s .. 'end ' .. t.short_src end - elseif t.what == "Lua" then - s = s .. event .. " " .. (t.name or "(Lua)") .. " <" .. - t.linedefined .. ":" .. t.short_src .. ">" + 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 .. "]" + s = s .. event .. ' ' .. (t.name or '(C)') .. ' [' .. t.what .. ']' end - io_stderr:write (s .. "\n") + io_stderr:write (s .. '\n') end -- Set hooks according to _DEBUG -if type (_DEBUG) == "table" and _DEBUG.call then - debug.sethook (trace, "cr") +if type (_DEBUG) == 'table' and _DEBUG.call then + debug.sethook (trace, 'cr') end @@ -142,20 +142,20 @@ local M = { -- @int[opt=1] n debugging level, smaller is higher priority -- @param ... objects to print (as for print) -- @usage - -- local _DEBUG = require "std.debug_init"._DEBUG + -- local _DEBUG = require 'std.debug_init'._DEBUG -- _DEBUG.level = 3 - -- say (2, "_DEBUG table contents:", _DEBUG) + -- say (2, '_DEBUG table contents:', _DEBUG) say = say, --- Trace function calls. - -- Use as debug.sethook (trace, "cr"), which is done automatically + -- Use as debug.sethook (trace, 'cr'), which is done automatically -- when `_DEBUG.call` is set. -- Based on test/trace-calls.lua from the Lua distribution. -- @function trace -- @string event event causing the call -- @usage -- _DEBUG = { call = true } - -- local debug = require "std.debug" + -- local debug = require 'std.debug' trace = trace, } @@ -167,8 +167,8 @@ local M = { -- @function __call -- @see say -- @usage --- local debug = require "std.debug" --- debug "oh noes!" +-- local debug = require 'std.debug' +-- debug 'oh noes!' local metatable = { __call = function (self, ...) M.say (1, ...) diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index 66d1cea..18ec812 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -12,7 +12,7 @@ local function choose (t) t[k] = v.fast elseif _DEBUG == nil then t[k] = v.default - elseif type(_DEBUG) ~= "table" then + elseif type(_DEBUG) ~= 'table' then t[k] = v.safe elseif _DEBUG[k] ~= nil then t[k] = _DEBUG[k] diff --git a/lib/std/init.lua b/lib/std/init.lua index d163f10..73bc703 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -29,7 +29,7 @@ local string_format = string.format local string_match = string.match -local _ = require "std._base" +local _ = require 'std._base' local _ipairs = _.ipairs local _pairs = _.pairs @@ -57,7 +57,7 @@ local M local function _assert (expect, fmt, arg1, ...) - local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or "" + local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or '' return expect or error (msg, 2) end @@ -74,7 +74,7 @@ end local function eval (s) - return loadstring ("return " .. s)() + return loadstring ('return ' .. s)() end @@ -90,7 +90,7 @@ end local function npairs (t) - local m = getmetamethod (t, "__len") + local m = getmetamethod (t, '__len') local i, n = 0, m and m(t) or maxn (t) return function (t) i = i + 1 @@ -116,7 +116,7 @@ end local function rnpairs (t) - local m = getmetamethod (t, "__len") + local m = getmetamethod (t, '__len') local oob = (m and m (t) or maxn (t)) + 1 return function (t, n) @@ -129,7 +129,7 @@ end local vconvert = setmetatable ({ - string = function (x) return split (x, "%.") end, + string = function (x) return split (x, '%.') end, number = function (x) return {x} end, table = function (x) return x end, }, { @@ -146,18 +146,18 @@ end local function _require (module, min, too_big, pattern) - pattern = pattern or "([%.%d]+)%D*$" + 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 s, m = '', require (module) + if type (m) == 'table' then s = tostring (m.version or m._VERSION or '') end local v = string_match (s, pattern) or 0 if min then _assert (vcompare (v, min) >= 0, "require '" .. module .. - "' with at least version " .. min .. ", but found version " .. v) + "' 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) + "' with version less than " .. too_big .. ', but found version ' .. v) end return m end @@ -170,7 +170,7 @@ end local function X (decl, fn) - return argscheck and argscheck ("std." .. decl, fn) or fn + return argscheck and argscheck ('std.' .. decl, fn) or fn end M = { @@ -184,13 +184,13 @@ M = { --- Enhance core `assert` to also allow formatted arguments. -- @function assert -- @param expect expression, expected to be *truthy* - -- @string[opt=""] f format string + -- @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), + -- 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 @@ -198,8 +198,8 @@ M = { -- @return result of evaluating `s` -- @usage -- --> 2 - -- std.eval "math.min (2, 10)" - eval = X ("eval (string)", eval), + -- 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 @@ -211,8 +211,8 @@ M = { -- @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), + -- clone = std.getmetamethod (std.object.prototype, '__call') + getmetamethod = X ('getmetamethod (?any, string)', getmetamethod), --- Enhance core `tostring` to render table contents as a string. -- @function tostring @@ -220,8 +220,8 @@ M = { -- @treturn string compact string rendering of *x* -- @usage -- -- {1=baz,foo=bar} - -- print (std.tostring {foo="bar","baz"}) - tostring = X ("tostring (?any)", _tostring), + -- print (std.tostring {foo='bar','baz'}) + tostring = X ('tostring (?any)', _tostring), --- Module Functions @@ -235,11 +235,11 @@ M = { -- @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*$"`) + -- `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), + -- -- 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 @@ -258,8 +258,8 @@ M = { -- --> bar -- --> baz -- --> 5 - -- std.functional.map (print, std.ielems, {"foo", "bar", [4]="baz", d=5}) - elems = X ("elems (table)", elems), + -- 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. -- @@ -277,8 +277,8 @@ M = { -- @usage -- --> foo -- --> bar - -- std.functional.map (print, std.ielems, {"foo", "bar", [4]="baz", d=5}) - ielems = X ("ielems (table)", ielems), + -- 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. -- @@ -301,8 +301,8 @@ M = { -- @usage -- --> 1 foo -- --> 2 bar - -- std.functional.map (print, std.ipairs, {"foo", "bar", [4]="baz", d=5}) - ipairs = X ("ipairs (table)", _ipairs), + -- 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*. @@ -317,8 +317,8 @@ M = { -- --> 2 bar -- --> 3 nil -- --> 4 baz - -- std.functional.map (print, std.npairs, {"foo", "bar", [4]="baz", d=5}) - npairs = X ("npairs (table)", npairs), + -- 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 @@ -333,8 +333,8 @@ M = { -- --> 2 bar -- --> 4 baz -- --> d 5 - -- std.functional.map (print, std.pairs, {"foo", "bar", [4]="baz", d=5}) - pairs = X ("pairs (table)", _pairs), + -- 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 @@ -349,8 +349,8 @@ M = { -- @usage -- --> 2 bar -- --> 1 foo - -- std.functional.map (print, std.ripairs, {"foo", "bar", [4]="baz", d=5}) - ripairs = X ("ripairs (table)", ripairs), + -- 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 @@ -366,8 +366,8 @@ M = { -- --> 3 nil -- --> 2 bar -- --> 1 foo - -- std.functional.map (print, std.rnpairs, {"foo", "bar", [4]="baz", d=5}) - rnpairs = X ("rnpairs (table)", rnpairs), + -- std.functional.map (print, std.rnpairs, {'foo', 'bar', [4]='baz', d=5}) + rnpairs = X ('rnpairs (table)', rnpairs), } @@ -383,10 +383,10 @@ return setmetatable (M, { -- @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 std = require 'std' -- local Object = std.object.prototype __index = function (self, name) - local ok, t = pcall (require, "std." .. name) + local ok, t = pcall (require, 'std.' .. name) if ok then rawset (self, name, t) return t diff --git a/lib/std/io.lua b/lib/std/io.lua index 254b108..52c3f00 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -5,7 +5,7 @@ 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" + local io = require 'std.io' @corelibrary std.io ]] @@ -33,7 +33,7 @@ local table_concat = table.concat local table_insert = table.insert -local _ = require "std._base" +local _ = require 'std._base' local _ipairs = _.ipairs local _tostring = _.tostring @@ -63,7 +63,7 @@ local M local function input_handle (h) if h == nil then return io_input () - elseif type (h) == "string" then + elseif type (h) == 'string' then return io_open (h) end return h @@ -72,10 +72,10 @@ 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 == nil then argerror ('std.io.slurp', 1, err, 2) end if h then - local s = h:read ("*a") + local s = h:read ('*a') h:close () return s end @@ -84,7 +84,7 @@ end local function readlines (file) local h, err = input_handle (file) - if h == nil then argerror ("std.io.readlines", 1, err, 2) end + if h == nil then argerror ('std.io.readlines', 1, err, 2) end local l = {} for line in h:lines () do @@ -96,23 +96,23 @@ end local function writelines (h, ...) - if io_type (h) ~= "file" then - io_write (h, "\n") + if io_type (h) ~= 'file' then + io_write (h, '\n') h = io_output () end for v in leaves (_ipairs, {...}) do - h:write (v, "\n") + h:write (v, '\n') end end local function process_files (fn) - -- N.B. "arg" below refers to the global array of command-line args + -- N.B. 'arg' below refers to the global array of command-line args if len (arg) == 0 then - table_insert (arg, "-") + table_insert (arg, '-') end for i, v in _ipairs (arg) do - if v == "-" then + if v == '-' then io_input (io_stdin) else io_input (v) @@ -123,26 +123,26 @@ end local function warnfmt (msg, ...) - local prefix = "" - local prog = rawget (_G, "prog") or {} - local opts = rawget (_G, "opts") or {} + local prefix = '' + local prog = rawget (_G, 'prog') or {} + local opts = rawget (_G, 'opts') or {} if prog.name then - prefix = prog.name .. ":" + prefix = prog.name .. ':' if prog.line then - prefix = prefix .. _tostring (prog.line) .. ":" + prefix = prefix .. _tostring (prog.line) .. ':' end elseif prog.file then - prefix = prog.file .. ":" + prefix = prog.file .. ':' if prog.line then - prefix = prefix .. _tostring (prog.line) .. ":" + prefix = prefix .. _tostring (prog.line) .. ':' end elseif opts.program then - prefix = opts.program .. ":" + prefix = opts.program .. ':' if opts.line then - prefix = prefix .. _tostring (opts.line) .. ":" + prefix = prefix .. _tostring (opts.line) .. ':' end end - if #prefix > 0 then prefix = prefix .. " " end + if #prefix > 0 then prefix = prefix .. ' ' end return prefix .. string_format (msg, ...) end @@ -159,7 +159,7 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.io." .. decl, fn) or fn + return argscheck and argscheck ('std.io.' .. decl, fn) or fn end @@ -175,8 +175,8 @@ M = { -- @param ... additional arguments to plug format string specifiers -- @see warn -- @usage - -- die ("oh noes! (%s)", tostring (obj)) - die = X ("die (string, [any...])", function (...) + -- die ('oh noes! (%s)', tostring (obj)) + die = X ('die (string, [any...])', function (...) error (warnfmt (...), 0) end), @@ -190,13 +190,13 @@ M = { -- @param ... additional arguments to plug format string specifiers -- @see die -- @usage - -- local OptionParser = require "std.optparse" - -- local parser = OptionParser "eg 0\nUsage: eg\n" + -- 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!" + -- require 'std.io'.warn 'oh noes!' -- end - warn = X ("warn (string, [any...])", warn), + warn = X ('warn (string, [any...])', warn), --- Path Functions @@ -208,9 +208,9 @@ M = { -- @return path without trailing separator -- @see catfile -- @usage - -- dirpath = catdir ("", "absolute", "directory") - catdir = X ("catdir (string...)", function (...) - return (table_concat ({...}, dirsep):gsub("^$", dirsep)) + -- dirpath = catdir ('', 'absolute', 'directory') + catdir = X ('catdir (string...)', function (...) + return (table_concat ({...}, dirsep):gsub('^$', dirsep)) end), --- Concatenate one or more directories and a filename into a path. @@ -220,8 +220,8 @@ M = { -- @see catdir -- @see splitdir -- @usage - -- filepath = catfile ("relative", "path", "filename") - catfile = X ("catfile (string...)", catfile), + -- filepath = catfile ('relative', 'path', 'filename') + catfile = X ('catfile (string...)', catfile), --- Remove the last dirsep delimited element from a path. -- @function dirname @@ -229,20 +229,20 @@ M = { -- @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 (path:gsub (catfile ("", "[^", "]*$"), "")) + -- dir = dirname '/base/subdir/filename' + dirname = X ('dirname (string)', function (path) + return (path:gsub (catfile ('', '[^', ']*$'), '')) end), --- Split a directory path into components. - -- Empty components are retained: the root directory becomes `{"", ""}`. + -- 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) + splitdir = X ('splitdir (string)', function (path) return split (path, dirsep) end), @@ -261,9 +261,9 @@ M = { -- @usage -- #! /usr/bin/env lua -- -- minimal cat command - -- local io = require "std.io" + -- local io = require 'std.io' -- io.process_files (function () io.write (io.slurp ()) end) - process_files = X ("process_files (function)", process_files), + 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. @@ -272,8 +272,8 @@ M = { -- 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), + -- list = readlines '/etc/passwd' + readlines = X ('readlines (?file|string)', readlines), --- Perform a shell command and return its output. -- @function shell @@ -282,7 +282,7 @@ M = { -- @see os.execute -- @usage -- users = shell [[cat /etc/passwd | awk -F: '{print $1;}']] - shell = X ("shell (string)", function (c) return slurp (io_popen (c)) end), + shell = X ('shell (string)', function (c) return slurp (io_popen (c)) end), --- Slurp a file handle. -- @function slurp @@ -292,7 +292,7 @@ M = { -- @see process_files -- @usage -- contents = slurp (filename) - slurp = X ("slurp (?file|string)", slurp), + slurp = X ('slurp (?file|string)', slurp), --- Write values adding a newline after each. -- @function writelines @@ -300,8 +300,8 @@ M = { -- 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), + -- writelines (io.stdout, 'first line', 'next line') + writelines = X ('writelines (?file|string|number, [string|number...])', writelines), } @@ -318,6 +318,6 @@ return merge (M, io) -- @int i argument number of *filename* -- @usage -- local fileprocessor = function (filename, i) --- io.write (tostring (i) .. ":\n===\n" .. io.slurp (filename) .. "\n") +-- io.write (tostring (i) .. ':\n===\n' .. io.slurp (filename) .. '\n') -- end -- io.process_files (fileprocessor) diff --git a/lib/std/math.lua b/lib/std/math.lua index 0ed214f..7bf3a1e 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -5,7 +5,7 @@ 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" + local math = require 'std.math' @corelibrary std.math ]] @@ -16,7 +16,7 @@ local math = math local math_floor = math.floor -local _ = require "std._base" +local _ = require 'std._base' local argscheck = _.typecheck and _.typecheck.argscheck local merge = _.base.merge @@ -57,7 +57,7 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.math." .. decl, fn) or fn + return argscheck and argscheck ('std.math.' .. decl, fn) or fn end @@ -72,7 +72,7 @@ M = { -- @treturn number `n` truncated to `p` decimal places -- @usage -- tenths = floor (magnitude, 1) - floor = X ("floor (number, ?int)", floor), + floor = X ('floor (number, ?int)', floor), --- Round a number to a given number of decimal places. -- @function round @@ -81,7 +81,7 @@ M = { -- @treturn number `n` rounded to `p` decimal places -- @usage -- roughly = round (exactly, 2) - round = X ("round (number, ?int)", round), + round = X ('round (number, ?int)', round), } diff --git a/lib/std/package.lua b/lib/std/package.lua index f268b04..1cb6da6 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -5,14 +5,14 @@ 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" + 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.path_mark`, for easy addition of new paths. For example, instead of all this: - lib = std.io.catfile (".", "lib", package.path_mark .. ".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... @@ -30,7 +30,7 @@ You can now write just: - package.path = package.normalize ("./lib/?.lua", package.path) + package.path = package.normalize ('./lib/?.lua', package.path) @corelibrary std.package ]] @@ -47,7 +47,7 @@ local table_remove = table.remove local table_unpack = table.unpack or unpack -local _ = require "std._base" +local _ = require 'std._base' local argscheck = _.typecheck and _.typecheck.argscheck local catfile = _.io.catfile @@ -77,17 +77,17 @@ _ = nil -- @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 dirsep, pathsep, path_mark, execdir, igmark = - string_match (package_config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") + string_match (package_config, '^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)') local function pathsub (path) - return path:gsub ("%%?.", function (capture) - if capture == "?" then + return path:gsub ('%%?.', function (capture) + if capture == '?' then return path_mark - elseif capture == "/" then + elseif capture == '/' then return dirsep else - return capture:gsub ("^%%", "", 1) + return capture:gsub ('^%%', '', 1) end end) end @@ -108,30 +108,30 @@ local function normalize (...) local i, paths, pathstrings = 1, {}, table_concat ({...}, pathsep) for _, path in ipairs (split (pathstrings, pathsep)) do path = pathsub (path): - gsub (catfile ("^[^", "]"), catfile (".", "%0")): - gsub (catfile ("", "%.", ""), dirsep): - gsub (catfile ("", "%.$"), ""): - gsub (catfile ("^%.", "%..", ""), catfile ("..", "")): - gsub (catfile ("", "$"), "") + gsub (catfile ('^[^', ']'), catfile ('.', '%0')): + gsub (catfile ('', '%.', ''), dirsep): + gsub (catfile ('', '%.$'), ''): + gsub (catfile ('^%.', '%..', ''), catfile ('..', '')): + gsub (catfile ('', '$'), '') -- Carefully remove redundant /foo/../ matches. repeat local again = false - path = path:gsub (catfile ("", "([^", "]+)", "%.%.", ""), + path = path:gsub (catfile ('', '([^', ']+)', '%.%.', ''), function (dir1) - if dir1 == ".." then -- don't remove /../../ - return catfile ("", "..", "..", "") + if dir1 == '..' then -- don't remove /../../ + return catfile ('', '..', '..', '') else again = true return dirsep end - end):gsub (catfile ("", "([^", "]+)", "%.%.$"), + end):gsub (catfile ('', '([^', ']+)', '%.%.$'), function (dir1) - if dir1 == ".." then -- don't remove /../.. - return catfile ("", "..", "..") + if dir1 == '..' then -- don't remove /../.. + return catfile ('', '..', '..') else again = true - return "" + return '' end end) until again == false @@ -175,7 +175,7 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.package." .. decl, fn) or fn + return argscheck and argscheck ('std.package.' .. decl, fn) or fn end @@ -192,8 +192,8 @@ local M = { -- @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), + -- 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 @@ -203,8 +203,8 @@ local M = { -- @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), + -- 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 @@ -214,7 +214,7 @@ local M = { -- @return nil, or first non-nil returned by *callback* -- @usage -- mappath (package.path, searcherfn, transformfn) - mappath = X ("mappath (string, function, [any...])", mappath), + mappath = X ('mappath (string, function, [any...])', mappath), --- Normalize a path list. -- Removing redundant `.` and `..` directories, and keep only the first @@ -227,7 +227,7 @@ local M = { -- @treturn string a single normalized `pathsep` delimited paths string -- @usage -- package.path = normalize (user_paths, sys_paths, package.path) - normalize = X ("normalize (string...)", normalize), + normalize = X ('normalize (string...)', normalize), --- Remove any element from a `package.path` like string of paths. -- @function remove @@ -237,7 +237,7 @@ local M = { -- @treturn string a new string with given element removed -- @usage -- package.path = remove (package.path) - remove = X ("remove (string, ?int)", remove), + remove = X ('remove (string, ?int)', remove), } diff --git a/lib/std/string.lua b/lib/std/string.lua index 6e77777..ce036fb 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -5,7 +5,7 @@ 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" + local string = require 'std.string' @corelibrary std.string ]] @@ -26,7 +26,7 @@ local table_insert = table.insert local string_format = string.format -local _ = require "std._base" +local _ = require 'std._base' local _tostring = _.tostring local argscheck = _.typecheck and _.std.typecheck.argscheck @@ -58,7 +58,7 @@ end local function __index (s, i) - if type (i) == "number" then + if type (i) == 'number' then return s:sub (i, i) else -- Fall back to module metamethods @@ -99,12 +99,12 @@ end local function caps (s) - return (s:gsub ("(%w)([%w]*)", function (l, ls) return l:upper () .. ls end)) + return (s:gsub ('(%w)([%w]*)', function (l, ls) return l:upper () .. ls end)) end local function escape_shell (s) - return (s:gsub ("([ %(%)%\\%[%]\"'])", "\\%1")) + return (s:gsub ('([ %(%)%\\%[%]\'"])', '\\%1')) end @@ -112,19 +112,19 @@ local function ordinal_suffix (n) n = math_abs (n) % 100 local d = n % 10 if d == 1 and n ~= 11 then - return "st" + return 'st' elseif d == 2 and n ~= 12 then - return "nd" + return 'nd' elseif d == 3 and n ~= 13 then - return "rd" + return 'rd' else - return "th" + return 'th' end end local function pad (s, w, p) - p = string.rep (p or " ", math_abs (w)) + p = string.rep (p or ' ', math_abs (w)) if w < 0 then return string.sub (p .. s, w) end @@ -137,22 +137,22 @@ local function wrap (s, w, ind, ind1) 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) } + '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 + while len (s[j]) > 0 and s[j] ~= ' ' and j > i do j = j - 1 end local ni = j + 1 - while s[j] == " " do + while s[j] == ' ' do j = j - 1 end table_insert (r, s:sub (i, j)) i = ni if i < lens then - table_insert (r, "\n" .. string.rep (" ", ind)) + table_insert (r, '\n' .. string.rep (' ', ind)) lstart = ind end end @@ -162,73 +162,73 @@ 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" + [-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 = t:find(".(.%...)e(.+)") + local t = _format ('% #.2e', n) + local _, _, m, e = t:find('.(.%...)e(.+)') local man, exp = tonumber (m), tonumber (e) local siexp = math_floor (exp / 3) local shift = exp - siexp * 3 - local s = SIprefix[siexp] or "e" .. tostring (siexp) + local s = SIprefix[siexp] or 'e' .. tostring (siexp) man = man * (10 ^ shift) - return _format ("%0.f", man) .. s + return _format ('%0.f', man) .. s end local function prettytostring (x, indent, spacing) - indent = indent or "\t" - spacing = spacing or "" + indent = indent or '\t' + spacing = spacing or '' return render (x, { open = function () - local s = spacing .. "{" + local s = spacing .. '{' spacing = spacing .. indent return s end, close = function () - spacing = string.gsub (spacing, indent .. "$", "") - return spacing .. "}" + spacing = string.gsub (spacing, indent .. '$', '') + return spacing .. '}' end, elem = function (x) - if type (x) ~= "string" then return tostring (x) end - return string_format ("%q", x) + if type (x) ~= 'string' then return tostring (x) end + return string_format ('%q', x) end, pair = function (x, _, _, k, v, kstr, vstr) local type_k = type (k) local s = spacing - if type_k ~= "string" or k:match "[^%w_]" then - s = s .. "[" - if type_k == "table" then - s = s .. "\n" + if type_k ~= 'string' or k:match '[^%w_]' then + s = s .. '[' + if type_k == 'table' then + s = s .. '\n' end s = s .. kstr - if type_k == "table" then - s = s .. "\n" + if type_k == 'table' then + s = s .. '\n' end - s = s .. "]" + s = s .. ']' else s = s .. k end - s = s .. " =" - if type (v) == "table" then - s = s .. "\n" + s = s .. ' =' + if type (v) == 'table' then + s = s .. '\n' else - s = s .. " " + s = s .. ' ' end s = s .. vstr return s end, sep = function (_, k) - local s = "\n" + local s = '\n' if k then - s = "," .. s + s = ',' .. s end return s end, @@ -239,8 +239,8 @@ end local function trim (s, r) - r = r or "%s+" - return (s:gsub ("^" .. r, ""):gsub (r .. "$", "")) + r = r or '%s+' + return (s:gsub ('^' .. r, ''):gsub (r .. '$', '')) end @@ -251,7 +251,7 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.string." .. decl, fn) or fn + return argscheck and argscheck ('std.string.' .. decl, fn) or fn end M = { @@ -264,8 +264,8 @@ M = { -- @param o object to stringify and concatenate -- @return s .. tostring (o) -- @usage - -- local string = setmetatable ("", require "std.string") - -- concatenated = "foo" .. {"bar"} + -- local string = setmetatable ('', require 'std.string') + -- concatenated = 'foo' .. {'bar'} __concat = __concat, --- String subscript operation. @@ -275,8 +275,8 @@ M = { -- @return `s:sub (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] + -- getmetatable ('').__index = require 'std.string'.__index + -- third = ('12345')[3] __index = __index, @@ -289,7 +289,7 @@ M = { -- @treturn string *s* with each word capitalized -- @usage -- userfullname = caps (input_string) - caps = X ("caps (string)", caps), + caps = X ('caps (string)', caps), --- Remove any final newline from a string. -- @function chomp @@ -297,7 +297,7 @@ M = { -- @treturn string *s* with any single trailing newline removed -- @usage -- line = chomp (line) - chomp = X ("chomp (string)", function (s) return (s:gsub ("\n$", "")) end), + chomp = X ('chomp (string)', function (s) return (s:gsub ('\n$', '')) end), --- Escape a string to be used as a pattern. -- @function escape_pattern @@ -305,7 +305,7 @@ M = { -- @treturn string *s* with active pattern characters escaped -- @usage -- substr = inputstr:match (escape_pattern (literal)) - escape_pattern = X ("escape_pattern (string)", escape_pattern), + 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 @@ -314,8 +314,8 @@ M = { -- @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), + -- os.execute ('echo ' .. escape_shell (outputstr)) + escape_shell = X ('escape_shell (string)', escape_shell), --- Repeatedly `string.find` until target string is exhausted. -- @function finds @@ -326,10 +326,10 @@ M = { -- @return list of `{from, to; capt = {captures}}` -- @see std.string.tfind -- @usage - -- for t in std.elems (finds ("the target string", "%S+")) do + -- 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), + 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. @@ -338,18 +338,18 @@ M = { -- @param[opt] ... arguments to format -- @return formatted string -- @usage - -- print (format "100% stdlib!") - format = X ("format (string, [any...])", format), + -- 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 + -- @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 (s:gsub ("^" .. (r or "%s+"), "")) + -- print ('got: ' .. ltrim (userinput)) + ltrim = X ('ltrim (string, ?string)', function (s, r) + return (s:gsub ('^' .. (r or '%s+'), '')) end), --- Write a number using SI suffixes. @@ -358,17 +358,17 @@ M = { -- @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), + -- 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), + -- 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 @@ -377,21 +377,21 @@ M = { -- @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 + -- @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), + -- 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 + -- @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), + -- print (prettytostring (std, ' ')) + prettytostring = X ('prettytostring (?any, ?string, ?string)', prettytostring), --- Turn tables into strings with recursion detection. -- N.B. Functions calling render should not recurse, or recursion @@ -404,24 +404,24 @@ M = { -- function tostablestring (x) -- return render (x, { -- sort = function (keys) - -- table.sort (keys, lambda "=tostring (_1) < tostring (_2)") + -- table.sort (keys, lambda '=tostring (_1) < tostring (_2)') -- return keys -- end, -- }) -- end - render = X ("render (?any, ?table)", function (x, rendercbs, roots) + render = X ('render (?any, ?table)', function (x, rendercbs, roots) return render (x, rendercbs, roots) end), --- Remove trailing matter from a string. -- @function rtrim -- @string s any string - -- @string[opt="%s+"] r trailing pattern + -- @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 (s:gsub ((r or "%s+") .. "$", "")) + -- print ('got: ' .. rtrim (userinput)) + rtrim = X ('rtrim (string, ?string)', function (s, r) + return (s:gsub ((r or '%s+') .. '$', '')) end), --- Split a string at a given separator. @@ -429,11 +429,11 @@ M = { -- `^$()%.[]*+-?` with a `%` prefix to match a literal character in *s*. -- @function split -- @string s to split - -- @string[opt="%s+"] sep separator pattern + -- @string[opt='%s+'] sep separator pattern -- @return list of strings -- @usage - -- words = split "a very short sentence" - split = X ("split (string, ?string)", split), + -- words = split 'a very short sentence' + split = X ('split (string, ?string)', split), --- Do `string.find`, returning a table of captures. -- @function tfind @@ -446,17 +446,17 @@ M = { -- @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), + -- 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 + -- @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), + -- print ('got: ' .. trim (userinput)) + trim = X ('trim (string, ?string)', trim), --- Wrap a string into a paragraph. -- @function wrap @@ -467,7 +467,7 @@ M = { -- @treturn string *s* wrapped to *w* columns -- @usage -- print (wrap (copyright, 72, 4)) - wrap = X ("wrap (string, ?int, ?int, ?int)", wrap), + wrap = X ('wrap (string, ?int, ?int, ?int)', wrap), } @@ -500,7 +500,7 @@ return merge (M, string) -- @treturn string open table rendering -- @see render -- @usage --- function open (t) return "{" end +-- function open (t) return '{' end --- Signature of @{render} close table callback. @@ -509,7 +509,7 @@ return merge (M, string) -- @treturn string close table rendering -- @see render -- @usage --- function close (t) return "}" end +-- function close (t) return '}' end --- Signature of @{render} element callback. @@ -518,7 +518,7 @@ return merge (M, string) -- @treturn string element rendering -- @see render -- @usage --- function element (e) return require "std".tostring (e) end +-- function element (e) return require 'std'.tostring (e) end --- Signature of @{render} pair callback. @@ -533,7 +533,7 @@ return merge (M, string) -- @treturn string pair rendering -- @see render -- @usage --- function pair (_, _, _, key, value) return key .. "=" .. value end +-- function pair (_, _, _, key, value) return key .. '=' .. value end --- Signature of @{render} separator callback. @@ -545,7 +545,7 @@ return merge (M, string) -- @param fv *t* value following separator, or `nil` for last value -- @treturn string separator rendering -- @usage --- function separator (_, _, _, fk) return fk and "," or "" end +-- function separator (_, _, _, fk) return fk and ',' or '' end --- Signature of @{render} key sorting callback. @@ -562,5 +562,5 @@ return merge (M, string) -- @treturn boolean whether *x* can be rendered by @{elementcb} -- @usage -- function term (x) --- return type (x) ~= "table" or getmetamethod (x, "__tostring") +-- return type (x) ~= 'table' or getmetamethod (x, '__tostring') -- end diff --git a/lib/std/table.lua b/lib/std/table.lua index e8cca56..b402ac8 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -5,7 +5,7 @@ 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" + local table = require 'std.table' @corelibrary std.table ]] @@ -23,7 +23,7 @@ local table_insert = table.insert local table_unpack = table.unpack or unpack -local _ = require "std._base" +local _ = require 'std._base' local _ipairs = _.ipairs local _pairs = _.pairs @@ -52,7 +52,7 @@ local M local function merge_allfields (t, u, map, nometa) - if type (map) ~= "table" then + if type (map) ~= 'table' then map, nometa = nil, map end @@ -69,7 +69,7 @@ end local function merge_namedfields (t, u, keys, nometa) - if type (keys) ~= "table" then + if type (keys) ~= 'table' then keys, nometa = nil, keys end @@ -102,7 +102,7 @@ end 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) + argerror ('std.table.insert', 2, 'position ' .. pos .. ' out of bounds', 2) end table_insert (t, pos, v) return t @@ -158,7 +158,7 @@ local function remove (t, pos) local lent = len (t) pos = pos or lent if pos < math_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) + argerror ('std.table.remove', 2, 'position ' .. pos .. ' out of bounds', 2) end return _remove (t, pos) end @@ -167,7 +167,7 @@ end 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") + local m = getmetamethod (t, '__len') j = m and m (t) or maxn (t) end return table_unpack (t, tonumber (i) or 1, tonumber (j)) @@ -190,7 +190,7 @@ end local function X (decl, fn) - return argscheck and argscheck ("std.table." .. decl, fn) or fn + return argscheck and argscheck ('std.table.' .. decl, fn) or fn end M = { @@ -207,9 +207,9 @@ M = { -- @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), + -- --> {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 @@ -217,16 +217,16 @@ M = { -- @treturn int largest integer key in *t* -- @usage -- --> 42 - -- maxn {"a", b="c", 99, [42]="x", "x", [5]=67} - maxn = X ("maxn (table)", maxn), + -- 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 (("ax1"):find "(%D+)") + -- --> {1, 2, 'ax', n=3} + -- pack (('ax1'):find '(%D+)') pack = pack, --- Enhance core *table.remove* to respect `__len` when *pos* is omitted. @@ -238,9 +238,9 @@ M = { -- @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), + -- 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 @@ -249,7 +249,7 @@ M = { -- @return *t* with keys sorted according to *c* -- @usage -- table.concat (sort (object)) - sort = X ("sort (table, ?function)", sort), + sort = X ('sort (table, ?function)', sort), --- Enhance core *table.unpack* to always unpack up to __len or maxn. -- @function unpack @@ -259,7 +259,7 @@ M = { -- @return ... values of numeric indices of *t* -- @usage -- return unpack (results_table) - unpack = X ("unpack (table, ?int, ?int)", unpack), + unpack = X ('unpack (table, ?int, ?int)', unpack), --- Accessor Functions @@ -275,8 +275,8 @@ M = { -- @see merge -- @see clone_select -- @usage - -- shallowcopy = clone (original, {rename_this = "to_this"}, ":nometa") - clone = X ("clone (table, [table], ?boolean|:nometa)", function (...) + -- shallowcopy = clone (original, {rename_this = 'to_this'}, ':nometa') + clone = X ('clone (table, [table], ?boolean|:nometa)', function (...) return merge_allfields ({}, ...) end), @@ -292,8 +292,8 @@ M = { -- @see clone -- @see merge_select -- @usage - -- partialcopy = clone_select (original, {"this", "and_this"}, true) - clone_select = X ("clone_select (table, [table], ?boolean|:nometa)", + -- 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. @@ -304,8 +304,8 @@ M = { -- @see enpair -- @usage -- --> {a=1, b=2, c=3} - -- depair {{"a", 1}, {"b", 2}, {"c", 3}} - depair = X ("depair (list of lists)", depair), + -- 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. @@ -314,17 +314,17 @@ M = { -- @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), + -- --> {{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), + -- 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 @@ -333,7 +333,7 @@ M = { -- @treturn table table whose unset elements are *x* -- @usage -- t = new (0) - new = X ("new (?any, ?table)", new), + new = X ('new (?any, ?table)', new), --- Project a list of fields from a list of tables. -- @function project @@ -341,9 +341,9 @@ M = { -- @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), + -- --> {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 @@ -352,7 +352,7 @@ M = { -- @usage -- --> 3 -- size {foo = true, bar = true, baz = false} - size = X ("size (table)", size), + size = X ('size (table)', size), --- Make the list of values of a table. -- @function values @@ -360,9 +360,9 @@ M = { -- @treturn table list of values in *t* -- @see keys -- @usage - -- --> {"a", "c", 42} - -- values {"a", b="c", [-1]=42} - values = X ("values (table)", values), + -- --> {'a', 'c', 42} + -- values {'a', b='c', [-1]=42} + values = X ('values (table)', values), --- Mutator Functions @@ -374,8 +374,8 @@ M = { -- @treturn table inverted table `{v=k, ...}` -- @usage -- --> {a=1, b=2, c=3} - -- invert {"a", "b", "c"} - invert = X ("invert (table)", invert), + -- invert {'a', 'b', 'c'} + invert = X ('invert (table)', invert), --- Make the list of keys in table. -- @function keys @@ -384,20 +384,20 @@ M = { -- @see values -- @usage -- globals = keys (_G) - keys = X ("keys (table)", keys), + keys = X ('keys (table)', keys), --- Destructively merge another 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 + -- @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), + -- 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*. -- @@ -406,14 +406,14 @@ M = { -- @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 + -- @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_select (_G, require 'std.debug', {'say'}, false) + merge_select = X ('merge_select (table, table, [table], ?boolean|:nometa)', merge_namedfields), } diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index d1acb83..d2661a5 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -1,9 +1,9 @@ before: | - base_module = "debug" - this_module = "std.debug" - global_table = "_G" + base_module = 'debug' + this_module = 'std.debug' + global_table = '_G' - extend_base = { "getfenv", "setfenv", "say", "trace" } + extend_base = { 'getfenv', 'setfenv', 'say', 'trace' } M = require (this_module) @@ -56,205 +56,205 @@ specify std.debug: - context via the std module: - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). + 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"}). + expect (show_apis {added_to=base_module, by='std'}). to_equal {} - describe debug: - before: | function mkwrap (k, v) - local fmt = "%s" - if type (v) == "string" then fmt = "%q" end - return k, string.format (fmt, require "std".tostring (v)) + local fmt = '%s' + if type (v) == 'string' then fmt = '%q' end + return k, string.format (fmt, require 'std'.tostring (v)) end function mkdebug (debugp, ...) return string.format ([[ _DEBUG = %s - require "std.debug" (%s) + require 'std.debug' (%s) ]], - require "std".tostring (debugp), - table.concat (map (mkwrap, {...}), ", ")) + require 'std'.tostring (debugp), + table.concat (map (mkwrap, {...}), ', ')) end - it does nothing when _DEBUG is disabled: - expect (luaproc (mkdebug (false, "nothing to see here"))). - not_to_contain_error "nothing to see here" + expect (luaproc (mkdebug (false, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' - it writes to stderr when _DEBUG is not set: - expect (luaproc (mkdebug (nil, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mkdebug (nil, 'debugging'))). + to_contain_error 'debugging' - it writes to stderr when _DEBUG is enabled: - expect (luaproc (mkdebug (true, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mkdebug (true, 'debugging'))). + to_contain_error 'debugging' - it writes to stderr when _DEBUG.level is not set: - expect (luaproc (mkdebug ({}, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mkdebug ({}, 'debugging'))). + to_contain_error 'debugging' - it writes to stderr when _DEBUG.level is specified: - expect (luaproc (mkdebug ({level = 0}, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mkdebug ({level = 1}, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mkdebug ({level = 2}, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mkdebug ({level = 0}, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mkdebug ({level = 1}, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mkdebug ({level = 2}, 'debugging'))). + to_contain_error 'debugging' - describe say: - before: | function mkwrap (k, v) - local fmt = "%s" - if type (v) == "string" then fmt = "%q" end - return k, string.format (fmt, require "std".tostring (v)) + local fmt = '%s' + if type (v) == 'string' then fmt = '%q' end + return k, string.format (fmt, require 'std'.tostring (v)) end function mksay (debugp, ...) return string.format ([[ _DEBUG = %s - require "std.debug".say (%s) + require 'std.debug'.say (%s) ]], - require "std".tostring (debugp), - table.concat (map (mkwrap, {...}), ", ")) + require 'std'.tostring (debugp), + table.concat (map (mkwrap, {...}), ', ')) end f = M.say - it uses stdlib tostring: - expect (luaproc [[require "std.debug".say {"debugging"}]]). - to_contain_error (require "std".tostring {"debugging"}) + expect (luaproc [[require 'std.debug'.say {'debugging'}]]). + to_contain_error (require 'std'.tostring {'debugging'}) - context when _DEBUG is disabled: - it does nothing when message level is not set: - expect (luaproc (mksay (false, "nothing to see here"))). - not_to_contain_error "nothing to see here" + expect (luaproc (mksay (false, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' - it does nothing when message is set: - expect (luaproc (mksay (false, -999, "nothing to see here"))). - not_to_contain_error "nothing to see here" - expect (luaproc (mksay (false, 0, "nothing to see here"))). - not_to_contain_error "nothing to see here" - expect (luaproc (mksay (false, 1, "nothing to see here"))). - not_to_contain_error "nothing to see here" - expect (luaproc (mksay (false, 2, "nothing to see here"))). - not_to_contain_error "nothing to see here" - expect (luaproc (mksay (false, 999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + expect (luaproc (mksay (false, -999, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' + expect (luaproc (mksay (false, 0, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' + expect (luaproc (mksay (false, 1, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' + expect (luaproc (mksay (false, 2, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' + expect (luaproc (mksay (false, 999, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' - context when _DEBUG is not set: - it writes to stderr when message level is not set: - expect (luaproc (mksay (nil, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay (nil, 'debugging'))). + to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect (luaproc (mksay (nil, -999, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mksay (nil, 0, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mksay (nil, 1, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay (nil, -999, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mksay (nil, 0, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mksay (nil, 1, 'debugging'))). + to_contain_error 'debugging' - it does nothing when message level is 2 or higher: - expect (luaproc (mksay (nil, 2, "nothing to see here"))). - not_to_contain_error "nothing to see here" - expect (luaproc (mksay (nil, 999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + expect (luaproc (mksay (nil, 2, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' + expect (luaproc (mksay (nil, 999, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' - context when _DEBUG is enabled: - it writes to stderr when message level is not set: - expect (luaproc (mksay (true, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay (true, 'debugging'))). + to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect (luaproc (mksay (true, -999, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mksay (true, 0, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mksay (true, 1, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay (true, -999, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mksay (true, 0, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mksay (true, 1, 'debugging'))). + to_contain_error 'debugging' - it does nothing when message level is 2 or higher: - expect (luaproc (mksay (true, 2, "nothing to see here"))). - not_to_contain_error "nothing to see here" - expect (luaproc (mksay (true, 999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + expect (luaproc (mksay (true, 2, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' + expect (luaproc (mksay (true, 999, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' - context when _DEBUG.level is not set: - it writes to stderr when message level is not set: - expect (luaproc (mksay ({}, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay ({}, 'debugging'))). + to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect (luaproc (mksay ({}, -999, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mksay ({}, 0, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mksay ({}, 1, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay ({}, -999, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mksay ({}, 0, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mksay ({}, 1, 'debugging'))). + to_contain_error 'debugging' - it does nothing when message level is 2 or higher: - expect (luaproc (mksay ({}, 2, "nothing to see here"))). - not_to_contain_error "nothing to see here" - expect (luaproc (mksay ({}, 999, "nothing to see here"))). - not_to_contain_error "nothing to see here" + expect (luaproc (mksay ({}, 2, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' + expect (luaproc (mksay ({}, 999, 'nothing to see here'))). + not_to_contain_error 'nothing to see here' - context when _DEBUG.level is specified: - it writes to stderr when message level is 1 or lower: - expect (luaproc (mksay ({level = 0}, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mksay ({level = 1}, "debugging"))). - to_contain_error "debugging" - expect (luaproc (mksay ({level = 2}, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay ({level = 0}, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mksay ({level = 1}, 'debugging'))). + to_contain_error 'debugging' + expect (luaproc (mksay ({level = 2}, 'debugging'))). + to_contain_error 'debugging' - it does nothing when message level is higher than debug level: - expect (luaproc (mksay ({level = 2}, 3, "nothing to see here"))). - not_to_contain_error "nothing to see here" + expect (luaproc (mksay ({level = 2}, 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 (mksay ({level = 2}, 2, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay ({level = 2}, 2, 'debugging'))). + to_contain_error 'debugging' - it writes to stderr when message level is lower than debug level: - expect (luaproc (mksay ({level = 2}, 1, "debugging"))). - to_contain_error "debugging" + expect (luaproc (mksay ({level = 2}, 1, 'debugging'))). + to_contain_error 'debugging' - describe trace: - before: - f = init (M, this_module, "trace") + f = init (M, this_module, 'trace') - it does nothing when _DEBUG is disabled: expect (luaproc [[ _DEBUG = false - require "std.debug" + require 'std.debug' os.exit (0) - ]]).to_succeed_with "" + ]]).to_succeed_with '' - it does nothing when _DEBUG is not set: expect (luaproc [[ - require "std.debug" + require 'std.debug' os.exit (0) - ]]).to_succeed_with "" + ]]).to_succeed_with '' - it does nothing when _DEBUG is enabled: expect (luaproc [[ _DEBUG = true - require "std.debug" + require 'std.debug' os.exit (0) - ]]).to_succeed_with "" + ]]).to_succeed_with '' - it enables automatically when _DEBUG.call is set: | expect (luaproc [[ _DEBUG = {call = true} - local debug = require "std.debug" + local debug = require 'std.debug' os.exit (1) - ]]).to_fail_while_containing ":3 call exit" + ]]).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") + local debug = require 'std.debug' + debug.sethook (debug.trace, 'cr') os.exit (1) - ]]).to_fail_while_containing ":3 call exit" + ]]).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") + local debug = require 'std.debug' + debug.sethook (debug.trace, 'cr') os.exit (0) - ]]).to_contain_error ":3 call exit" + ]]).to_contain_error ':3 call exit' - it traces lua calls: | expect (luaproc [[ - local debug = require "std.debug" -- line 1 + local debug = require 'std.debug' -- line 1 local function incr (i) return i + 1 end -- line 2 - debug.sethook (debug.trace, "cr") -- line 3 + 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:.*" + ]]).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 debug = require 'std.debug' local function incr (i) return i + 1 end - debug.sethook (debug.trace, "cr") + debug.sethook (debug.trace, 'cr') os.exit (incr (41)) - ]]).to_fail_while_matching ".*:4 call exit %[C%]%s$" + ]]).to_fail_while_matching '.*:4 call exit %[C%]%s$' diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 8497f84..5941122 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -1,13 +1,13 @@ before: | - base_module = "io" - this_module = "std.io" - global_table = "_G" + base_module = 'io' + this_module = 'std.io' + global_table = '_G' - extend_base = { "catdir", "catfile", "die", "dirname", - "process_files", "readlines", "shell", "slurp", - "splitdir", "warn", "writelines" } + extend_base = { 'catdir', 'catfile', 'die', 'dirname', + 'process_files', 'readlines', 'shell', 'slurp', + 'splitdir', 'warn', 'writelines' } - dirsep = string.match (package.config, "^([^\n]+)\n") + dirsep = string.match (package.config, '^([^\n]+)\n') M = require (this_module) @@ -27,10 +27,10 @@ specify std.io: - context via the std module: - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). + 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"}). + expect (show_apis {added_to=base_module, by='std'}). to_equal {} @@ -39,18 +39,18 @@ specify std.io: f = M.catdir - context with bad arguments: - badargs.diagnose (f, "std.io.catdir (string*)") + 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") + 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" + 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)) + 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: @@ -58,28 +58,28 @@ specify std.io: f = M.catfile - context with bad arguments: - badargs.diagnose (f, "std.io.catfile (string*)") + 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") + 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" + 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)) + 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!"]] + script = [[require 'std.io'.die "By 'eck!"]] f = M.die - context with bad arguments: - badargs.diagnose (f, "std.io.die (string, ?any*)") + badargs.diagnose (f, 'std.io.die (string, ?any*)') - it outputs a message to stderr: | expect (luaproc (script)).to_fail_while_matching ": By 'eck!\n" @@ -90,85 +90,85 @@ specify std.io: 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 + 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 + 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 + 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 + 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" } + 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" } + 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" } + 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" } + 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 + 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 + 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) + path = table.concat ({'', 'one', 'two', 'three'}, dirsep) - context with bad arguments: - badargs.diagnose (f, "std.io.dirname (string)") + badargs.diagnose (f, 'std.io.dirname (string)') - it removes final separator and following: - expect (f (path)).to_be (table.concat ({"", "one", "two"}, dirsep)) + expect (f (path)).to_be (table.concat ({'', 'one', 'two'}, dirsep)) - describe process_files: - before: - name = "Makefile" - names = {"LICENSE.md", "Makefile", "README.md"} + name = 'Makefile' + names = {'LICENSE.md', 'Makefile', 'README.md'} ascript = [[ - require "std.io".process_files (function (a) print (a) end) + require 'std.io'.process_files (function (a) print (a) end) ]] lscript = [[ - require "std.io".process_files ("=print (_1)") + require 'std.io'.process_files ('=print (_1)') ]] iscript = [[ - require "std.io".process_files (function (_, i) print (i) end) + require 'std.io'.process_files (function (_, i) print (i) end) ]] catscript = [[ - require "std.io".process_files (function () io.write (io.input ():read "*a") end) + 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)") + 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 { + 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 'io_input' (not-an-existing-file:", -- Lua 5.1 } @@ -176,61 +176,61 @@ specify std.io: } - it defaults to `-` if no arguments were passed: - expect (luaproc (ascript)).to_output "-\n" + expect (luaproc (ascript)).to_output '-\n' - it iterates over arguments with supplied function: - expect (luaproc (ascript, name)).to_output (name .. "\n") + expect (luaproc (ascript, name)).to_output (name .. '\n') expect (luaproc (ascript, names)). - to_output (table.concat (names, "\n") .. "\n") + to_output (table.concat (names, '\n') .. '\n') - it passes argument numbers to supplied function: - expect (luaproc (iscript, names)).to_output "1\n2\n3\n" + 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" + 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" + expect (luaproc (catscript, '-', 'some\nlines\nof input')).to_output 'some\nlines\nof input\n' - describe readlines: - before: | - name = "Makefile" + 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") + f, badarg = init (M, this_module, 'readlines') - after: - if io.type (defaultin) ~= "closed file" then io.input (defaultin) end + if io.type (defaultin) ~= 'closed file' then io.input (defaultin) end - context with bad arguments: | - badargs.diagnose (f, "std.io.readlines (?file|string)") + 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"). + ['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 () + 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")) + ['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" + expect (io.type (h)).not_to_be 'closed file' f (h) - expect (io.type (h)).to_be "closed file" + 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: @@ -245,38 +245,38 @@ specify std.io: f = M.shell - context with bad arguments: - badargs.diagnose (f, "std.io.shell (string)") + 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" + expect (f [[printf '%s\n' 'foo' 'bar']]).to_be 'foo\nbar\n' - describe slurp: - before: | - name = "Makefile" + name = 'Makefile' h = io.open (name) - content = h:read "*a" + content = h:read '*a' h:close () defaultin = io.input () - f, badarg = init (M, this_module, "slurp") + f, badarg = init (M, this_module, 'slurp') - after: - if io.type (defaultin) ~= "closed file" then io.input (defaultin) end + if io.type (defaultin) ~= 'closed file' then io.input (defaultin) end - context with bad arguments: | - badargs.diagnose (f, "std.io.slurp (?file|string)") + 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"). + ['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 () + 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")) + ['it diagnoses closed file argument'] = function () + expect (f (closed)).to_raise (badarg (1, '?file|string', 'closed file')) end } end @@ -287,9 +287,9 @@ specify std.io: 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" + expect (io.type (h)).not_to_be 'closed file' f (h) - expect (io.type (h)).to_be "closed file" + 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) @@ -300,112 +300,112 @@ specify std.io: f = M.splitdir - context with bad arguments: - badargs.diagnose (f, "std.io.splitdir (string)") + badargs.diagnose (f, 'std.io.splitdir (string)') - it returns a filename as a one element list: - expect (f ("hello")).to_equal {"hello"} + expect (f ('hello')).to_equal {'hello'} - it splits root directory in two empty elements: - expect (f (dirsep)).to_equal {"", ""} + expect (f (dirsep)).to_equal {'', ''} - it returns initial empty string for absolute path: - expect (f (dirsep .. "root")).to_equal {"", "root"} + 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"} + 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!"]] + script = [[require 'std.io'.warn 'Ayup!']] f = M.warn - context with bad arguments: - badargs.diagnose (f, "std.io.warn (string, ?any*)") + badargs.diagnose (f, 'std.io.warn (string, ?any*)') - it outputs a message to stderr: - expect (luaproc (script)).to_output_error "Ayup!\n" + 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" + 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" + 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" + 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" + 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" + 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" } + prog = { file = 'file', name = 'name' } + opts = { program = 'program' } ]] .. script - expect (luaproc (script)).to_output_error "name: Ayup!\n" + 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" } + prog = { file = 'file', line = 125, name = 'name' } + opts = { line = 99, program = 'program' } ]] .. script - expect (luaproc (script)).to_output_error "name:125: Ayup!\n" + expect (luaproc (script)).to_output_error 'name:125: Ayup!\n' - it prefers `prog.file` to `opts.program`: | script = [[ - prog = { file = "file" }; opts = { program = "program" } + prog = { file = 'file' }; opts = { program = 'program' } ]] .. script - expect (luaproc (script)).to_output_error "file: Ayup!\n" + 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" } + prog = { file = 'file', line = 125 } + opts = { line = 99, program = 'program' } ]] .. script - expect (luaproc (script)).to_output_error "file:125: Ayup!\n" + 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" + 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" + 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") + h = io.open (name, 'w') + lines = M.readlines (io.open 'Makefile') defaultout = io.output () - f, badarg = init (M, this_module, "writelines") + f, badarg = init (M, this_module, 'writelines') - after: - if io.type (defaultout) ~= "closed file" then io.output (defaultout) end + 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")) + 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")) + 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")) + 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 () + closed = io.open (name, 'r') closed:close () if have_typecheck then - expect (f (closed)).to_raise (badarg (1, "?file|string|number", "closed file")) + 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" + 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 () @@ -413,7 +413,7 @@ specify std.io: - it accepts number valued arguments: f (h, 1, 2, 3) h:flush () - expect (M.readlines (io.open (name))).to_equal {"1", "2", "3"} + 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)) diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 59abea2..3c22e89 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -1,9 +1,9 @@ before: - base_module = "math" - this_module = "std.math" - global_table = "_G" + base_module = 'math' + this_module = 'std.math' + global_table = '_G' - extend_base = { "floor", "round" } + extend_base = { 'floor', 'round' } M = require (this_module) @@ -23,10 +23,10 @@ specify std.math: - context via the std module: - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). + 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"}). + expect (show_apis {added_to=base_module, by='std'}). to_equal {} @@ -35,7 +35,7 @@ specify std.math: f = M.floor - context with bad arguments: - badargs.diagnose (f, "std.math.floor (number, ?int)") + badargs.diagnose (f, 'std.math.floor (number, ?int)') - it rounds to the nearest smaller integer: expect (f (1.2)).to_be (1) @@ -57,7 +57,7 @@ specify std.math: f = M.round - context with bad arguments: - badargs.diagnose (f, "std.math.round (number, ?int)") + badargs.diagnose (f, 'std.math.round (number, ?int)') - it rounds to the nearest integer: expect (f (1.2)).to_be (1) diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index 2fb80e9..9b852ae 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -1,15 +1,15 @@ before: | - base_module = "package" - this_module = "std.package" - global_table = "_G" + base_module = 'package' + this_module = 'std.package' + global_table = '_G' - extend_base = { "dirsep", "execdir", "find", "igmark", "insert", - "mappath", "normalize", "pathsep", "path_mark", - "remove" } + extend_base = { 'dirsep', 'execdir', 'find', 'igmark', 'insert', + 'mappath', 'normalize', 'pathsep', 'path_mark', + 'remove' } M = require (this_module) - path = M.normalize ("begin", "middle", "end") + path = M.normalize ('begin', 'middle', 'end') function catfile (...) return table.concat ({...}, M.dirsep) end function catpath (...) return table.concat ({...}, M.pathsep) end @@ -30,36 +30,36 @@ specify std.package: - context via the std module: - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). + 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"}). + expect (show_apis {added_to=base_module, by='std'}). to_equal {} - describe find: - before: | - path = table.concat ({"begin", "m%ddl.", "end"}, M.pathsep) + 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)") + badargs.diagnose (f, 'std.package.find (string, string, ?int, ?boolean|:plain)') - it returns nil for unmatched element: - expect (f (path, "unmatchable")).to_be (nil) + expect (f (path, 'unmatchable')).to_be (nil) - it returns the element index for a matched element: - expect (f (path, "end")).to_be (3) + 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"} + 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"} + 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."} + 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: @@ -67,35 +67,35 @@ specify std.package: f = M.insert - context with bad arguments: - badargs.diagnose (f, "std.package.insert (string, [int], string)") + 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")) + 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")) + 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")) + 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")) + 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) + expected = require 'std.string'.split (path, M.pathsep) f = M.mappath - context with bad arguments: - badargs.diagnose (f, "std.package.mappath (string, function, ?any*)") + badargs.diagnose (f, 'std.package.mappath (string, function, ?any*)') - it calls a function with each path element: t = {} @@ -116,56 +116,56 @@ specify std.package: f = M.normalize - context with bad arguments: - badargs.diagnose (f, "std.package.normalize (string*)") + 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")) + 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 ("..", "..", "..")) + 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")) + 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")) + expect (f '/foo/bar').to_be (catfile ('', 'foo', 'bar')) - it normalizes ? to platform path_mark: - expect (f "?.lua"). - to_be (catfile (".", M.path_mark .. ".lua")) + expect (f '?.lua'). + to_be (catfile ('.', M.path_mark .. '.lua')) - it strips redundant trailing /: - expect (f "/foo/bar/").to_be (catfile ("", "foo", "bar")) + 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")) + 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"))) + 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"))) + 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"))) + expect (f ('/foo/bar', 'x')). + to_be (catpath (catfile ('', 'foo', 'bar'), catfile ('.', 'x'))) - it normalizes ? to platform path_mark: - expect (f ("?.lua", "x")). - to_be (catpath (catfile (".", M.path_mark .. ".lua"), catfile (".", "x"))) + expect (f ('?.lua', 'x')). + to_be (catpath (catfile ('.', M.path_mark .. '.lua'), catfile ('.', 'x'))) - it strips redundant trailing /: - expect (f ("/foo/bar/", "x")). - to_be (catpath (catfile ("", "foo", "bar"), catfile (".", "x"))) + 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"))) + 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")) + expect (f (catpath ('1', 'x', '2', './x', './2', './x/../x'))). + to_be (catpath ('./1', './x', './2')) - describe remove: @@ -173,21 +173,21 @@ specify std.package: f = M.remove - context with bad arguments: - badargs.diagnose (f, "std.package.remove (string, ?int)") + badargs.diagnose (f, 'std.package.remove (string, ?int)') - it removes the last item by default: - expect (f (path)).to_be (M.normalize ("begin", "middle")) + 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")) + 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")) + 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) + path = table.concat ({'begin', 'middle', 'end'}, M.pathsep) expect (f (path)). - to_be (table.concat ({"begin", "middle"}, M.pathsep)) + 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", + expect (string.format ('%s\n%s\n%s\n%s\n%s\n', M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark) ).to_contain (package.config) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index f16658f..9154652 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -1,40 +1,40 @@ local typecheck -have_typecheck, typecheck = pcall (require, "typecheck") +have_typecheck, typecheck = pcall (require, 'typecheck') -local inprocess = require "specl.inprocess" -local hell = require "specl.shell" -local std = require "specl.std" +local inprocess = require 'specl.inprocess' +local hell = require 'specl.shell' +local std = require 'specl.std' -badargs = require "specl.badargs" +badargs = require 'specl.badargs' -local top_srcdir = os.getenv "top_srcdir" or "." -local top_builddir = os.getenv "top_builddir" or "." +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", + 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" +-- back to environment PATH search for 'lua' if nothing else works. +local LUA = os.getenv 'LUA' or 'lua' -- Tweak _DEBUG without tripping over Specl nested environments. -setdebug = require "std.debug"._setdebug +setdebug = require 'std.debug'._setdebug -- 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 + 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 @@ -49,14 +49,14 @@ end 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 + if type (k) == 'number' and k > n then n = k end end return n end pack = table.pack or function (...) - return {n = select ("#", ...), ...} + return {n = select ('#', ...), ...} end @@ -81,33 +81,33 @@ end badargs.result = badargs.result or function (fname, i, want, got) if want == nil then i, want = i - 1, i end -- numbers only for narg error - if got == nil and type (want) == "number" then + 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) + 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 ("|", ", ") + 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") + 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 ("^%.", "") + local name = (mname .. '.' .. fname):gsub ('^%.', '') return M[fname], function (...) return badargs.format (name, ...) end, function (...) return badargs.result (name, ...) end @@ -145,7 +145,7 @@ end local function mkscript (code) local f = os.tmpname () - local h = io.open (f, "w") + local h = io.open (f, 'w') h:write (code) h:close () return f @@ -161,11 +161,11 @@ end -- execution was successful, otherwise nil function luaproc (code, arg, stdin) local f = mkscript (code) - if type (arg) ~= "table" then arg = {arg} end + 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.env = { LUA_PATH=package.path, LUA_INIT='', LUA_INIT_5_2='' } cmd.stdin = stdin local proc = hell.spawn (cmd) os.remove (f) @@ -180,25 +180,25 @@ end -- @param deprecate value of `_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 +-- @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 "" + args = args or '' local script if objectinit == nil then script = string.format([[ _DEBUG = { deprecate = %s } - M = require "%s" + M = require '%s' P = M.prototype print (M.%s (%s)) ]], tostring (deprecate), module, fname, tostring (args)) else script = string.format([[ _DEBUG = { deprecate = %s } - local M = require "%s" + local M = require '%s' local P = M.prototype local obj = P (%s) print (obj:%s (%s)) @@ -215,7 +215,7 @@ function concat_file_content (...) local t = {} for _, name in ipairs {...} do h = io.open (name) - t[#t + 1] = h:read "*a" + t[#t + 1] = h:read '*a' end return table.concat (t) end @@ -225,9 +225,9 @@ 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]*", + proc.output:gsub ('(%S*)[%s]*', function (x) - if x ~= "" then r[x] = true end + if x ~= '' then r[x] = true end end) return r end @@ -235,16 +235,16 @@ 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"`: +-- 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 +-- 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 +-- List keys from `require 'import'`, which are also in T1 but with -- a different value: -- -- show_apis {from=T1, enhanced_by=import} @@ -267,7 +267,7 @@ function show_apis (argt) before[k] = true end - local M = require "]] .. by .. [[" + local M = require ']] .. by .. [[' for k in pairs (]] .. added_to .. [[) do after[k] = true end @@ -280,7 +280,7 @@ function show_apis (argt) elseif from and not_in then return tabulate_output ([[ local from = ]] .. from .. [[ - local M = require "]] .. not_in .. [[" + local M = require ']] .. not_in .. [[' for k in pairs (M) do -- M[1] is typically the module namespace name, don't match @@ -292,7 +292,7 @@ function show_apis (argt) elseif from and enhanced_in then return tabulate_output ([[ local from = ]] .. from .. [[ - local M = require "]] .. enhanced_in .. [[" + local M = require ']] .. enhanced_in .. [[' for k, v in pairs (M) do if from[k] ~= M[k] and from[k] ~= nil then print (k) end @@ -314,7 +314,7 @@ function show_apis (argt) ]]) end - assert (false, "missing argument to show_apis") + assert (false, 'missing argument to show_apis') end @@ -326,8 +326,8 @@ capture = inprocess.capture or do -- Custom matcher for set size and set membership. - local util = require "specl.util" - local matchers = require "specl.matchers" + local util = require 'specl.util' + local matchers = require 'specl.matchers' local Matcher, matchers, q = matchers.Matcher, matchers.matchers, matchers.stringify @@ -339,15 +339,15 @@ do return size == expect end, - actual = "table", + actual = 'table', format_expect = function (self, expect) - return " a table containing " .. expect .. " elements, " + 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, " + return ' a table with any of ' .. + util.concat (alternatives, util.QUOTED) .. ' elements, ' end, } @@ -356,15 +356,15 @@ do return actual[expect] ~= nil end, - actual = "set", + actual = 'set', format_expect = function (self, expect) - return " a set containing " .. q (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) .. ", " + return ' a set containing any of ' .. + util.concat (alternatives, util.QUOTED) .. ', ' end, } diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 495531a..89f21b6 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -1,13 +1,13 @@ before: | - this_module = "std" - global_table = "_G" + this_module = 'std' + global_table = '_G' - exported_apis = { "assert", "elems", "eval", "getmetamethod", - "ielems", "ipairs", "npairs", "pairs", - "require", "ripairs", "rnpairs", "tostring" } + exported_apis = { 'assert', 'elems', 'eval', 'getmetamethod', + 'ielems', 'ipairs', 'npairs', 'pairs', + 'require', 'ripairs', 'rnpairs', 'tostring' } -- Tables with iterator metamethods used by various examples. - __pairs = setmetatable ({ content = "a string" }, { + __pairs = setmetatable ({ content = 'a string' }, { __pairs = function (t) return function (x, n) if n < #x.content then @@ -16,7 +16,7 @@ before: | end, t, 0 end, }) - __index = setmetatable ({ content = "a string" }, { + __index = setmetatable ({ content = 'a string' }, { __index = function (t, n) if n <= #t.content then return t.content:sub (n, n) @@ -42,11 +42,11 @@ specify std: - context when lazy loading: - it has no submodules on initial load: for _, v in pairs (M) do - expect (type (v)).not_to_be "table" + expect (type (v)).not_to_be 'table' end - it loads submodules on demand: lazy = M.math - expect (lazy).to_be (require "std.math") + expect (lazy).to_be (require 'std.math') - it loads submodule functions on demand: expect (M.math.round (3.141592)).to_be (3) @@ -55,18 +55,18 @@ specify std: f = M.assert - context with bad arguments: - badargs.diagnose (f, "std.assert (?any, ?string, ?any*)") + 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" + 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 'yes').to_be 'yes' expect (f (false == false)).to_be (true) - context when it triggers: - it has a falsey initial argument: @@ -74,10 +74,10 @@ specify std: 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" + 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")) + expect (f (nil, '%s %d: %q', 'here', 42, 'a string')). + to_raise (string.format ('%s %d: %q', 'here', 42, 'a string')) - describe elems: @@ -85,19 +85,19 @@ specify std: f = M.elems - context with bad arguments: - badargs.diagnose (f, "std.elems (table)") + badargs.diagnose (f, 'std.elems (table)') - it is an iterator over table values: t = {} - for e in f {"foo", bar = "baz", 42} do + for e in f {'foo', bar = 'baz', 42} do t[#t + 1] = e end - expect (t).to_contain.a_permutation_of {"foo", "baz", 42} + 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"} + 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 @@ -109,13 +109,13 @@ specify std: f = M.eval - context with bad arguments: - badargs.diagnose (f, "std.eval (string)") + badargs.diagnose (f, 'std.eval (string)') - it diagnoses invalid lua: - # Some internal error when eval tries to call uncompilable "=" code. - expect (f "=").to_raise () + # 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)) + expect (f 'math.min (2, 10)').to_be (math.min (2, 10)) - describe getmetamethod: @@ -123,25 +123,25 @@ specify std: f = M.getmetamethod - context with bad arguments: - badargs.diagnose (f, "std.getmetamethod (?any, string)") + badargs.diagnose (f, 'std.getmetamethod (?any, string)') - context with a table: - before: - method = function () return "called" end + method = function () return 'called' end functor = setmetatable ({}, {__call = method}) t = setmetatable ({}, { - _type = "table", _method = method, _functor = functor, + _type = 'table', _method = method, _functor = functor, }) - it returns nil for missing metamethods: - expect (f (t, "not a metamethod on t")).to_be (nil) + 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) + 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" + 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" + expect (f (t, '_functor')).to_be (functor) + expect (f (t, '_functor')()).to_be 'called' - describe ielems: @@ -149,24 +149,24 @@ specify std: f = M.ielems - context with bad arguments: - badargs.diagnose (f, "std.ielems (table)") + badargs.diagnose (f, 'std.ielems (table)') - it is an iterator over integer-keyed table values: t = {} - for e in f {"foo", 42} do + for e in f {'foo', 42} do t[#t + 1] = e end - expect (t).to_equal {"foo", 42} + 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 + for e in f {'foo', 42; bar = 'baz', qux = 'quux'} do t[#t + 1] = e end - expect (t).to_equal {"foo", 42} + 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"} + 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 @@ -178,24 +178,24 @@ specify std: f = M.ipairs - context with bad arguments: - badargs.diagnose (f, "std.ipairs (table)") + badargs.diagnose (f, 'std.ipairs (table)') - it is an iterator over integer-keyed table values: t = {} - for i, v in f {"foo", 42} do + for i, v in f {'foo', 42} do t[i] = v end - expect (t).to_equal {"foo", 42} + 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 + for i, v in f {'foo', 42; bar = 'baz', qux = 'quux'} do t[i] = v end - expect (t).to_equal {"foo", 42} + 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"} + 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 @@ -207,26 +207,26 @@ specify std: f = M.npairs - context with bad arguments: - badargs.diagnose (f, "std.npairs (table)") + 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 + for i, v in f {'foo', 42, nil, nil, 'five'} do t[i] = v end - expect (t).to_equal {"foo", 42, nil, nil, "five"} + 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 + 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"} + 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" + 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 @@ -238,19 +238,19 @@ specify std: f = M.pairs - context with bad arguments: - badargs.diagnose (f, "std.pairs (table)") + 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 + for k, v in f {'foo', bar = 'baz', 42} do t[k] = v end - expect (t).to_equal {"foo", bar = "baz", 42} + 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"} + 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 @@ -262,81 +262,81 @@ specify std: f = M.require - context with bad arguments: - badargs.diagnose (f, "std.require (string, ?string, ?string, ?string)") + badargs.diagnose (f, 'std.require (string, ?string, ?string, ?string)') - it diagnoses non-existent module: - expect (f ("module-not-exists", "", "")).to_raise "module-not-exists" + expect (f ('module-not-exists', '', '')).to_raise 'module-not-exists' - it diagnoses module too old: - expect (f ("std", "9999", "9999")). + expect (f ('std', '9999', '9999')). to_raise "require 'std' with at least version 9999," - it diagnoses module too new: - expect (f ("std", "0", "0")). + 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") + expect (f ('std', '0', '9999')).to_be (require 'std') - it places no upper bound by default: - expect (f ("std", "0")).to_be (require "std") + expect (f ('std', '0')).to_be (require 'std') - it places no lower bound by default: - expect (f "std").to_be (require "std") + 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" + 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" + std = require 'std' ver = std.version - std.version = "1.2.3" + std.version = '1.2.3' - after: std.version = ver - it diagnoses module too old: - expect (f ("std", "1.2.4")). + expect (f ('std', '1.2.4')). to_raise "require 'std' with at least version 1.2.4," - expect (f ("std", "1.3")). + expect (f ('std', '1.3')). to_raise "require 'std' with at least version 1.3," - expect (f ("std", "2.1.2")). + expect (f ('std', '2.1.2')). to_raise "require 'std' with at least version 2.1.2," - expect (f ("std", "2")). + expect (f ('std', '2')). to_raise "require 'std' with at least version 2," - expect (f ("std", "1.2.10")). + 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")). + expect (f ('std', nil, '1.2.2')). to_raise "require 'std' with version less than 1.2.2," - expect (f ("std", nil, "1.1")). + expect (f ('std', nil, '1.1')). to_raise "require 'std' with version less than 1.1," - expect (f ("std", nil, "1.1.2")). + expect (f ('std', nil, '1.1.2')). to_raise "require 'std' with version less than 1.1.2," - expect (f ("std", nil, "1")). + 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) + 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" + std = require 'std' ver = std.version - std.version = "standard library for Lua 5.3 / 41.0.0" + 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 () + expect (f ('std', '42')).to_raise () - it diagnoses module too new: - expect (f ("std", nil, "40")).to_raise () + 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) + 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: @@ -344,26 +344,26 @@ specify std: f = M.ripairs - context with bad arguments: - badargs.diagnose (f, "std.ripairs (table)") + 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"} + 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"}, {} + t, u = {'one', 'two', 'five'}, {} for _, v in f (t) do u[#u + 1] = v end - expect (u).to_equal {"five", "two", "one"} + 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"} + 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"} + 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 @@ -375,26 +375,26 @@ specify std: f = M.rnpairs - context with bad arguments: - badargs.diagnose (f, "std.rnpairs (table)") + 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"} + 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 + 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"} + 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" + 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 @@ -406,18 +406,18 @@ specify std: f = M.tostring - context with bad arguments: - badargs.diagnose (f, "std.tostring (?any)") + badargs.diagnose (f, 'std.tostring (?any)') - it renders primitives exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) expect (f (false)).to_be (tostring (false)) expect (f (42)).to_be (tostring (42)) expect (f (f)).to_be (tostring (f)) - expect (f "a string").to_be "a string" + expect (f 'a string').to_be 'a string' - it renders empty tables as a pair of braces: - expect (f {}).to_be ("{}") + expect (f {}).to_be ('{}') - it renders table array part compactly: - expect (f {"one", "two", "five"}). + expect (f {'one', 'two', 'five'}). to_be '{one,two,five}' - it renders a table dictionary part compactly: expect (f { one = true, two = 2, three = {3}}). @@ -426,6 +426,6 @@ specify std: expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }). to_be '{five=1,four=2,one=3,three=4,two=5}' - it renders keys with invalid symbol names compactly: - expect (f { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }). + expect (f { _ = 0, word = 0, ['?'] = 1, ['a-key'] = 1, ['[]'] = 1 }). to_be '{?=1,[]=1,_=0,a-key=1,word=0}' diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 9f422ae..3e76c65 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -1,22 +1,22 @@ before: - base_module = "string" - this_module = "std.string" - global_table = "_G" + 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", "render", "rtrim", "split", - "tfind", "trim", "wrap" } + extend_base = { '__concat', '__index', + 'caps', 'chomp', 'escape_pattern', 'escape_shell', + 'finds', 'format', 'ltrim', + 'numbertosi', 'ordinal_suffix', 'pad', + 'prettytostring', 'render', 'rtrim', 'split', + 'tfind', 'trim', 'wrap' } M = require (this_module) - getmetatable ("").__concat = M.__concat - getmetatable ("").__index = M.__index + getmetatable ('').__concat = M.__concat + getmetatable ('').__index = M.__index specify std.string: - before: - subject = "a string \n\n" + subject = 'a string \n\n' - context when required: - context by name: @@ -32,27 +32,27 @@ specify std.string: - context via the std module: - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}). + 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"}). + 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) + target = 'a string \n\n another string' + expect (subject .. ' another string').to_be (target) - it stringifies non-string arguments: - argument = { "a table" } + argument = { 'a table' } expect (subject .. argument). - to_be (string.format ("%s%s", subject, require "std".tostring (argument))) + to_be (string.format ('%s%s', subject, require 'std'.tostring (argument))) - it stringifies nil arguments: argument = nil expect (subject .. argument). - to_be (string.format ("%s%s", subject, require "std".tostring (argument))) + to_be (string.format ('%s%s', subject, require 'std'.tostring (argument))) - it does not perturb the original subject: original = subject - newstring = subject .. " concatenate something" + newstring = subject .. ' concatenate something' expect (subject).to_be (original) @@ -61,15 +61,15 @@ specify std.string: f = M.caps - context with bad arguments: - badargs.diagnose (f, "std.string.caps (string)") + badargs.diagnose (f, 'std.string.caps (string)') - it capitalises words of a string: - target = "A String \n\n" + 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" + expect (f 'a stRiNg').to_be 'A StRiNg' - it is available as a string metamethod: - expect (("a stRiNg"):caps ()).to_be "A StRiNg" + expect (('a stRiNg'):caps ()).to_be 'A StRiNg' - it does not perturb the original subject: original = subject newstring = f (subject) @@ -78,16 +78,16 @@ specify std.string: - describe chomp: - before: - target = "a string \n" + target = 'a string \n' f = M.chomp - context with bad arguments: - badargs.diagnose (f, "std.string.chomp (string)") + 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 " + subject = 'a string ' expect (f (subject)).to_be (subject) - it is available as a string metamethod: expect (subject:chomp ()).to_be (target) @@ -100,25 +100,25 @@ specify std.string: - describe escape_pattern: - before: magic = {} - meta = "^$()%.[]*+-?" + 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)") + badargs.diagnose (f, 'std.string.escape_pattern (string)') - context with each printable ASCII char: - before: - subject, target = "", "" + subject, target = '', '' for c = 32, 126 do s = string.char (c) subject = subject .. s - if magic[s] then target = target .. "%" end + if magic[s] then target = target .. '%' end target = target .. s end - - "it inserts a % before any non-alphanumeric in a string": + - '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) @@ -133,18 +133,18 @@ specify std.string: f = M.escape_shell - context with bad arguments: - badargs.diagnose (f, "std.string.escape_shell (string)") + badargs.diagnose (f, 'std.string.escape_shell (string)') - context with each printable ASCII char: - before: - subject, target = "", "" + subject, target = '', '' for c = 32, 126 do s = string.char (c) subject = subject .. s - if s:match ("[][ ()\\\"']") then target = target .. "\\" end + if s:match ('[][ ()\\\'"]') then target = target .. '\\' end target = target .. s end - - "it inserts a \\ before any shell metacharacters": + - '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) @@ -152,51 +152,51 @@ specify std.string: original = subject newstring = f (subject) expect (subject).to_be (original) - - "it diagnoses non-string arguments": + - 'it diagnoses non-string arguments': if typecheck then - expect (f ()).to_raise ("string expected") - expect (f {"a table"}).to_raise ("string expected") + expect (f ()).to_raise ('string expected') + expect (f {'a table'}).to_raise ('string expected') end - describe finds: - before: - subject = "abcd" + subject = 'abcd' f = M.finds - context with bad arguments: - badargs.diagnose (f, "std.string.finds (string, string, ?int, ?boolean|:plain)") + 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" } } } + target = { { 1, 2; capt = { 'a', 'b' } }, { 3, 4; capt = { 'c', 'd' } } } - it creates a list of pattern captures: - expect ({f (subject, "(.)(.)")}).to_equal ({ target }) + expect ({f (subject, '(.)(.)')}).to_equal ({ target }) - it is available as a string metamethod: - expect ({subject:finds ("(.)(.)")}).to_equal ({ target }) + expect ({subject:finds ('(.)(.)')}).to_equal ({ target }) - it creates an empty list where no captures are matched: target = {} - expect ({f (subject, "(x)")}).to_equal ({ 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 }) + 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 }) + 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, "...") + newstring = f (subject, '...') expect (subject).to_be (original) - describe format: - before: - subject = "string=%s, number=%d" + subject = 'string=%s, number=%d' f = M.format - context with bad arguments: - badargs.diagnose (f, "std.string.format (string, ?any*)") + badargs.diagnose (f, 'std.string.format (string, ?any*)') - it returns a single argument without attempting formatting: expect (f (subject)).to_be (subject) @@ -210,25 +210,25 @@ specify std.string: - describe ltrim: - before: - subject = " \t\r\n a short string \t\r\n " + 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)") + badargs.diagnose (f, 'std.string.ltrim (string, ?string)') - it removes whitespace from the start of a string: - target = "a short string \t\r\n " + 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) + 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) + 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") + newstring = f (subject, '%W') expect (subject).to_be (original) @@ -237,11 +237,11 @@ specify std.string: f = M.numbertosi - context with bad arguments: - badargs.diagnose (f, "std.string.numbertosi (number|string)") + 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"} + 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) @@ -249,7 +249,7 @@ specify std.string: end expect (subject).to_equal (target) - it coerces string arguments to a number: - expect (f "1000").to_be "1k" + expect (f '1000').to_be '1k' - describe ordinal_suffix: @@ -257,23 +257,23 @@ specify std.string: f = M.ordinal_suffix - context with bad arguments: - badargs.diagnose (f, "std.string.ordinal_suffix (int|string)") + 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" + 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" + 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" + expect (f '-91').to_be 'st' - describe pad: @@ -283,20 +283,20 @@ specify std.string: f = M.pad - context with bad arguments: - badargs.diagnose (f, "std.string.pad (string, int, ?string)") + badargs.diagnose (f, 'std.string.pad (string, int, ?string)') - context when string is shorter than given width: - before: - subject = "short string" + subject = 'short string' - it right pads a string to the given width with spaces: - target = "short string " + 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" + target = ' short string' expect (f (subject, width)).to_be (target) - it is available as a string metamethod: - target = "short string " + target = 'short string ' expect (subject:pad (width)).to_be (target) - context when string is longer than given width: @@ -307,7 +307,7 @@ specify std.string: expect (f (subject, width)).to_be (target) - it left pads a string to given width with spaces: width = -width - target = "an twenty characters" + target = 'an twenty characters' expect (f (subject, width)).to_be (target) - it is available as a string metamethod: target = "a string that's long" @@ -324,7 +324,7 @@ specify std.string: f = M.prettytostring - context with bad arguments: - badargs.diagnose (f, "std.string.prettytostring (?any, ?string, ?string)") + badargs.diagnose (f, 'std.string.prettytostring (?any, ?string, ?string)') - it renders nil exactly like system tostring: expect (f (nil)).to_be (tostring (nil)) @@ -336,192 +336,192 @@ specify std.string: 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 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}") + expect (f {}).to_be ('{\n}') - it renders an array prettily: - a = {"one", "two", "three"} - expect (f (a, "")). + 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, "")). + 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, "")). + 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, "")). + 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 " + 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)") + badargs.diagnose (f, 'std.string.rtrim (string, ?string)') - it removes whitespace from the end of a string: - target = " \t\r\n a short 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) + 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) + 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") + newstring = f (subject, '%W') expect (subject).to_be (original) - describe split: - before: - target = { "first", "the second one", "final entry" } - subject = table.concat (target, ", ") + 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)") + badargs.diagnose (f, 'std.string.split (string, ?string)') - - it falls back to "%s+" when no pattern is given: + - it falls back to '%s+' when no pattern is given: expect (f (subject)). - to_equal {"first,", "the", "second", "one,", "final", "entry"} + to_equal {'first,', 'the', 'second', 'one,', 'final', 'entry'} - it returns a one-element list for an empty string: - expect (f ("", ", ")).to_equal {""} + expect (f ('', ', ')).to_equal {''} - it makes a table of substrings delimited by a separator: - expect (f (subject, ", ")).to_equal (target) + 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) + 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"} + 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"} + 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", ""} + 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"} + 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") + 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"} + expect (f (subject, '%s+')). + to_equal {'first,', 'the', 'second', 'one,', 'final', 'entry'} - describe tfind: - before: - subject = "abc" + subject = 'abc' f = M.tfind - context with bad arguments: - badargs.diagnose (f, "std.string.tfind (string, string, ?int, ?boolean|:plain)") + 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) + 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) + 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) + 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) + 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) + 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, "...") + newstring = f (subject, '...') expect (subject).to_be (original) - describe trim: - before: - subject = " \t\r\n a short string \t\r\n " + 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)") + badargs.diagnose (f, 'std.string.trim (string, ?string)') - it removes whitespace from each end of a string: - target = "a short 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) + 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) + 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") + 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." + 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)") + 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." + 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." + 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." + 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." + 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: @@ -531,10 +531,10 @@ specify std.string: 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") + 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") + expect (f ()).to_raise ('string expected') + expect (f {'a table'}).to_raise ('string expected') end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 5694558..9bf47d2 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -1,13 +1,13 @@ before: | - base_module = "table" - this_module = "std.table" - global_table = "_G" + 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" } + 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) @@ -33,20 +33,20 @@ specify std.table: - context via the std module: - it does not touch the global table: - expect (show_apis {added_to=global_table, by="std"}).to_equal {} + 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 {} + 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!"}) + 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)") + badargs.diagnose (f, 'std.table.clone (table, [table], ?boolean|:nometa)') - it does not just return the subject: expect (f (subject)).not_to_be (subject) @@ -64,67 +64,67 @@ specify std.table: - 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) + 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) + 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) + 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) + 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!"}) + 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)") + 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"} }) + 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) + expect (f (subject, {'k1', 'k2', 'k3'})).to_equal (subject) - it only makes a shallow copy: - expect (f (subject, {"k1"}).k1).to_be (subject.k1) + 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"}) + 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) + 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)) + 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) + 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} + 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)") + badargs.diagnose (f, 'std.table.depair (list of lists)') - it returns a primitive table: - expect (objtype (f (l))).to_be "table" + expect (objtype (f (l))).to_be 'table' - it works with an empty table: expect (f {}).to_equal {} - it is the inverse of enpair: @@ -136,55 +136,55 @@ specify std.table: f = M.empty - context with bad arguments: - badargs.diagnose (f, "std.table.empty (table)") + 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 {'stuff'}).to_be (false) expect (f {{}}).to_be (false) expect (f {false}).to_be (false) - describe enpair: - before: - t = {"first", "second", third = 4} + t = {'first', 'second', third = 4} l = M.enpair (t) f = M.enpair - context with bad arguments: - badargs.diagnose (f, "std.table.enpair (table)") + badargs.diagnose (f, 'std.table.enpair (table)') - it returns a table: - expect (objtype (f (t))).to_be "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}} + 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") + f, badarg = init (M, this_module, 'insert') - context with bad arguments: - badargs.diagnose (f, "std.table.insert (table, [int], any)") + badargs.diagnose (f, 'std.table.insert (table, [int], any)') examples { - ["it diagnoses more than 2 arguments with no pos"] = function () - pending "#issue 76" + ['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" + ['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 } @@ -192,18 +192,18 @@ specify std.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"} + 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} + 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"} + 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"} + 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"} + expect (f ({}, 1, 'x')).to_equal {'x'} - describe invert: @@ -213,12 +213,12 @@ specify std.table: f = M.invert - context with bad arguments: - badargs.diagnose (f, "std.table.invert (table)") + 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" } + 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: @@ -233,13 +233,13 @@ specify std.table: f = M.keys - context with bad arguments: - badargs.diagnose (f, "std.table.keys (table)") + 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"} + 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 @@ -253,10 +253,10 @@ specify std.table: f = M.maxn - context with bad arguments: - badargs.diagnose (f, "std.table.maxn (table)") + 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 {'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) @@ -270,22 +270,22 @@ specify std.table: - 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!"}) + 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") + f, badarg = init (M, this_module, 'merge') - context with bad arguments: - badargs.diagnose (f, "std.table.merge (table, table, [table], ?boolean|:nometa)") + 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)) + ['it diagnoses more than 2 arguments with no pos'] = function () + pending '#issue 76' + expect (f ({}, {}, ':nometa', false)).to_raise (badarg (4)) end } @@ -308,45 +308,45 @@ specify std.table: - 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)) + 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) + 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)) + 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) + 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) + 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) + 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" } + 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") + f, badarg = init (M, this_module, 'merge_select') - context with bad arguments: - badargs.diagnose (f, "std.table.merge_select (table, table, [table], ?boolean|:nometa)") + 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)) + ['it diagnoses more than 2 arguments with no pos'] = function () + pending '#issue 76' + expect (f ({}, {}, ':nometa', false)).to_raise (badarg (4)) end } @@ -359,9 +359,9 @@ specify std.table: - 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) + 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) + 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: @@ -373,15 +373,15 @@ specify std.table: - 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)) + 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) + 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)) + 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) + expect (getmetatable (f ({}, t1mt, {}, ':nometa'))).to_be (nil) + expect (getmetatable (f ({}, t1mt, {'k1'}, ':nometa'))).to_be (nil) - describe new: @@ -389,35 +389,35 @@ specify std.table: f = M.new - context with bad arguments: - badargs.diagnose (f, "std.table.new (?any, ?table)") + 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" } + t = { 'unique table' } expect (f (default, t)).to_be (t) - context when setting a default: - before: - default = "default" + 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" } + 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 = 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" + t = f ('default') + expect (t[1]).to_be 'default' - it returns the actual default object: - default = { "unique object" } + default = { 'unique object' } t = f (default) expect (t[1]).to_be (default) @@ -425,17 +425,17 @@ specify std.table: - describe pack: - before: unpack = unpack or table.unpack - t = {"one", "two", "five", n=3} + 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) + 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 ('', false, nil).n).to_be (3) expect (f (nil, nil, nil).n).to_be (3) @@ -444,22 +444,22 @@ specify std.table: l = { {first = false, second = true, third = true}, {first = 1, second = 2, third = 3}, - {first = "1st", second = "2nd", third = "3rd"}, + {first = '1st', second = '2nd', third = '3rd'}, } f = M.project - context with bad arguments: - badargs.diagnose (f, "std.table.project (any, list of tables)") + badargs.diagnose (f, 'std.table.project (any, list of tables)') - it returns a table: - expect (objtype (f ("third", l))).to_be "table" + expect (objtype (f ('third', l))).to_be 'table' - it works with an empty table: - expect (f ("third", {})).to_equal {} + 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"} + 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"} + expect (f ('first', l)).to_equal {false, 1, '1st'} - describe remove: @@ -467,49 +467,49 @@ specify std.table: f = M.remove - context with bad arguments: - badargs.diagnose (f, "std.table.remove (table, ?int)") + 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" + ['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" + 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" + 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" + 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}) + 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"} + 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" + 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 } + subject = { 'one', { { 'two' }, 'three' }, four = 5 } f = M.size - context with bad arguments: - badargs.diagnose (f, "std.table.size (table)") + badargs.diagnose (f, 'std.table.size (table)') - it counts the number of keys in a table: expect (f (subject)).to_be (3) @@ -526,7 +526,7 @@ specify std.table: f = M.sort - context with bad arguments: - badargs.diagnose (f, "std.table.sort (table, ?function)") + badargs.diagnose (f, 'std.table.sort (table, ?function)') - it sorts elements in place: f (subject, cmp) @@ -537,7 +537,7 @@ specify std.table: - describe unpack: - before: - t = {"one", "two", "five"} + t = {'one', 'two', 'five'} f = M.unpack - it returns nil for an empty table: expect (f {}).to_be (nil) @@ -554,7 +554,7 @@ specify std.table: 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) + expect ({f (M.pack ('one', 'two', 'five'))}).to_equal (t) - describe values: @@ -564,7 +564,7 @@ specify std.table: f = M.values - context with bad arguments: - badargs.diagnose (f, "std.table.values (table)") + badargs.diagnose (f, 'std.table.values (table)') - it returns an empty list when subject is empty: expect (f {}).to_equal {} From d98ee5748913e43f4022491be4b5165c3e285a21 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 25 Sep 2017 22:58:57 -0700 Subject: [PATCH 690/703] maint: modernize formatting - horizontal space. * README.md: Document horizontal space saving style. * lib/std/_base.lua, lib/std/debug.lua, lib/std/debug_init/init.lua, lib/std/init.lua, lib/std/io.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua, specs/debug_spec.yaml, specs/io_spec.yaml, specs/math_spec.yaml, specs/package_spec.yaml, specs/spec_helper.lua, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Use horizontal space saving style. Signed-off-by: Gary V. Vaughan --- README.md | 3 + lib/std/_base.lua | 210 ++++++++-------- lib/std/debug.lua | 52 ++-- lib/std/debug_init/init.lua | 14 +- lib/std/init.lua | 144 +++++------ lib/std/io.lua | 134 +++++------ lib/std/math.lua | 28 +-- lib/std/package.lua | 118 ++++----- lib/std/string.lua | 238 +++++++++---------- lib/std/table.lua | 164 ++++++------- specs/debug_spec.yaml | 192 +++++++-------- specs/io_spec.yaml | 356 +++++++++++++-------------- specs/math_spec.yaml | 106 ++++----- specs/package_spec.yaml | 166 ++++++------- specs/spec_helper.lua | 208 ++++++++-------- specs/std_spec.yaml | 292 +++++++++++------------ specs/string_spec.yaml | 336 +++++++++++++------------- specs/table_spec.yaml | 462 ++++++++++++++++++------------------ 18 files changed, 1613 insertions(+), 1610 deletions(-) diff --git a/README.md b/README.md index 012ad25..6b7610b 100644 --- a/README.md +++ b/README.md @@ -82,4 +82,7 @@ points when proposing changes: 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. + [issues]: http://github.com/lua-stdlib/lua-stdlib/issues diff --git a/lib/std/_base.lua b/lib/std/_base.lua index d4ba92d..1127df7 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -22,7 +22,7 @@ local _ENV = _ENV -local dirsep = string.match (package.config, '^(%S+)\n') +local dirsep = string.match(package.config, '^(%S+)\n') local error = error local getfenv = getfenv or false local getmetatable = getmetatable @@ -69,7 +69,7 @@ do -- Unless strict was disabled (`_DEBUG = false`), or that module is not -- available, check for use of undeclared variables in this module... if _DEBUG.strict then - ok, strict = pcall (require, 'strict') + ok, strict = pcall(require, 'strict') if ok then _ENV = strict {} else @@ -82,7 +82,7 @@ do -- Unless strict was disabled (`_DEBUG = false`), or that module is not -- available, check for use of undeclared variables in this module... if _DEBUG.argcheck then - ok, typecheck = pcall (require, 'typecheck') + ok, typecheck = pcall(require, 'typecheck') if not ok then -- ...otherwise, the strict function is not available at all! _DEBUG.argcheck = false @@ -107,9 +107,9 @@ local getmetamethod, len -- Iterate over keys 1..n, where n is the key before the first nil --- valued ordinal key (like Lua 5.3). -local function ipairs (l) - return function (l, n) +-- valued ordinal key(like Lua 5.3). +local function ipairs(l) + return function(l, n) n = n + 1 if l[n] ~= nil then return n, l[n] @@ -121,15 +121,15 @@ end local _pairs = pairs -- Respect __pairs metamethod, even in Lua 5.1. -local function pairs (t) - return (getmetamethod (t, '__pairs') or _pairs) (t) +local function pairs(t) + return(getmetamethod(t, '__pairs') or _pairs)(t) end -local maxn = table_maxn or function (t) +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 + for k in pairs(t) do + if type(k) == 'number' and k > n then n = k end end return n end @@ -141,43 +141,43 @@ end --[[ ============================ ]]-- -local function argerror (name, i, extramsg, level) +local function argerror(name, i, extramsg, level) level = level or 1 - local s = string_format ("bad argument #%d to '%s'", i, name) + local s = string_format("bad argument #%d to '%s'", i, name) if extramsg ~= nil then - s = s .. ' (' .. extramsg .. ')' + s = s .. '(' .. extramsg .. ')' end - error (s, level + 1) + error(s, level + 1) end -- No need to recurse because functables are second class citizens in -- Lua: --- func=function () print 'called' end +-- func = function() print 'called' end -- func() --> 'called' --- functable=setmetatable ({}, {__call=func}) +-- functable=setmetatable({}, {__call=func}) -- functable() --> 'called' --- nested=setmetatable ({}, {__call=functable}) +-- nested=setmetatable({}, {__call=functable}) -- nested() --- --> stdin:1: attempt to call a table value (global 'd') +-- --> 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 +local function callable(x) + if type(x) == 'function' then return x end + return(getmetatable(x) or {}).__call end -local function catfile (...) - return table_concat ({...}, dirsep) +local function catfile(...) + return table_concat({...}, dirsep) end -local function compare (l, m) - local lenl, lenm = len (l), len (m) - for i = 1, math_min (lenl, lenm) do - local li, mi = tonumber (l[i]), tonumber (m[i]) +local function compare(l, m) + local lenl, lenm = len(l), len(m) + for i = 1, math_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 @@ -196,52 +196,52 @@ local function compare (l, m) end -local function copy (dest, src) +local function copy(dest, src) if src == nil then dest, src = {}, dest end - for k, v in pairs (src) do dest[k] = v end + for k, v in pairs(src) do dest[k] = v end return dest end -local function escape_pattern (s) - return (s:gsub ('[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) +local function escape_pattern(s) + return(s:gsub('[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) end -local function _getfenv (fn) +local function _getfenv(fn) fn = fn or 1 -- Unwrap functable: - if type (fn) == 'table' then - fn = fn.call or (getmetatable (fn) or {}).__call + if type(fn) == 'table' then + fn = fn.call or(getmetatable(fn) or {}).__call end if getfenv then - if type (fn) == 'number' then fn = fn + 1 end + if type(fn) == 'number' then fn = fn + 1 end -- Stack frame count is critical here, so ensure we don't optimise one -- away in LuaJIT... - return getfenv (fn), nil + return getfenv(fn), nil else - if type (fn) == 'number' then - fn = debug_getinfo (fn + 1, 'f').func + if type(fn) == 'number' then + fn = debug_getinfo(fn + 1, 'f').func end local name, env local up = 0 repeat up = up + 1 - name, env = debug_getupvalue (fn, up) + name, env = debug_getupvalue(fn, up) until name == '_ENV' or name == nil return env end end -local function invert (t) +local function invert(t) local i = {} - for k, v in pairs (t) do + for k, v in pairs(t) do i[v] = k end return i @@ -249,52 +249,52 @@ end -- Sort numbers first then asciibetically -local function keysort (a, b) - if type (a) == 'number' then - return type (b) ~= 'number' or a < b +local function keysort(a, b) + if type(a) == 'number' then + return type(b) ~= 'number' or a < b else - return type (b) ~= 'number' and tostring (a) < tostring (b) + return type(b) ~= 'number' and tostring(a) < tostring(b) end end -local function leaves (it, tr) - local function visit (n) - if type (n) == 'table' then - for _, v in it (n) do - visit (v) +local function leaves(it, tr) + local function visit(n) + if type(n) == 'table' then + for _, v in it(n) do + visit(v) end else - coroutine_yield (n) + coroutine_yield(n) end end - return coroutine_wrap (visit), tr + return coroutine_wrap(visit), tr end -local function merge (dest, src) - for k, v in pairs (src) do dest[k] = dest[k] or v end +local function merge(dest, src) + for k, v in pairs(src) do dest[k] = dest[k] or v end return dest end -local pack = table_pack or function (...) - return {n = select ('#', ...), ...} +local pack = table_pack or function(...) + return {n=select('#', ...), ...} end local fallbacks = { __index = { - open = function (x) return '{' end, - close = function (x) return '}' end, + open = function(x) return '{' end, + close = function(x) return '}' end, elem = tostring, - pair = function (x, kp, vp, k, v, kstr, vstr) return kstr .. '=' .. vstr end, - sep = function (x, kp, vp, kn, vn) + pair = function(x, kp, vp, k, v, kstr, vstr) return kstr .. '=' .. vstr end, + sep = function(x, kp, vp, kn, vn) return kp ~= nil and kn ~= nil and ',' or '' end, - sort = function (keys) return keys end, - term = function (x) - return type (x) ~= 'table' or getmetamethod (x, '__tostring') + sort = function(keys) return keys end, + term = function(x) + return type(x) ~= 'table' or getmetamethod(x, '__tostring') end, }, } @@ -308,57 +308,57 @@ local fallbacks = { -- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps -- Heavily modified by Simon Peyton Jones, Dec 96 -local function render (x, fns, roots) - fns = setmetatable (fns or {}, fallbacks) +local function render(x, fns, roots) + fns = setmetatable(fns or {}, fallbacks) roots = roots or {} - local function stop_roots (x) - return roots[x] or render (x, fns, copy (roots)) + local function stop_roots(x) + return roots[x] or render(x, fns, copy(roots)) end - if fns.term (x) then - return fns.elem (x) + if fns.term(x) then + return fns.elem(x) else - local buf, keys = {fns.open (x)}, {} -- pre-buffer table open - roots[x] = fns.elem (x) -- recursion protection + local buf, keys = {fns.open(x)}, {} -- pre-buffer table open + roots[x] = fns.elem(x) -- recursion protection - for k in pairs (x) do -- collect keys + for k in pairs(x) do -- collect keys keys[#keys + 1] = k end - keys = fns.sort (keys) + keys = fns.sort(keys) local pair, sep = fns.pair, fns.sep local kp, vp -- previous key and value - for _, k in ipairs (keys) do + for _, k in ipairs(keys) do local v = x[k] - buf[#buf + 1] = sep (x, kp, vp, k, v) -- | buffer << separator - buf[#buf + 1] = pair (x, kp, vp, k, v, stop_roots (k), stop_roots (v)) + buf[#buf + 1] = sep(x, kp, vp, k, v) -- | buffer << separator + buf[#buf + 1] = pair(x, kp, vp, k, v, stop_roots(k), stop_roots(v)) -- | buffer << key/value pair kp, vp = k, v end - buf[#buf + 1] = sep (x, kp, vp) -- buffer << trailing separator - buf[#buf + 1] = fns.close (x) -- buffer << table close + buf[#buf + 1] = sep(x, kp, vp) -- buffer << trailing separator + buf[#buf + 1] = fns.close(x) -- buffer << table close - return table_concat (buf) -- stringify buffer + return table_concat(buf) -- stringify buffer end end -local function sortkeys (t) - table_sort (t, keysort) +local function sortkeys(t) + table_sort(t, keysort) return t end -local function _setfenv (fn, env) +local function _setfenv(fn, env) -- Unwrap functable: - if type (fn) == 'table' then - fn = fn.call or (getmetatable (fn) or {}).__call + if type(fn) == 'table' then + fn = fn.call or(getmetatable(fn) or {}).__call end if debug_setfenv then - return debug_setfenv (fn, env) + return debug_setfenv(fn, env) else -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html @@ -366,11 +366,11 @@ local function _setfenv (fn, env) local up = 0 repeat up = up + 1 - name = debug_getupvalue (fn, up) + name = debug_getupvalue(fn, up) until name == '_ENV' or name == nil if name then - debug_upvaluejoin (fn, up, function () return name end, 1) - debug_setupvalue (fn, up, env) + debug_upvaluejoin(fn, up, function() return name end, 1) + debug_setupvalue(fn, up, env) end return fn @@ -378,18 +378,18 @@ local function _setfenv (fn, env) end -local function split (s, sep) +local function split(s, sep) local r, patt = {} if sep == '' then patt = '(.)' - table_insert (r, '') + table_insert(r, '') else - patt = '(.-)' .. (sep or '%s+') + patt = '(.-)' ..(sep or '%s+') end - local b, slen = 0, len (s) + local b, slen = 0, len(s) while b <= slen do - local e, n, m = string_find (s, patt, b + 1) - table_insert (r, m or s:sub (b + 1, slen)) + local e, n, m = string_find(s, patt, b + 1) + table_insert(r, m or s:sub(b + 1, slen)) b = n or slen + 1 end return r @@ -397,8 +397,8 @@ end local tostring_vtable = { - pair = function (x, kp, vp, k, v, kstr, vstr) - if k == 1 or type (k) == 'number' and k -1 == kp then + pair = function(x, kp, vp, k, v, kstr, vstr) + if k == 1 or type(k) == 'number' and k -1 == kp then return vstr end return kstr .. '=' .. vstr @@ -422,10 +422,10 @@ local tostring_vtable = { -- Also PUC-Rio Lua #operation can return any numerically indexed -- element with an immediately following nil valued element, which is -- non-deterministic for non-sequence tables. -len = function (x) - local m = getmetamethod (x, '__len') - if m then return m (x) end - if type (x) ~= 'table' then return #x end +len = function(x) + local m = getmetamethod(x, '__len') + if m then return m(x) end + if type(x) ~= 'table' then return #x end local n = #x for i = 1, n do @@ -435,9 +435,9 @@ len = function (x) end -getmetamethod = function (x, n) - local m = (getmetatable (x) or {})[n] - if callable (m) then return m end +getmetamethod = function(x, n) + local m =(getmetatable(x) or {})[n] + if callable(m) then return m end end @@ -463,7 +463,7 @@ return { ipairs = ipairs, pairs = pairs, - tostring = function (x) return render (x, tostring_vtable) end, + tostring = function(x) return render(x, tostring_vtable) end, base = { copy = copy, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index b12316a..5089730 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -43,7 +43,7 @@ _ = nil --- Control std.debug function behaviour. -- To declare debugging state, set _DEBUG either to `false` to disable all --- runtime debugging; to any "truthy" value (equivalent to enabling everything +-- runtime debugging; to any "truthy" value(equivalent to enabling everything -- except *call*, or as documented below. -- @class table -- @name _DEBUG @@ -55,40 +55,40 @@ _ = nil -- value causes deprecated APIs not to be defined at all -- @tfield[opt=1] int level debugging level -- @tfield[opt=true] boolean strict enforce strict variable declaration --- before use **in stdlib internals** (if `require 'strict'` works) +-- before use **in stdlib internals**(if `require 'strict'` works) -- @usage --- _DEBUG = { argcheck = false, level = 9, strict = false } +-- _DEBUG = {argcheck=false, level=9, strict=false} -local function say (n, ...) +local function say(n, ...) local level, argt = n, {...} - if type (n) ~= 'number' then + if type(n) ~= 'number' then level, argt = 1, {n, ...} end if _DEBUG.level ~= math_huge and - ((type (_DEBUG.level) == 'number' and _DEBUG.level >= level) or level <= 1) + ((type(_DEBUG.level) == 'number' and _DEBUG.level >= level) or level <= 1) then local t = {} - for k, v in _pairs (argt) do t[k] = _tostring (v) end - io_stderr:write (table_concat (t, '\t') .. '\n') + for k, v in _pairs(argt) do t[k] = _tostring(v) end + io_stderr:write(table_concat(t, '\t') .. '\n') end end local level = 0 -local function trace (event) - local t = debug.getinfo (3) +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) + t = debug.getinfo(2) if event == 'call' then level = level + 1 else - level = math_max (level - 1, 0) + level = math_max(level - 1, 0) end if t.what == 'main' then if event == 'call' then @@ -97,17 +97,17 @@ local function trace (event) s = s .. 'end ' .. t.short_src end elseif t.what == 'Lua' then - s = s .. event .. ' ' .. (t.name or '(Lua)') .. ' <' .. + s = s .. event .. ' ' ..(t.name or '(Lua)') .. ' <' .. t.linedefined .. ':' .. t.short_src .. '>' else - s = s .. event .. ' ' .. (t.name or '(C)') .. ' [' .. t.what .. ']' + s = s .. event .. ' ' ..(t.name or '(C)') .. ' [' .. t.what .. ']' end - io_stderr:write (s .. '\n') + io_stderr:write(s .. '\n') end -- Set hooks according to _DEBUG -if type (_DEBUG) == 'table' and _DEBUG.call then - debug.sethook (trace, 'cr') +if type(_DEBUG) == 'table' and _DEBUG.call then + debug.sethook(trace, 'cr') end @@ -140,21 +140,21 @@ local M = { -- nil, nothing is written. -- @function say -- @int[opt=1] n debugging level, smaller is higher priority - -- @param ... objects to print (as for print) + -- @param ... objects to print(as for print) -- @usage -- local _DEBUG = require 'std.debug_init'._DEBUG -- _DEBUG.level = 3 - -- say (2, '_DEBUG table contents:', _DEBUG) + -- say(2, '_DEBUG table contents:', _DEBUG) say = say, --- Trace function calls. - -- Use as debug.sethook (trace, 'cr'), which is done automatically + -- Use as debug.sethook(trace, 'cr'), which is done automatically -- when `_DEBUG.call` is set. -- Based on test/trace-calls.lua from the Lua distribution. -- @function trace -- @string event event causing the call -- @usage - -- _DEBUG = { call = true } + -- _DEBUG = {call=true} -- local debug = require 'std.debug' trace = trace, } @@ -163,17 +163,17 @@ local M = { --- Metamethods -- @section metamethods ---- Equivalent to calling `debug.say (1, ...)` +--- 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, + __call = function(self, ...) + M.say(1, ...) + end, } -return setmetatable (merge (M, debug), metatable) +return setmetatable(merge(M, debug), metatable) diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index 18ec812..7342d0b 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -6,8 +6,8 @@ ]] -local function choose (t) - for k, v in pairs (t) do +local function choose(t) + for k, v in pairs(t) do if _DEBUG == false then t[k] = v.fast elseif _DEBUG == nil then @@ -26,10 +26,10 @@ end return { _DEBUG = choose { - argcheck = { default = true, safe = true, fast = false}, - call = { default = false, safe = false, fast = false}, - deprecate = { default = nil, safe = true, fast = false}, - level = { default = 1, safe = 1, fast = math.huge}, - strict = { default = true, safe = true, fast = false}, + argcheck = { default=true, safe=true, fast=false}, + call = { default=false, safe=false, fast=false}, + deprecate = { default=nil, safe=true, fast=false}, + level = { default=1, safe=1, fast=math.huge}, + strict = { default=true, safe=true, fast=false}, }, } diff --git a/lib/std/init.lua b/lib/std/init.lua index 73bc703..dbd8b11 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -6,9 +6,9 @@ changes to any global symbols, or monkey patching of core module tables and metatables. - @todo Write a style guide (indenting/wrapping, capitalisation, + @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). + error, not die; OO vs non-OO(a thorny problem). @todo pre-compile. @corefunction std ]] @@ -56,43 +56,43 @@ _ = nil local M -local function _assert (expect, fmt, arg1, ...) - local msg = (arg1 ~= nil) and string_format (fmt, arg1, ...) or fmt or '' - return expect or error (msg, 2) +local function _assert(expect, fmt, arg1, ...) + local msg =(arg1 ~= nil) and string_format(fmt, arg1, ...) or fmt or '' + return expect or error(msg, 2) end -local function elems (t) +local function elems(t) -- capture _pairs iterator initial state - local fn, istate, ctrl = _pairs (t) - return function (state, _) + local fn, istate, ctrl = _pairs(t) + return function(state, _) local v - ctrl, v = fn (state, ctrl) + ctrl, v = fn(state, ctrl) if ctrl then return v end end, istate, true -- wrapped initial state end -local function eval (s) - return loadstring ('return ' .. s)() +local function eval(s) + return loadstring('return ' .. s)() end -local function ielems (t) +local function ielems(t) -- capture _pairs iterator initial state - local fn, istate, ctrl = _ipairs (t) - return function (state, _) + local fn, istate, ctrl = _ipairs(t) + return function(state, _) local v - ctrl, v = fn (state, ctrl) + 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) +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, @@ -100,13 +100,13 @@ local function npairs (t) end -local function ripairs (t) +local function ripairs(t) local oob = 1 while t[oob] ~= nil do oob = oob + 1 end - return function (t, n) + return function(t, n) n = n - 1 if n > 0 then return n, t[n] @@ -115,11 +115,11 @@ local function ripairs (t) end -local function rnpairs (t) - local m = getmetamethod (t, '__len') - local oob = (m and m (t) or maxn (t)) + 1 +local function rnpairs(t) + local m = getmetamethod(t, '__len') + local oob =(m and m(t) or maxn(t)) + 1 - return function (t, n) + return function(t, n) n = n - 1 if n > 0 then return n, t[n] @@ -128,35 +128,35 @@ local function rnpairs (t) end -local vconvert = setmetatable ({ - string = function (x) return split (x, '%.') end, - number = function (x) return {x} end, - table = function (x) return x 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 + __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)) +local function vcompare(a, b) + return compare(vconvert(a), vconvert(b)) end -local function _require (module, min, too_big, pattern) +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 = string_match (s, pattern) or 0 + local s, m = '', require(module) + if type(m) == 'table' then s = tostring(m.version or m._VERSION or '') end + local v = string_match(s, pattern) or 0 if min then - _assert (vcompare (v, min) >= 0, "require '" .. module .. + _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 .. + _assert(vcompare(v, too_big) < 0, "require '" .. module .. "' with version less than " .. too_big .. ', but found version ' .. v) end return m @@ -169,8 +169,8 @@ end --[[ ================= ]]-- -local function X (decl, fn) - return argscheck and argscheck ('std.' .. decl, fn) or fn +local function X(decl, fn) + return argscheck and argscheck('std.' .. decl, fn) or fn end M = { @@ -188,9 +188,9 @@ M = { -- @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), + -- 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 @@ -198,8 +198,8 @@ M = { -- @return result of evaluating `s` -- @usage -- --> 2 - -- std.eval 'math.min (2, 10)' - eval = X ('eval (string)', eval), + -- 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 @@ -211,8 +211,8 @@ M = { -- @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), + -- clone = std.getmetamethod(std.object.prototype, '__call') + getmetamethod = X('getmetamethod(?any, string)', getmetamethod), --- Enhance core `tostring` to render table contents as a string. -- @function tostring @@ -220,26 +220,26 @@ M = { -- @treturn string compact string rendering of *x* -- @usage -- -- {1=baz,foo=bar} - -- print (std.tostring {foo='bar','baz'}) - tostring = X ('tostring (?any)', _tostring), + -- print(std.tostring {foo='bar','baz'}) + tostring = X('tostring(?any)', _tostring), --- Module Functions -- @section modulefuncs --- Enhance core `require` to assert version number compatibility. - -- By default match against the last substring of (dot-delimited) + -- 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*$'`) + -- `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), + -- posix = require('posix', '29') + require = X('require(string, ?string, ?string, ?string)', _require), --- Iterator Functions -- @section iteratorfuncs @@ -258,8 +258,8 @@ M = { -- --> bar -- --> baz -- --> 5 - -- std.functional.map (print, std.ielems, {'foo', 'bar', [4]='baz', d=5}) - elems = X ('elems (table)', elems), + -- 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. -- @@ -277,8 +277,8 @@ M = { -- @usage -- --> foo -- --> bar - -- std.functional.map (print, std.ielems, {'foo', 'bar', [4]='baz', d=5}) - ielems = X ('ielems (table)', ielems), + -- 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. -- @@ -301,8 +301,8 @@ M = { -- @usage -- --> 1 foo -- --> 2 bar - -- std.functional.map (print, std.ipairs, {'foo', 'bar', [4]='baz', d=5}) - ipairs = X ('ipairs (table)', _ipairs), + -- 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*. @@ -317,8 +317,8 @@ M = { -- --> 2 bar -- --> 3 nil -- --> 4 baz - -- std.functional.map (print, std.npairs, {'foo', 'bar', [4]='baz', d=5}) - npairs = X ('npairs (table)', npairs), + -- 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 @@ -333,8 +333,8 @@ M = { -- --> 2 bar -- --> 4 baz -- --> d 5 - -- std.functional.map (print, std.pairs, {'foo', 'bar', [4]='baz', d=5}) - pairs = X ('pairs (table)', _pairs), + -- 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 @@ -349,8 +349,8 @@ M = { -- @usage -- --> 2 bar -- --> 1 foo - -- std.functional.map (print, std.ripairs, {'foo', 'bar', [4]='baz', d=5}) - ripairs = X ('ripairs (table)', ripairs), + -- 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 @@ -366,15 +366,15 @@ M = { -- --> 3 nil -- --> 2 bar -- --> 1 foo - -- std.functional.map (print, std.rnpairs, {'foo', 'bar', [4]='baz', d=5}) - rnpairs = X ('rnpairs (table)', rnpairs), + -- std.functional.map(print, std.rnpairs, {'foo', 'bar', [4]='baz', d=5}) + rnpairs = X('rnpairs(table)', rnpairs), } --- Metamethods -- @section Metamethods -return setmetatable (M, { +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. @@ -385,10 +385,10 @@ return setmetatable (M, { -- @usage -- local std = require 'std' -- local Object = std.object.prototype - __index = function (self, name) - local ok, t = pcall (require, 'std.' .. name) + __index = function(self, name) + local ok, t = pcall(require, 'std.' .. name) if ok then - rawset (self, name, t) + rawset(self, name, t) return t end end, diff --git a/lib/std/io.lua b/lib/std/io.lua index 52c3f00..5c3c065 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -60,95 +60,95 @@ _ = nil local M -local function input_handle (h) +local function input_handle(h) if h == nil then - return io_input () - elseif type (h) == 'string' then - return io_open (h) + return io_input() + elseif type(h) == 'string' then + return io_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 +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 () + 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 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 + for line in h:lines() do l[#l + 1] = line end - h:close () + h:close() return l end -local function writelines (h, ...) - if io_type (h) ~= 'file' then - io_write (h, '\n') - h = io_output () +local function writelines(h, ...) + if io_type(h) ~= 'file' then + io_write(h, '\n') + h = io_output() end - for v in leaves (_ipairs, {...}) do - h:write (v, '\n') + for v in leaves(_ipairs, {...}) do + h:write(v, '\n') end end -local function process_files (fn) +local function process_files(fn) -- N.B. 'arg' below refers to the global array of command-line args - if len (arg) == 0 then - table_insert (arg, '-') + if len(arg) == 0 then + table_insert(arg, '-') end - for i, v in _ipairs (arg) do + for i, v in _ipairs(arg) do if v == '-' then - io_input (io_stdin) + io_input(io_stdin) else - io_input (v) + io_input(v) end - fn (v, i) + fn(v, i) end end -local function warnfmt (msg, ...) +local function warnfmt(msg, ...) local prefix = '' - local prog = rawget (_G, 'prog') or {} - local opts = rawget (_G, 'opts') or {} + 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 .. _tostring (prog.line) .. ':' + prefix = prefix .. _tostring(prog.line) .. ':' end elseif prog.file then prefix = prog.file .. ':' if prog.line then - prefix = prefix .. _tostring (prog.line) .. ':' + prefix = prefix .. _tostring(prog.line) .. ':' end elseif opts.program then prefix = opts.program .. ':' if opts.line then - prefix = prefix .. _tostring (opts.line) .. ':' + prefix = prefix .. _tostring(opts.line) .. ':' end end if #prefix > 0 then prefix = prefix .. ' ' end - return prefix .. string_format (msg, ...) + return prefix .. string_format(msg, ...) end -local function warn (msg, ...) - writelines (io_stderr, warnfmt (msg, ...)) +local function warn(msg, ...) + writelines(io_stderr, warnfmt(msg, ...)) end @@ -158,8 +158,8 @@ end --[[ ================= ]]-- -local function X (decl, fn) - return argscheck and argscheck ('std.io.' .. decl, fn) or fn +local function X(decl, fn) + return argscheck and argscheck('std.io.' .. decl, fn) or fn end @@ -175,12 +175,12 @@ M = { -- @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) + -- 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). + --- 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 @@ -192,11 +192,11 @@ M = { -- @usage -- local OptionParser = require 'std.optparse' -- local parser = OptionParser 'eg 0\nUsage: eg\n' - -- _G.arg, _G.opts = parser:parse (_G.arg) + -- _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), + warn = X('warn(string, [any...])', warn), --- Path Functions @@ -208,9 +208,9 @@ M = { -- @return path without trailing separator -- @see catfile -- @usage - -- dirpath = catdir ('', 'absolute', 'directory') - catdir = X ('catdir (string...)', function (...) - return (table_concat ({...}, dirsep):gsub('^$', dirsep)) + -- dirpath = catdir('', 'absolute', 'directory') + catdir = X('catdir(string...)', function(...) + return(table_concat({...}, dirsep):gsub('^$', dirsep)) end), --- Concatenate one or more directories and a filename into a path. @@ -220,8 +220,8 @@ M = { -- @see catdir -- @see splitdir -- @usage - -- filepath = catfile ('relative', 'path', 'filename') - catfile = X ('catfile (string...)', catfile), + -- filepath = catfile('relative', 'path', 'filename') + catfile = X('catfile(string...)', catfile), --- Remove the last dirsep delimited element from a path. -- @function dirname @@ -230,8 +230,8 @@ M = { -- truncated -- @usage -- dir = dirname '/base/subdir/filename' - dirname = X ('dirname (string)', function (path) - return (path:gsub (catfile ('', '[^', ']*$'), '')) + dirname = X('dirname(string)', function(path) + return(path:gsub(catfile('', '[^', ']*$'), '')) end), --- Split a directory path into components. @@ -241,9 +241,9 @@ M = { -- @return list of path components -- @see catdir -- @usage - -- dir_components = splitdir (filepath) - splitdir = X ('splitdir (string)', function (path) - return split (path, dirsep) + -- dir_components = splitdir(filepath) + splitdir = X('splitdir(string)', function(path) + return split(path, dirsep) end), @@ -262,8 +262,8 @@ M = { -- #! /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), + -- 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. @@ -273,7 +273,7 @@ M = { -- @treturn list lines -- @usage -- list = readlines '/etc/passwd' - readlines = X ('readlines (?file|string)', readlines), + readlines = X('readlines(?file|string)', readlines), --- Perform a shell command and return its output. -- @function shell @@ -282,7 +282,7 @@ M = { -- @see os.execute -- @usage -- users = shell [[cat /etc/passwd | awk -F: '{print $1;}']] - shell = X ('shell (string)', function (c) return slurp (io_popen (c)) end), + shell = X('shell(string)', function(c) return slurp(io_popen(c)) end), --- Slurp a file handle. -- @function slurp @@ -291,21 +291,21 @@ M = { -- @return contents of file or handle, or nil if error -- @see process_files -- @usage - -- contents = slurp (filename) - slurp = X ('slurp (?file|string)', slurp), + -- 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) + -- @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), + -- writelines(io.stdout, 'first line', 'next line') + writelines = X('writelines(?file|string|number, [string|number...])', writelines), } -return merge (M, io) +return merge(M, io) @@ -317,7 +317,7 @@ return merge (M, io) -- @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') +-- local fileprocessor = function(filename, i) +-- io.write(tostring(i) .. ':\n===\n' .. io.slurp(filename) .. '\n') -- end --- io.process_files (fileprocessor) +-- io.process_files(fileprocessor) diff --git a/lib/std/math.lua b/lib/std/math.lua index 7bf3a1e..de71e01 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -35,18 +35,18 @@ _ = nil local M -local function floor (n, p) - if (p or 0) == 0 then - return math_floor (n) +local function floor(n, p) + if(p or 0) == 0 then + return math_floor(n) end local e = 10 ^ p - return math_floor (n * e) / e + return math_floor(n * e) / e end -local function round (n, p) - local e = 10 ^ (p or 0) - return math_floor (n * e + 0.5) / e +local function round(n, p) + local e = 10 ^(p or 0) + return math_floor(n * e + 0.5) / e end @@ -56,8 +56,8 @@ end --[[ ================= ]]-- -local function X (decl, fn) - return argscheck and argscheck ('std.math.' .. decl, fn) or fn +local function X(decl, fn) + return argscheck and argscheck('std.math.' .. decl, fn) or fn end @@ -71,8 +71,8 @@ M = { -- @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), + -- tenths = floor(magnitude, 1) + floor = X('floor(number, ?int)', floor), --- Round a number to a given number of decimal places. -- @function round @@ -80,9 +80,9 @@ M = { -- @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), + -- roughly = round(exactly, 2) + round = X('round(number, ?int)', round), } -return merge (M, math) +return merge(M, math) diff --git a/lib/std/package.lua b/lib/std/package.lua index 1cb6da6..056cd16 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -12,25 +12,25 @@ onto `package.dirsep` and `package.path_mark`, for easy addition of new paths. For example, instead of all this: - lib = std.io.catfile ('.', 'lib', package.path_mark .. '.lua') - paths = std.string.split (package.path, package.pathsep) - for i, path in ipairs (paths) do + 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) + table.remove(paths, i) else i = i + 1 end end - table.insert (paths, 1, lib) - package.path = table.concat (paths, package.pathsep) + 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) + package.path = package.normalize('./lib/?.lua', package.path) @corelibrary std.package ]] @@ -74,61 +74,61 @@ _ = nil -- @string dirsep directory separator -- @string pathsep path separator -- @string path_mark string that marks substitution points in a path template --- @string execdir (Windows only) replaced by the executable's directory in a path +-- @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 dirsep, pathsep, path_mark, execdir, igmark = - string_match (package_config, '^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)') + string_match(package_config, '^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)') -local function pathsub (path) - return path:gsub ('%%?.', function (capture) +local function pathsub(path) + return path:gsub('%%?.', function(capture) if capture == '?' then return path_mark elseif capture == '/' then return dirsep else - return capture:gsub ('^%%', '', 1) + return capture:gsub('^%%', '', 1) end end) end -local function find (pathstrings, patt, init, plain) - local paths = split (pathstrings, pathsep) - if plain then patt = escape_pattern (patt) 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 paths[i]:find (patt) then return i, paths[i] end + if paths[i]:find(patt) then return i, paths[i] end end end -local function normalize (...) - local i, paths, pathstrings = 1, {}, table_concat ({...}, pathsep) - for _, path in ipairs (split (pathstrings, pathsep)) do - path = pathsub (path): - gsub (catfile ('^[^', ']'), catfile ('.', '%0')): - gsub (catfile ('', '%.', ''), dirsep): - gsub (catfile ('', '%.$'), ''): - gsub (catfile ('^%.', '%..', ''), catfile ('..', '')): - gsub (catfile ('', '$'), '') +local function normalize(...) + local i, paths, pathstrings = 1, {}, table_concat({...}, pathsep) + for _, path in ipairs(split(pathstrings, pathsep)) do + path = pathsub(path): + gsub(catfile('^[^', ']'), catfile('.', '%0')): + gsub(catfile('', '%.', ''), dirsep): + gsub(catfile('', '%.$'), ''): + gsub(catfile('^%.', '%..', ''), catfile('..', '')): + gsub(catfile('', '$'), '') -- Carefully remove redundant /foo/../ matches. repeat local again = false - path = path:gsub (catfile ('', '([^', ']+)', '%.%.', ''), - function (dir1) + path = path:gsub(catfile('', '([^', ']+)', '%.%.', ''), + function(dir1) if dir1 == '..' then -- don't remove /../../ - return catfile ('', '..', '..', '') + return catfile('', '..', '..', '') else again = true return dirsep end - end):gsub (catfile ('', '([^', ']+)', '%.%.$'), - function (dir1) + end):gsub(catfile('', '([^', ']+)', '%.%.$'), + function(dir1) if dir1 == '..' then -- don't remove /../.. - return catfile ('', '..', '..') + return catfile('', '..', '..') else again = true return '' @@ -142,29 +142,29 @@ local function normalize (...) paths[path], i = i, i + 1 end end - return table_concat (invert (paths), pathsep) + return table_concat(invert(paths), pathsep) end -local function insert (pathstrings, ...) - local paths = split (pathstrings, pathsep) - table_insert (paths, ...) - return normalize (table_unpack (paths, 1, len (paths))) +local function insert(pathstrings, ...) + local paths = split(pathstrings, pathsep) + table_insert(paths, ...) + return normalize(table_unpack(paths, 1, len(paths))) end -local function mappath (pathstrings, callback, ...) - for _, path in ipairs (split (pathstrings, pathsep)) do - local r = callback (path, ...) +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 table_concat (paths, pathsep) +local function remove(pathstrings, pos) + local paths = split(pathstrings, pathsep) + table_remove(paths, pos) + return table_concat(paths, pathsep) end @@ -174,8 +174,8 @@ end --[[ ================= ]]-- -local function X (decl, fn) - return argscheck and argscheck ('std.package.' .. decl, fn) or fn +local function X(decl, fn) + return argscheck and argscheck('std.package.' .. decl, fn) or fn end @@ -184,16 +184,16 @@ local M = { -- @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. + -- @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 + -- @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), + -- 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 @@ -203,8 +203,8 @@ local M = { -- @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), + -- 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 @@ -213,21 +213,21 @@ local M = { -- @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), + -- 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 - -- `path_mark` (unless immediately preceded by a `%` character). + -- `path_mark`(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), + -- 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 @@ -236,8 +236,8 @@ local M = { -- 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), + -- package.path = remove(package.path) + remove = X('remove(string, ?int)', remove), } @@ -248,7 +248,7 @@ M.path_mark = path_mark M.pathsep = pathsep -return merge (M, package) +return merge(M, package) --- Types diff --git a/lib/std/string.lua b/lib/std/string.lua index ce036fb..c68c0ef 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -52,14 +52,14 @@ _ = nil local M -local function __concat (s, o) - return _tostring (s) .. _tostring (o) +local function __concat(s, o) + return _tostring(s) .. _tostring(o) end -local function __index (s, i) - if type (i) == 'number' then - return s:sub (i, i) +local function __index(s, i) + if type(i) == 'number' then + return s:sub(i, i) else -- Fall back to module metamethods return M[i] @@ -69,28 +69,28 @@ end local _format = string.format -local function format (f, arg1, ...) - return (arg1 ~= nil) and _format (f, arg1, ...) or f +local function format(f, arg1, ...) + return(arg1 ~= nil) and _format(f, arg1, ...) or f end -local function tpack (from, to, ...) +local function tpack(from, to, ...) return from, to, {...} end -local function tfind (s, ...) - return tpack (s:find (...)) +local function tfind(s, ...) + return tpack(s:find(...)) end -local function finds (s, p, i, ...) +local function finds(s, p, i, ...) i = i or 1 local l = {} local from, to, r repeat - from, to, r = tfind (s, p, i, ...) + from, to, r = tfind(s, p, i, ...) if from ~= nil then - table_insert (l, {from, to, capt = r}) + table_insert(l, {from, to, capt=r}) i = to + 1 end until not from @@ -98,18 +98,18 @@ local function finds (s, p, i, ...) end -local function caps (s) - return (s:gsub ('(%w)([%w]*)', function (l, ls) return l:upper () .. ls end)) +local function caps(s) + return(s:gsub('(%w)([%w]*)', function(l, ls) return l:upper() .. ls end)) end -local function escape_shell (s) - return (s:gsub ('([ %(%)%\\%[%]\'"])', '\\%1')) +local function escape_shell(s) + return(s:gsub('([ %(%)%\\%[%]\'"])', '\\%1')) end -local function ordinal_suffix (n) - n = math_abs (n) % 100 +local function ordinal_suffix(n) + n = math_abs(n) % 100 local d = n % 10 if d == 1 and n ~= 11 then return 'st' @@ -123,84 +123,84 @@ local function ordinal_suffix (n) end -local function pad (s, w, p) - p = string.rep (p or ' ', math_abs (w)) +local function pad(s, w, p) + p = string.rep(p or ' ', math_abs(w)) if w < 0 then - return string.sub (p .. s, w) + return string.sub(p .. s, w) end - return string.sub (s .. p, 1, w) + return string.sub(s .. p, 1, w) end -local function wrap (s, w, ind, ind1) +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, + 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) + 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 + 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 - table_insert (r, s:sub (i, j)) + table_insert(r, s:sub(i, j)) i = ni if i < lens then - table_insert (r, '\n' .. string.rep (' ', ind)) + table_insert(r, '\n' .. string.rep(' ', ind)) lstart = ind end end - return table_concat (r) + return table_concat(r) end -local function numbertosi (n) +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' + [-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 t = _format('% #.2e', n) local _, _, m, e = t:find('.(.%...)e(.+)') - local man, exp = tonumber (m), tonumber (e) - local siexp = math_floor (exp / 3) + local man, exp = tonumber(m), tonumber(e) + local siexp = math_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 + local s = SIprefix[siexp] or 'e' .. tostring(siexp) + man = man *(10 ^ shift) + return _format('%0.f', man) .. s end -local function prettytostring (x, indent, spacing) +local function prettytostring(x, indent, spacing) indent = indent or '\t' spacing = spacing or '' - return render (x, { - open = function () + return render(x, { + open = function() local s = spacing .. '{' spacing = spacing .. indent return s end, - close = function () - spacing = string.gsub (spacing, indent .. '$', '') + close = function() + spacing = string.gsub(spacing, indent .. '$', '') return spacing .. '}' end, - elem = function (x) - if type (x) ~= 'string' then return tostring (x) end - return string_format ('%q', x) + elem = function(x) + if type(x) ~= 'string' then return tostring(x) end + return string_format('%q', x) end, - pair = function (x, _, _, k, v, kstr, vstr) - local type_k = type (k) + pair = function(x, _, _, k, v, kstr, vstr) + local type_k = type(k) local s = spacing if type_k ~= 'string' or k:match '[^%w_]' then s = s .. '[' @@ -216,7 +216,7 @@ local function prettytostring (x, indent, spacing) s = s .. k end s = s .. ' =' - if type (v) == 'table' then + if type(v) == 'table' then s = s .. '\n' else s = s .. ' ' @@ -225,7 +225,7 @@ local function prettytostring (x, indent, spacing) return s end, - sep = function (_, k) + sep = function(_, k) local s = '\n' if k then s = ',' .. s @@ -238,9 +238,9 @@ local function prettytostring (x, indent, spacing) end -local function trim (s, r) +local function trim(s, r) r = r or '%s+' - return (s:gsub ('^' .. r, ''):gsub (r .. '$', '')) + return(s:gsub('^' .. r, ''):gsub(r .. '$', '')) end @@ -250,8 +250,8 @@ end --[[ ================= ]]-- -local function X (decl, fn) - return argscheck and argscheck ('std.string.' .. decl, fn) or fn +local function X(decl, fn) + return argscheck and argscheck('std.string.' .. decl, fn) or fn end M = { @@ -262,9 +262,9 @@ M = { -- @function __concat -- @string s initial string -- @param o object to stringify and concatenate - -- @return s .. tostring (o) + -- @return s .. tostring(o) -- @usage - -- local string = setmetatable ('', require 'std.string') + -- local string = setmetatable('', require 'std.string') -- concatenated = 'foo' .. {'bar'} __concat = __concat, @@ -272,11 +272,11 @@ M = { -- @function __index -- @string s string -- @tparam int|string i index or method name - -- @return `s:sub (i, i)` if i is a number, otherwise - -- fall back to a `std.string` metamethod (if any). + -- @return `s:sub(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] + -- getmetatable('').__index = require 'std.string'.__index + -- third =('12345')[3] __index = __index, @@ -288,24 +288,24 @@ M = { -- @string s any string -- @treturn string *s* with each word capitalized -- @usage - -- userfullname = caps (input_string) - caps = X ('caps (string)', caps), + -- 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 (s:gsub ('\n$', '')) end), + -- line = chomp(line) + chomp = X('chomp(string)', function(s) return(s:gsub('\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 = inputstr:match (escape_pattern (literal)) - escape_pattern = X ('escape_pattern (string)', escape_pattern), + -- substr = inputstr:match(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 @@ -314,8 +314,8 @@ M = { -- @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), + -- os.execute('echo ' .. escape_shell(outputstr)) + escape_shell = X('escape_shell(string)', escape_shell), --- Repeatedly `string.find` until target string is exhausted. -- @function finds @@ -323,13 +323,13 @@ M = { -- @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}}` + -- @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)) + -- 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), + 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. @@ -338,8 +338,8 @@ M = { -- @param[opt] ... arguments to format -- @return formatted string -- @usage - -- print (format '100% stdlib!') - format = X ('format (string, [any...])', format), + -- print(format '100% stdlib!') + format = X('format(string, [any...])', format), --- Remove leading matter from a string. -- @function ltrim @@ -347,9 +347,9 @@ M = { -- @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 (s:gsub ('^' .. (r or '%s+'), '')) + -- print('got: ' .. ltrim(userinput)) + ltrim = X('ltrim(string, ?string)', function(s, r) + return(s:gsub('^' ..(r or '%s+'), '')) end), --- Write a number using SI suffixes. @@ -358,8 +358,8 @@ M = { -- @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), + -- print(numbertosi(bitspersecond) .. 'bps') + numbertosi = X('numbertosi(number|string)', numbertosi), --- Return the English suffix for an ordinal. -- @function ordinal_suffix @@ -367,21 +367,21 @@ M = { -- @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), + -- 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 + -- 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 + -- @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), + -- print(pad(trim(outputstr, 78)) .. '\n') + pad = X('pad(string, int, ?string)', pad), --- Pretty-print a table, or other object. -- @function prettytostring @@ -390,8 +390,8 @@ M = { -- @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), + -- print(prettytostring(std, ' ')) + prettytostring = X('prettytostring(?any, ?string, ?string)', prettytostring), --- Turn tables into strings with recursion detection. -- N.B. Functions calling render should not recurse, or recursion @@ -401,16 +401,16 @@ M = { -- @tparam[opt] rendercbs fns default rendering function overrides -- @return string representation of *x* -- @usage - -- function tostablestring (x) - -- return render (x, { - -- sort = function (keys) - -- table.sort (keys, lambda '=tostring (_1) < tostring (_2)') + -- function tostablestring(x) + -- return render(x, { + -- sort = function(keys) + -- table.sort(keys, lambda '=tostring(_1) < tostring(_2)') -- return keys -- end, -- }) -- end - render = X ('render (?any, ?table)', function (x, rendercbs, roots) - return render (x, rendercbs, roots) + render = X('render(?any, ?table)', function(x, rendercbs, roots) + return render(x, rendercbs, roots) end), --- Remove trailing matter from a string. @@ -419,9 +419,9 @@ M = { -- @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 (s:gsub ((r or '%s+') .. '$', '')) + -- print('got: ' .. rtrim(userinput)) + rtrim = X('rtrim(string, ?string)', function(s, r) + return(s:gsub((r or '%s+') .. '$', '')) end), --- Split a string at a given separator. @@ -433,7 +433,7 @@ M = { -- @return list of strings -- @usage -- words = split 'a very short sentence' - split = X ('split (string, ?string)', split), + split = X('split(string, ?string)', split), --- Do `string.find`, returning a table of captures. -- @function tfind @@ -446,8 +446,8 @@ M = { -- @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), + -- 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 @@ -455,8 +455,8 @@ M = { -- @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), + -- print('got: ' .. trim(userinput)) + trim = X('trim(string, ?string)', trim), --- Wrap a string into a paragraph. -- @function wrap @@ -466,12 +466,12 @@ M = { -- @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), + -- print(wrap(copyright, 72, 4)) + wrap = X('wrap(string, ?int, ?int, ?int)', wrap), } -return merge (M, string) +return merge(M, string) @@ -489,8 +489,8 @@ return merge (M, string) -- @tfield[opt] termcb term terminal predicate -- @see render -- @usage --- function tostringstable (x) --- return render (x, { sort = some_sequence_reordering_fn }) +-- function tostringstable(x) +-- return render(x, {sort=some_sequence_reordering_fn}) -- end @@ -500,7 +500,7 @@ return merge (M, string) -- @treturn string open table rendering -- @see render -- @usage --- function open (t) return '{' end +-- function open(t) return '{' end --- Signature of @{render} close table callback. @@ -509,7 +509,7 @@ return merge (M, string) -- @treturn string close table rendering -- @see render -- @usage --- function close (t) return '}' end +-- function close(t) return '}' end --- Signature of @{render} element callback. @@ -518,7 +518,7 @@ return merge (M, string) -- @treturn string element rendering -- @see render -- @usage --- function element (e) return require 'std'.tostring (e) end +-- function element(e) return require 'std'.tostring(e) end --- Signature of @{render} pair callback. @@ -533,7 +533,7 @@ return merge (M, string) -- @treturn string pair rendering -- @see render -- @usage --- function pair (_, _, _, key, value) return key .. '=' .. value end +-- function pair(_, _, _, key, value) return key .. '=' .. value end --- Signature of @{render} separator callback. @@ -545,7 +545,7 @@ return merge (M, string) -- @param fv *t* value following separator, or `nil` for last value -- @treturn string separator rendering -- @usage --- function separator (_, _, _, fk) return fk and ',' or '' end +-- function separator(_, _, _, fk) return fk and ',' or '' end --- Signature of @{render} key sorting callback. @@ -553,7 +553,7 @@ return merge (M, string) -- @tparam sequence keys all keys from rendering table -- @treturn sequence *keys* in desired display order -- @usage --- function unsorted (keys) return keys end +-- function unsorted(keys) return keys end --- Signature of @{render} terminal predicate callback. @@ -561,6 +561,6 @@ return merge (M, string) -- @param x an element to be rendered -- @treturn boolean whether *x* can be rendered by @{elementcb} -- @usage --- function term (x) --- return type (x) ~= 'table' or getmetamethod (x, '__tostring') +-- function term(x) +-- return type(x) ~= 'table' or getmetamethod(x, '__tostring') -- end diff --git a/lib/std/table.lua b/lib/std/table.lua index b402ac8..e1c9d9e 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -2,7 +2,7 @@ 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 + 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' @@ -51,92 +51,92 @@ _ = nil local M -local function merge_allfields (t, u, map, nometa) - if type (map) ~= 'table' then +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)) + setmetatable(t, getmetatable(u)) end if map then - for k, v in _pairs (u) do t[map[k] or k] = v end + 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 + 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 +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)) + setmetatable(t, getmetatable(u)) end - for _, k in _pairs (keys or {}) do t[k] = u[k] end + for _, k in _pairs(keys or {}) do t[k] = u[k] end return t end -local function depair (ls) +local function depair(ls) local t = {} - for _, v in _ipairs (ls) do + for _, v in _ipairs(ls) do t[v[1]] = v[2] end return t end -local function enpair (t) +local function enpair(t) local tt = {} - for i, v in _pairs (t) do + for i, v in _pairs(t) do tt[#tt + 1] = {i, v} end return tt end -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) +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 - table_insert (t, pos, v) + table_insert(t, pos, v) return t end -local function keys (t) +local function keys(t) local l = {} - for k in _pairs (t) do + 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) +local function new(x, t) + return setmetatable(t or {}, {__index = function(t, i) return x end}) end -local function project (fkey, tt) +local function project(fkey, tt) local r = {} - for _, t in _ipairs (tt) do + for _, t in _ipairs(tt) do r[#r + 1] = t[fkey] end return r end -local function size (t) +local function size(t) local n = 0 - for _ in _pairs (t) do + for _ in _pairs(t) do n = n + 1 end return n @@ -146,37 +146,37 @@ end -- Preserve core table sort function. local _sort = table.sort -local function sort (t, c) - _sort (t, c) +local function sort(t, c) + _sort(t, c) return t end local _remove = table.remove -local function remove (t, pos) - local lent = len (t) +local function remove(t, pos) + local lent = len(t) pos = pos or lent - if pos < math_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) + if pos < math_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) + return _remove(t, pos) end -local function unpack (t, i, j) +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) + local m = getmetamethod(t, '__len') + j = m and m(t) or maxn(t) end - return table_unpack (t, tonumber (i) or 1, tonumber (j)) + return table_unpack(t, tonumber(i) or 1, tonumber(j)) end -local function values (t) +local function values(t) local l = {} - for _, v in _pairs (t) do + for _, v in _pairs(t) do l[#l + 1] = v end return l @@ -189,8 +189,8 @@ end --[[ ================= ]]-- -local function X (decl, fn) - return argscheck and argscheck ('std.table.' .. decl, fn) or fn +local function X(decl, fn) + return argscheck and argscheck('std.table.' .. decl, fn) or fn end M = { @@ -203,13 +203,13 @@ M = { -- 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 + -- @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), + -- insert(insert({1, 2, 3}, 2, 'x'), 'y') + insert = X('insert(table, [int], any)', insert), --- Largest integer key in a table. -- @function maxn @@ -218,7 +218,7 @@ M = { -- @usage -- --> 42 -- maxn {'a', b='c', 99, [42]='x', 'x', [5]=67} - maxn = X ('maxn (table)', maxn), + maxn = X('maxn(table)', maxn), --- Turn a tuple into a list, with tuple-size in field `n` -- @function pack @@ -226,7 +226,7 @@ M = { -- @return list-like table, with tuple-size in field `n` -- @usage -- --> {1, 2, 'ax', n=3} - -- pack (('ax1'):find '(%D+)') + -- pack(('ax1'):find '(%D+)') pack = pack, --- Enhance core *table.remove* to respect `__len` when *pos* is omitted. @@ -234,13 +234,13 @@ M = { -- version of Lua. -- @function remove -- @tparam table t a table - -- @int[opt=len (t)] pos index from which to remove an element + -- @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), + -- remove(t, 3) == 'x' and t + remove = X('remove(table, ?int)', remove), --- Enhance core *table.sort* to return its result. -- @function sort @@ -248,8 +248,8 @@ M = { -- @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), + -- table.concat(sort(object)) + sort = X('sort(table, ?function)', sort), --- Enhance core *table.unpack* to always unpack up to __len or maxn. -- @function unpack @@ -258,8 +258,8 @@ M = { -- @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), + -- return unpack(results_table) + unpack = X('unpack(table, ?int, ?int)', unpack), --- Accessor Functions @@ -275,9 +275,9 @@ M = { -- @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 ({}, ...) + -- 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. @@ -292,9 +292,9 @@ M = { -- @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), + -- 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. @@ -305,26 +305,26 @@ M = { -- @usage -- --> {a=1, b=2, c=3} -- depair {{'a', 1}, {'b', 2}, {'c', 3}} - depair = X ('depair (list of lists)', depair), + 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}` + -- @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), + 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), + -- 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 @@ -332,8 +332,8 @@ M = { -- @tparam[opt={}] table t initial table -- @treturn table table whose unset elements are *x* -- @usage - -- t = new (0) - new = X ('new (?any, ?table)', new), + -- t = new(0) + new = X('new(?any, ?table)', new), --- Project a list of fields from a list of tables. -- @function project @@ -342,8 +342,8 @@ M = { -- @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), + -- 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 @@ -351,8 +351,8 @@ M = { -- @treturn int number of non-nil values in *t* -- @usage -- --> 3 - -- size {foo = true, bar = true, baz = false} - size = X ('size (table)', size), + -- size {foo=true, bar=true, baz=false} + size = X('size(table)', size), --- Make the list of values of a table. -- @function values @@ -362,7 +362,7 @@ M = { -- @usage -- --> {'a', 'c', 42} -- values {'a', b='c', [-1]=42} - values = X ('values (table)', values), + values = X('values(table)', values), --- Mutator Functions @@ -375,7 +375,7 @@ M = { -- @usage -- --> {a=1, b=2, c=3} -- invert {'a', 'b', 'c'} - invert = X ('invert (table)', invert), + invert = X('invert(table)', invert), --- Make the list of keys in table. -- @function keys @@ -383,8 +383,8 @@ M = { -- @treturn table list of keys from *t* -- @see values -- @usage - -- globals = keys (_G) - keys = X ('keys (table)', keys), + -- globals = keys(_G) + keys = X('keys(table)', keys), --- Destructively merge another table's fields into another. -- @function merge @@ -396,8 +396,8 @@ M = { -- @see clone -- @see merge_select -- @usage - -- merge (_G, require 'std.debug', {say = 'log'}, ':nometa') - merge = X ('merge (table, table, [table], ?boolean|:nometa)', merge_allfields), + -- 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*. -- @@ -412,13 +412,13 @@ M = { -- @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_select(_G, require 'std.debug', {'say'}, false) + merge_select = X('merge_select(table, table, [table], ?boolean|:nometa)', merge_namedfields), } -return merge (M, table) +return merge(M, table) @@ -432,5 +432,5 @@ return merge (M, table) -- @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) +-- local reversor = function(a, b) return a > b end +-- sort(t, reversor) diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index d2661a5..22f2e5c 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -3,27 +3,27 @@ before: | this_module = 'std.debug' global_table = '_G' - extend_base = { 'getfenv', 'setfenv', 'say', 'trace' } + extend_base = {'getfenv', 'setfenv', 'say', 'trace'} - M = require (this_module) + M = require(this_module) table_unpack = table.unpack or unpack - function map (mapfn, ...) - local argt, r = pack (...), {} + function map(mapfn, ...) + local argt, r = pack(...), {} - local nextfn, state, k = pairs (table_unpack (argt, 1, argt.n)) - local mapargs = pack (nextfn (state, k)) + local nextfn, state, k = pairs(table_unpack(argt, 1, argt.n)) + local mapargs = pack(nextfn(state, k)) local arity = 1 while mapargs[1] ~= nil do - local d, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) + local d, v = mapfn(table_unpack(mapargs, 1, mapargs.n)) if v ~= nil then arity, r = 2, {} break end r[#r + 1] = d - mapargs = {nextfn (state, mapargs[1])} + mapargs = {nextfn(state, mapargs[1])} end if arity > 1 then @@ -32,9 +32,9 @@ before: | -- (ii) arity used to be 1, but we only consumed nil values, so the -- current mapargs with arity > 1 is the correct next value to use while mapargs[1] ~= nil do - local k, v = mapfn (table_unpack (mapargs, 1, mapargs.n)) + local k, v = mapfn(table_unpack(mapargs, 1, mapargs.n)) r[k] = v - mapargs = pack (nextfn (state, mapargs[1])) + mapargs = pack(nextfn(state, mapargs[1])) end end return r @@ -45,216 +45,216 @@ 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}). + 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}). + 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) + 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'}). + 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'}). + expect(show_apis {added_to=base_module, by='std'}). to_equal {} - describe debug: - before: | - function mkwrap (k, v) + function mkwrap(k, v) local fmt = '%s' - if type (v) == 'string' then fmt = '%q' end - return k, string.format (fmt, require 'std'.tostring (v)) + if type(v) == 'string' then fmt = '%q' end + return k, string.format(fmt, require 'std'.tostring(v)) end - function mkdebug (debugp, ...) - return string.format ([[ + function mkdebug(debugp, ...) + return string.format([[ _DEBUG = %s - require 'std.debug' (%s) + require 'std.debug'(%s) ]], - require 'std'.tostring (debugp), - table.concat (map (mkwrap, {...}), ', ')) + require 'std'.tostring(debugp), + table.concat(map(mkwrap, {...}), ', ')) end - it does nothing when _DEBUG is disabled: - expect (luaproc (mkdebug (false, 'nothing to see here'))). + expect(luaproc(mkdebug(false, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - it writes to stderr when _DEBUG is not set: - expect (luaproc (mkdebug (nil, 'debugging'))). + expect(luaproc(mkdebug(nil, 'debugging'))). to_contain_error 'debugging' - it writes to stderr when _DEBUG is enabled: - expect (luaproc (mkdebug (true, 'debugging'))). + expect(luaproc(mkdebug(true, 'debugging'))). to_contain_error 'debugging' - it writes to stderr when _DEBUG.level is not set: - expect (luaproc (mkdebug ({}, 'debugging'))). + expect(luaproc(mkdebug({}, 'debugging'))). to_contain_error 'debugging' - it writes to stderr when _DEBUG.level is specified: - expect (luaproc (mkdebug ({level = 0}, 'debugging'))). + expect(luaproc(mkdebug({level=0}, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mkdebug ({level = 1}, 'debugging'))). + expect(luaproc(mkdebug({level=1}, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mkdebug ({level = 2}, 'debugging'))). + expect(luaproc(mkdebug({level=2}, 'debugging'))). to_contain_error 'debugging' - describe say: - before: | - function mkwrap (k, v) + function mkwrap(k, v) local fmt = '%s' - if type (v) == 'string' then fmt = '%q' end - return k, string.format (fmt, require 'std'.tostring (v)) + if type(v) == 'string' then fmt = '%q' end + return k, string.format(fmt, require 'std'.tostring(v)) end - function mksay (debugp, ...) - return string.format ([[ + function mksay(debugp, ...) + return string.format([[ _DEBUG = %s - require 'std.debug'.say (%s) + require 'std.debug'.say(%s) ]], - require 'std'.tostring (debugp), - table.concat (map (mkwrap, {...}), ', ')) + require 'std'.tostring(debugp), + table.concat(map(mkwrap, {...}), ', ')) end f = M.say - it uses stdlib tostring: - expect (luaproc [[require 'std.debug'.say {'debugging'}]]). - to_contain_error (require 'std'.tostring {'debugging'}) + expect(luaproc [[require 'std.debug'.say {'debugging'}]]). + to_contain_error(require 'std'.tostring {'debugging'}) - context when _DEBUG is disabled: - it does nothing when message level is not set: - expect (luaproc (mksay (false, 'nothing to see here'))). + expect(luaproc(mksay(false, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - it does nothing when message is set: - expect (luaproc (mksay (false, -999, 'nothing to see here'))). + expect(luaproc(mksay(false, -999, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - expect (luaproc (mksay (false, 0, 'nothing to see here'))). + expect(luaproc(mksay(false, 0, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - expect (luaproc (mksay (false, 1, 'nothing to see here'))). + expect(luaproc(mksay(false, 1, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - expect (luaproc (mksay (false, 2, 'nothing to see here'))). + expect(luaproc(mksay(false, 2, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - expect (luaproc (mksay (false, 999, 'nothing to see here'))). + expect(luaproc(mksay(false, 999, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - context when _DEBUG is not set: - it writes to stderr when message level is not set: - expect (luaproc (mksay (nil, 'debugging'))). + expect(luaproc(mksay(nil, 'debugging'))). to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect (luaproc (mksay (nil, -999, 'debugging'))). + expect(luaproc(mksay(nil, -999, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mksay (nil, 0, 'debugging'))). + expect(luaproc(mksay(nil, 0, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mksay (nil, 1, 'debugging'))). + expect(luaproc(mksay(nil, 1, 'debugging'))). to_contain_error 'debugging' - it does nothing when message level is 2 or higher: - expect (luaproc (mksay (nil, 2, 'nothing to see here'))). + expect(luaproc(mksay(nil, 2, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - expect (luaproc (mksay (nil, 999, 'nothing to see here'))). + expect(luaproc(mksay(nil, 999, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - context when _DEBUG is enabled: - it writes to stderr when message level is not set: - expect (luaproc (mksay (true, 'debugging'))). + expect(luaproc(mksay(true, 'debugging'))). to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect (luaproc (mksay (true, -999, 'debugging'))). + expect(luaproc(mksay(true, -999, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mksay (true, 0, 'debugging'))). + expect(luaproc(mksay(true, 0, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mksay (true, 1, 'debugging'))). + expect(luaproc(mksay(true, 1, 'debugging'))). to_contain_error 'debugging' - it does nothing when message level is 2 or higher: - expect (luaproc (mksay (true, 2, 'nothing to see here'))). + expect(luaproc(mksay(true, 2, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - expect (luaproc (mksay (true, 999, 'nothing to see here'))). + expect(luaproc(mksay(true, 999, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - context when _DEBUG.level is not set: - it writes to stderr when message level is not set: - expect (luaproc (mksay ({}, 'debugging'))). + expect(luaproc(mksay({}, 'debugging'))). to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect (luaproc (mksay ({}, -999, 'debugging'))). + expect(luaproc(mksay({}, -999, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mksay ({}, 0, 'debugging'))). + expect(luaproc(mksay({}, 0, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mksay ({}, 1, 'debugging'))). + expect(luaproc(mksay({}, 1, 'debugging'))). to_contain_error 'debugging' - it does nothing when message level is 2 or higher: - expect (luaproc (mksay ({}, 2, 'nothing to see here'))). + expect(luaproc(mksay({}, 2, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - expect (luaproc (mksay ({}, 999, 'nothing to see here'))). + expect(luaproc(mksay({}, 999, 'nothing to see here'))). not_to_contain_error 'nothing to see here' - context when _DEBUG.level is specified: - it writes to stderr when message level is 1 or lower: - expect (luaproc (mksay ({level = 0}, 'debugging'))). + expect(luaproc(mksay({level=0}, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mksay ({level = 1}, 'debugging'))). + expect(luaproc(mksay({level=1}, 'debugging'))). to_contain_error 'debugging' - expect (luaproc (mksay ({level = 2}, 'debugging'))). + expect(luaproc(mksay({level=2}, 'debugging'))). to_contain_error 'debugging' - it does nothing when message level is higher than debug level: - expect (luaproc (mksay ({level = 2}, 3, 'nothing to see here'))). + expect(luaproc(mksay({level=2}, 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 (mksay ({level = 2}, 2, 'debugging'))). + expect(luaproc(mksay({level=2}, 2, 'debugging'))). to_contain_error 'debugging' - it writes to stderr when message level is lower than debug level: - expect (luaproc (mksay ({level = 2}, 1, 'debugging'))). + expect(luaproc(mksay({level=2}, 1, 'debugging'))). to_contain_error 'debugging' - describe trace: - before: - f = init (M, this_module, 'trace') + f = init(M, this_module, 'trace') - it does nothing when _DEBUG is disabled: - expect (luaproc [[ + expect(luaproc [[ _DEBUG = false require 'std.debug' - os.exit (0) + os.exit(0) ]]).to_succeed_with '' - it does nothing when _DEBUG is not set: - expect (luaproc [[ + expect(luaproc [[ require 'std.debug' - os.exit (0) + os.exit(0) ]]).to_succeed_with '' - it does nothing when _DEBUG is enabled: - expect (luaproc [[ + expect(luaproc [[ _DEBUG = true require 'std.debug' - os.exit (0) + os.exit(0) ]]).to_succeed_with '' - it enables automatically when _DEBUG.call is set: | - expect (luaproc [[ - _DEBUG = {call = true} + expect(luaproc [[ + _DEBUG = {call=true} local debug = require 'std.debug' - os.exit (1) + os.exit(1) ]]).to_fail_while_containing ':3 call exit' - it is enabled manually with debug.sethook: | - expect (luaproc [[ + expect(luaproc [[ local debug = require 'std.debug' - debug.sethook (debug.trace, 'cr') - os.exit (1) + 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 [[ + expect(luaproc [[ local debug = require 'std.debug' - debug.sethook (debug.trace, 'cr') - os.exit (0) + 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 + 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 [[ + expect(luaproc [[ local debug = require 'std.debug' - local function incr (i) return i + 1 end - debug.sethook (debug.trace, 'cr') - os.exit (incr (41)) + 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/specs/io_spec.yaml b/specs/io_spec.yaml index 5941122..3c9df62 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -3,34 +3,34 @@ before: | this_module = 'std.io' global_table = '_G' - extend_base = { 'catdir', 'catfile', 'die', 'dirname', - 'process_files', 'readlines', 'shell', 'slurp', - 'splitdir', 'warn', 'writelines' } + extend_base = {'catdir', 'catfile', 'die', 'dirname', + 'process_files', 'readlines', 'shell', 'slurp', + 'splitdir', 'warn', 'writelines'} - dirsep = string.match (package.config, '^([^\n]+)\n') + dirsep = string.match(package.config, '^([^\n]+)\n') - M = require (this_module) + 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}). + 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}). + 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) + 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'}). + 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'}). + expect(show_apis {added_to=base_module, by='std'}). to_equal {} @@ -39,18 +39,18 @@ specify std.io: f = M.catdir - context with bad arguments: - badargs.diagnose (f, 'std.io.catdir (string*)') + 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') + 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' + 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)) + 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: @@ -58,18 +58,18 @@ specify std.io: f = M.catfile - context with bad arguments: - badargs.diagnose (f, 'std.io.catfile (string*)') + 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') + 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' + 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)) + 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: @@ -79,69 +79,69 @@ specify std.io: f = M.die - context with bad arguments: - badargs.diagnose (f, 'std.io.die (string, ?any*)') + badargs.diagnose(f, 'std.io.die(string, ?any*)') - it outputs a message to stderr: | - expect (luaproc (script)).to_fail_while_matching ": By 'eck!\n" + 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" + 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" + 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" + 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" + 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" + 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" + 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' } + prog = {file='file', name='name'} + opts = {program='program'} ]] .. script - expect (luaproc (script)).to_fail_while_matching ": name: By 'eck!\n" + 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' } + 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" + 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' } + prog = {file='file'}; opts = {program='program'} ]] .. script - expect (luaproc (script)).to_fail_while_matching ": file: By 'eck!\n" + 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' } + prog = {file='file', line=125} + opts = {line=99, program='program'} ]] .. script - expect (luaproc (script)).to_fail_while_matching ": file:125: By 'eck!\n" + 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" + 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" + 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) + path = table.concat({'', 'one', 'two', 'three'}, dirsep) - context with bad arguments: - badargs.diagnose (f, 'std.io.dirname (string)') + badargs.diagnose(f, 'std.io.dirname(string)') - it removes final separator and following: - expect (f (path)).to_be (table.concat ({'', 'one', 'two'}, dirsep)) + expect(f(path)).to_be(table.concat({'', 'one', 'two'}, dirsep)) - describe process_files: @@ -149,26 +149,26 @@ specify std.io: name = 'Makefile' names = {'LICENSE.md', 'Makefile', 'README.md'} ascript = [[ - require 'std.io'.process_files (function (a) print (a) end) + require 'std.io'.process_files(function(a) print(a) end) ]] lscript = [[ - require 'std.io'.process_files ('=print (_1)') + require 'std.io'.process_files('=print(_1)') ]] iscript = [[ - require 'std.io'.process_files (function (_, i) print (i) end) + require 'std.io'.process_files(function(_, i) print(i) end) ]] catscript = [[ - require 'std.io'.process_files (function () io.write (io.input ():read '*a') end) + 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)') + 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 { + ["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 'io_input' (not-an-existing-file:", -- Lua 5.1 } @@ -176,68 +176,68 @@ specify std.io: } - it defaults to `-` if no arguments were passed: - expect (luaproc (ascript)).to_output '-\n' + 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') + 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' + 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))) + 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' + 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' + 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 () + h = io.open(name) + lines = {} for l in h:lines() do lines[#lines + 1] = l end + h:close() - defaultin = io.input () + defaultin = io.input() - f, badarg = init (M, this_module, 'readlines') + f, badarg = init(M, this_module, 'readlines') - after: - if io.type (defaultin) ~= 'closed file' then io.input (defaultin) end + if io.type(defaultin) ~= 'closed file' then io.input(defaultin) end - context with bad arguments: | - badargs.diagnose (f, 'std.io.readlines (?file|string)') + 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 + ['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 () + 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')) + ['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' + 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) + expect(f(name)).to_equal(lines) - it reads lines from an open file handle: - expect (f (io.open (name))).to_equal (lines) + 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) + io.input(name) + expect(f()).to_equal(lines) - describe shell: @@ -245,54 +245,54 @@ specify std.io: f = M.shell - context with bad arguments: - badargs.diagnose (f, 'std.io.shell (string)') + 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' + expect(f [[printf '%s\n' 'foo' 'bar']]).to_be 'foo\nbar\n' - describe slurp: - before: | name = 'Makefile' - h = io.open (name) + h = io.open(name) content = h:read '*a' - h:close () + h:close() - defaultin = io.input () - f, badarg = init (M, this_module, 'slurp') + defaultin = io.input() + f, badarg = init(M, this_module, 'slurp') - after: - if io.type (defaultin) ~= 'closed file' then io.input (defaultin) end + if io.type(defaultin) ~= 'closed file' then io.input(defaultin) end - context with bad arguments: | - badargs.diagnose (f, 'std.io.slurp (?file|string)') + 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 + ['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 () + 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')) + ['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) + expect(f(name)).to_be(content) - it reads content from an open file handle: - expect (f (io.open (name))).to_be (content) + 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' + 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) + io.input(name) + expect(f()).to_be(content) - describe splitdir: @@ -300,17 +300,17 @@ specify std.io: f = M.splitdir - context with bad arguments: - badargs.diagnose (f, 'std.io.splitdir (string)') + badargs.diagnose(f, 'std.io.splitdir(string)') - it returns a filename as a one element list: - expect (f ('hello')).to_equal {'hello'} + expect(f('hello')).to_equal {'hello'} - it splits root directory in two empty elements: - expect (f (dirsep)).to_equal {'', ''} + expect(f(dirsep)).to_equal {'', ''} - it returns initial empty string for absolute path: - expect (f (dirsep .. 'root')).to_equal {'', 'root'} + 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))). + 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'} @@ -320,102 +320,102 @@ specify std.io: f = M.warn - context with bad arguments: - badargs.diagnose (f, 'std.io.warn (string, ?any*)') + badargs.diagnose(f, 'std.io.warn(string, ?any*)') - it outputs a message to stderr: - expect (luaproc (script)).to_output_error 'Ayup!\n' + 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' + 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' + 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' + 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' + 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' + 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' } + prog = {file='file', name='name'} + opts = {program='program'} ]] .. script - expect (luaproc (script)).to_output_error 'name: Ayup!\n' + 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' } + prog = {file='file', line=125, name='name'} + opts = {line=99, program='program'} ]] .. script - expect (luaproc (script)).to_output_error 'name:125: Ayup!\n' + expect(luaproc(script)).to_output_error 'name:125: Ayup!\n' - it prefers `prog.file` to `opts.program`: | script = [[ - prog = { file = 'file' }; opts = { program = 'program' } + prog = {file='file'}; opts = {program='program'} ]] .. script - expect (luaproc (script)).to_output_error 'file: Ayup!\n' + 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' } + prog = {file='file', line=125} + opts = {line=99, program='program'} ]] .. script - expect (luaproc (script)).to_output_error 'file:125: Ayup!\n' + 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' + 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' + 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') + name = os.tmpname() + h = io.open(name, 'w') + lines = M.readlines(io.open 'Makefile') - defaultout = io.output () - f, badarg = init (M, this_module, 'writelines') + 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) + 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')) + 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')) + 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')) + 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 () + closed = io.open(name, 'r') closed:close() if have_typecheck then - expect (f (closed)).to_raise (badarg (1, '?file|string|number', 'closed file')) + 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' + 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) + 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'} + 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) + io.output(h) + f(unpack(lines)) + h:flush() + expect(M.readlines(io.open(name))).to_equal(lines) diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 3c22e89..2c68aa3 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -3,30 +3,30 @@ before: this_module = 'std.math' global_table = '_G' - extend_base = { 'floor', 'round' } + extend_base = {'floor', 'round'} - M = require (this_module) + 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}). + 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}). + 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) + 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'}). + 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'}). + expect(show_apis {added_to=base_module, by='std'}). to_equal {} @@ -35,21 +35,21 @@ specify std.math: f = M.floor - context with bad arguments: - badargs.diagnose (f, 'std.math.floor (number, ?int)') + 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) + 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) + 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: @@ -57,40 +57,40 @@ specify std.math: f = M.round - context with bad arguments: - badargs.diagnose (f, 'std.math.round (number, ?int)') + 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) + 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) + 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) + 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/specs/package_spec.yaml b/specs/package_spec.yaml index 9b852ae..4a36be0 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -3,63 +3,63 @@ before: | this_module = 'std.package' global_table = '_G' - extend_base = { 'dirsep', 'execdir', 'find', 'igmark', 'insert', - 'mappath', 'normalize', 'pathsep', 'path_mark', - 'remove' } + extend_base = {'dirsep', 'execdir', 'find', 'igmark', 'insert', + 'mappath', 'normalize', 'pathsep', 'path_mark', + 'remove'} - M = require (this_module) + M = require(this_module) - path = M.normalize ('begin', 'middle', 'end') + path = M.normalize('begin', 'middle', 'end') - function catfile (...) return table.concat ({...}, M.dirsep) end - function catpath (...) return table.concat ({...}, M.pathsep) 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}). + 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}). + 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) + 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'}). + 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'}). + expect(show_apis {added_to=base_module, by='std'}). to_equal {} - describe find: - before: | - path = table.concat ({'begin', 'm%ddl.', 'end'}, M.pathsep) + 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)') + badargs.diagnose(f, 'std.package.find(string, string, ?int, ?boolean|:plain)') - it returns nil for unmatched element: - expect (f (path, 'unmatchable')).to_be (nil) + expect(f(path, 'unmatchable')).to_be(nil) - it returns the element index for a matched element: - expect (f (path, 'end')).to_be (3) + 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'} + 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'} + 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.'} + 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: @@ -67,48 +67,48 @@ specify std.package: f = M.insert - context with bad arguments: - badargs.diagnose (f, 'std.package.insert (string, [int], string)') + 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')) + 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')) + 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')) + 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')) + 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) + expected = require 'std.string'.split(path, M.pathsep) f = M.mappath - context with bad arguments: - badargs.diagnose (f, 'std.package.mappath (string, function, ?any*)') + 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) + 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]) + table.insert(reversed, expected[i]) end t = {} - f (path, function (e, pos) table.insert (t, pos, e) end, 1) - expect (t).to_equal (reversed) + f(path, function(e, pos) table.insert(t, pos, e) end, 1) + expect(t).to_equal(reversed) - describe normalize: @@ -116,56 +116,56 @@ specify std.package: f = M.normalize - context with bad arguments: - badargs.diagnose (f, 'std.package.normalize (string*)') + 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')) + 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 ('..', '..', '..')) + 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')) + 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')) + expect(f '/foo/bar').to_be(catfile('', 'foo', 'bar')) - it normalizes ? to platform path_mark: - expect (f '?.lua'). - to_be (catfile ('.', M.path_mark .. '.lua')) + expect(f '?.lua'). + to_be(catfile('.', M.path_mark .. '.lua')) - it strips redundant trailing /: - expect (f '/foo/bar/').to_be (catfile ('', 'foo', 'bar')) + 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')) + 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'))) + 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'))) + 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'))) + expect(f('/foo/bar', 'x')). + to_be(catpath(catfile('', 'foo', 'bar'), catfile('.', 'x'))) - it normalizes ? to platform path_mark: - expect (f ('?.lua', 'x')). - to_be (catpath (catfile ('.', M.path_mark .. '.lua'), catfile ('.', 'x'))) + expect(f('?.lua', 'x')). + to_be(catpath(catfile('.', M.path_mark .. '.lua'), catfile('.', 'x'))) - it strips redundant trailing /: - expect (f ('/foo/bar/', 'x')). - to_be (catpath (catfile ('', 'foo', 'bar'), catfile ('.', 'x'))) + 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'))) + 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')) + expect(f(catpath('1', 'x', '2', './x', './2', './x/../x'))). + to_be(catpath('./1', './x', './2')) - describe remove: @@ -173,21 +173,21 @@ specify std.package: f = M.remove - context with bad arguments: - badargs.diagnose (f, 'std.package.remove (string, ?int)') + badargs.diagnose(f, 'std.package.remove(string, ?int)') - it removes the last item by default: - expect (f (path)).to_be (M.normalize ('begin', 'middle')) + 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')) + 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')) + 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)) + 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', + expect(string.format('%s\n%s\n%s\n%s\n%s\n', M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark) - ).to_contain (package.config) + ).to_contain(package.config) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 9154652..298c11a 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -1,5 +1,5 @@ local typecheck -have_typecheck, typecheck = pcall (require, 'typecheck') +have_typecheck, typecheck = pcall(require, 'typecheck') local inprocess = require 'specl.inprocess' local hell = require 'specl.shell' @@ -11,7 +11,7 @@ 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 ( +package.path = std.package.normalize( top_builddir .. '/lib/?.lua', top_builddir .. '/lib/?/init.lua', top_srcdir .. '/lib/?.lua', @@ -31,10 +31,10 @@ setdebug = require 'std.debug'._setdebug -- 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 +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 @@ -46,17 +46,17 @@ 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) +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 + 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 ('#', ...), ...} +pack = table.pack or function(...) + return {n=select('#', ...), ...} end @@ -65,72 +65,72 @@ end 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))) +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 (...) +badargs.diagnose = function(...) if have_typecheck then - return _diagnose (...) + return _diagnose(...) end end -badargs.result = badargs.result or function (fname, i, want, got) - if want == nil then i, want = i - 1, i end -- numbers only for narg error +badargs.result = badargs.result or function(fname, i, want, got) + if want == nil then i, want = i - 1, i end -- numbers only for narg error - 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) + 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 ('|', ', ') + 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') + 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 ('^%.', '') +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 + 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) +function objtype(o) + return(getmetatable(o) or {})._type or io.type(o) or type(o) end -function nop () 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 (...) +function bind(f, fix) + return function(...) local arg = {} - for i, v in pairs (fix) do + for i, v in pairs(fix) do arg[i] = v end local i = 1 @@ -138,16 +138,16 @@ function bind (f, fix) while arg[i] ~= nil do i = i + 1 end arg[i] = v end - return f (unpack (arg)) + 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 () +local function mkscript(code) + local f = os.tmpname() + local h = io.open(f, 'w') + h:write(code) + h:close() return f end @@ -159,23 +159,23 @@ end -- @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)} +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.env = {LUA_PATH=package.path, LUA_INIT='', LUA_INIT_5_2=''} cmd.stdin = stdin - local proc = hell.spawn (cmd) - os.remove (f) + 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) +-- 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 `_DEBUG.deprecate` -- @string module dot delimited module path to load @@ -185,48 +185,48 @@ end -- 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) +function deprecation(deprecate, module, fname, args, objectinit) args = args or '' local script if objectinit == nil then script = string.format([[ - _DEBUG = { deprecate = %s } + _DEBUG = {deprecate=%s} M = require '%s' P = M.prototype - print (M.%s (%s)) - ]], tostring (deprecate), module, fname, tostring (args)) + print(M.%s(%s)) + ]], tostring(deprecate), module, fname, tostring(args)) else script = string.format([[ - _DEBUG = { deprecate = %s } + _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)) + local obj = P(%s) + print(obj:%s(%s)) + ]], tostring(deprecate), module, objectinit, fname, tostring(args)) end - return luaproc (script) + 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 (...) +function concat_file_content(...) local t = {} for _, name in ipairs {...} do - h = io.open (name) + h = io.open(name) t[#t + 1] = h:read '*a' end - return table.concat (t) + 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 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) + proc.output:gsub('(%S*)[%s]*', + function(x) if x ~= '' then r[x] = true end end) return r @@ -255,72 +255,72 @@ end -- -- @tparam table argt one of the combinations above -- @treturn table a list of keys according to criteria above -function show_apis (argt) +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 ([[ + return tabulate_output([[ local before, after = {}, {} - for k in pairs (]] .. added_to .. [[) do + for k in pairs(]] .. added_to .. [[) do before[k] = true end local M = require ']] .. by .. [[' - for k in pairs (]] .. added_to .. [[) do + 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 + for k in pairs(after) do + if not before[k] then print(k) end end ]]) elseif from and not_in then - return tabulate_output ([[ + return tabulate_output([[ local from = ]] .. from .. [[ local M = require ']] .. not_in .. [[' - for k in pairs (M) do + 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 + if k ~= 1 and from[k] ~= M[k] then print(k) end end ]]) elseif from and enhanced_in then - return tabulate_output ([[ + return tabulate_output([[ local from = ]] .. from .. [[ local M = require ']] .. enhanced_in .. [[' - for k, v in pairs (M) do - if from[k] ~= M[k] and from[k] ~= nil then print (k) end + 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 ([[ + return tabulate_output([[ local before, after = {}, {} local from = ]] .. from .. [[ - for k, v in pairs (from) do before[k] = v end + 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(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 + 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') + 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 + function(f, arg) return nil, nil, f(unpack(arg or {})) end do @@ -333,38 +333,38 @@ do matchers.Matcher, matchers.matchers, matchers.stringify matchers.have_size = Matcher { - function (self, actual, expect) + function(self, actual, expect) local size = 0 - for _ in pairs (actual) do size = size + 1 end + for _ in pairs(actual) do size = size + 1 end return size == expect end, actual = 'table', - format_expect = function (self, expect) + format_expect = function(self, expect) return ' a table containing ' .. expect .. ' elements, ' end, - format_any_of = function (self, alternatives) + format_any_of = function(self, alternatives) return ' a table with any of ' .. - util.concat (alternatives, util.QUOTED) .. ' elements, ' + util.concat(alternatives, util.QUOTED) .. ' elements, ' end, } matchers.have_member = Matcher { - function (self, actual, expect) + function(self, actual, expect) return actual[expect] ~= nil end, actual = 'set', - format_expect = function (self, expect) - return ' a set containing ' .. q (expect) .. ', ' + format_expect = function(self, expect) + return ' a set containing ' .. q(expect) .. ', ' end, - format_any_of = function (self, alternatives) + format_any_of = function(self, alternatives) return ' a set containing any of ' .. - util.concat (alternatives, util.QUOTED) .. ', ' + util.concat(alternatives, util.QUOTED) .. ', ' end, } diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 89f21b6..10d3896 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -2,82 +2,82 @@ before: | this_module = 'std' global_table = '_G' - exported_apis = { 'assert', 'elems', 'eval', 'getmetamethod', - 'ielems', 'ipairs', 'npairs', 'pairs', - 'require', 'ripairs', 'rnpairs', 'tostring' } + exported_apis = {'assert', 'elems', 'eval', 'getmetamethod', + 'ielems', 'ipairs', 'npairs', 'pairs', + 'require', 'ripairs', 'rnpairs', 'tostring'} -- Tables with iterator metamethods used by various examples. - __pairs = setmetatable ({ content = 'a string' }, { - __pairs = function (t) - return function (x, n) + __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) + 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) + __index = setmetatable({content='a string'}, { + __index = function(t, n) if n <= #t.content then - return t.content:sub (n, n) + return t.content:sub(n, n) end end, - __len = function (t) return #t.content end, + __len = function(t) return #t.content end, }) - M = require (this_module) + 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}). + 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) + 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' + 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') + expect(lazy).to_be(require 'std.math') - it loads submodule functions on demand: - expect (M.math.round (3.141592)).to_be (3) + 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*)') + 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' + 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) + 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 () + 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' + 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')) + expect(f(nil, '%s %d: %q', 'here', 42, 'a string')). + to_raise(string.format('%s %d: %q', 'here', 42, 'a string')) - describe elems: @@ -85,23 +85,23 @@ specify std: f = M.elems - context with bad arguments: - badargs.diagnose (f, 'std.elems (table)') + badargs.diagnose(f, 'std.elems(table)') - it is an iterator over table values: t = {} - for e in f {'foo', bar = 'baz', 42} do + for e in f {'foo', bar='baz', 42} do t[#t + 1] = e end - expect (t).to_contain.a_permutation_of {'foo', 'baz', 42} + 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). + 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 {} + expect(t).to_equal {} - describe eval: @@ -109,13 +109,13 @@ specify std: f = M.eval - context with bad arguments: - badargs.diagnose (f, 'std.eval (string)') + badargs.diagnose(f, 'std.eval(string)') - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable '=' code. - expect (f '=').to_raise () + expect(f '=').to_raise() - it evaluates a string of lua code: - expect (f 'math.min (2, 10)').to_be (math.min (2, 10)) + expect(f 'math.min(2, 10)').to_be(math.min(2, 10)) - describe getmetamethod: @@ -123,25 +123,25 @@ specify std: f = M.getmetamethod - context with bad arguments: - badargs.diagnose (f, 'std.getmetamethod (?any, string)') + 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, + 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) + 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) + 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' + 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' + expect(f(t, '_functor')).to_be(functor) + expect(f(t, '_functor')()).to_be 'called' - describe ielems: @@ -149,28 +149,28 @@ specify std: f = M.ielems - context with bad arguments: - badargs.diagnose (f, 'std.ielems (table)') + 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} + 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 + for e in f {'foo', 42; bar='baz', qux='quux'} do t[#t + 1] = e end - expect (t).to_equal {'foo', 42} + 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'} + 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 {} + expect(t).to_equal {} - describe ipairs: @@ -178,28 +178,28 @@ specify std: f = M.ipairs - context with bad arguments: - badargs.diagnose (f, 'std.ipairs (table)') + 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} + 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 + for i, v in f {'foo', 42; bar='baz', qux='quux'} do t[i] = v end - expect (t).to_equal {'foo', 42} + 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'} + 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 {} + expect(t).to_equal {} - describe npairs: @@ -207,30 +207,30 @@ specify std: f = M.npairs - context with bad arguments: - badargs.diagnose (f, 'std.npairs (table)') + 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'} + 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 + 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'} + 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) + 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' + 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 {} + expect(t).to_equal {} - describe pairs: @@ -238,23 +238,23 @@ specify std: f = M.pairs - context with bad arguments: - badargs.diagnose (f, 'std.pairs (table)') + 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 + for k, v in f {'foo', bar='baz', 42} do t[k] = v end - expect (t).to_equal {'foo', bar = 'baz', 42} + 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). + 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 {} + expect(t).to_equal {} - describe require: @@ -262,28 +262,28 @@ specify std: f = M.require - context with bad arguments: - badargs.diagnose (f, 'std.require (string, ?string, ?string, ?string)') + badargs.diagnose(f, 'std.require(string, ?string, ?string, ?string)') - it diagnoses non-existent module: - expect (f ('module-not-exists', '', '')).to_raise 'module-not-exists' + expect(f('module-not-exists', '', '')).to_raise 'module-not-exists' - it diagnoses module too old: - expect (f ('std', '9999', '9999')). + expect(f('std', '9999', '9999')). to_raise "require 'std' with at least version 9999," - it diagnoses module too new: - expect (f ('std', '0', '0')). + 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') + expect(f('std', '0', '9999')).to_be(require 'std') - it places no upper bound by default: - expect (f ('std', '0')).to_be (require 'std') + expect(f('std', '0')).to_be(require 'std') - it places no lower bound by default: - expect (f 'std').to_be (require 'std') + expect(f 'std').to_be(require 'std') - it uses _VERSION when version field is nil: - expect (luaproc [[ + expect(luaproc [[ package.loaded['poop'] = {_VERSION='41.1'} f = require 'std'.require - print (f ('poop', '41', '9999')._VERSION) + print(f('poop', '41', '9999')._VERSION) ]]).to_succeed_with '41.1\n' - context with semantic versioning: - before: @@ -293,33 +293,33 @@ specify std: - after: std.version = ver - it diagnoses module too old: - expect (f ('std', '1.2.4')). + expect(f('std', '1.2.4')). to_raise "require 'std' with at least version 1.2.4," - expect (f ('std', '1.3')). + expect(f('std', '1.3')). to_raise "require 'std' with at least version 1.3," - expect (f ('std', '2.1.2')). + expect(f('std', '2.1.2')). to_raise "require 'std' with at least version 2.1.2," - expect (f ('std', '2')). + expect(f('std', '2')). to_raise "require 'std' with at least version 2," - expect (f ('std', '1.2.10')). + 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')). + expect(f('std', nil, '1.2.2')). to_raise "require 'std' with version less than 1.2.2," - expect (f ('std', nil, '1.1')). + expect(f('std', nil, '1.1')). to_raise "require 'std' with version less than 1.1," - expect (f ('std', nil, '1.1.2')). + expect(f('std', nil, '1.1.2')). to_raise "require 'std' with version less than 1.1.2," - expect (f ('std', nil, '1')). + 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) + 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' @@ -328,15 +328,15 @@ specify std: - after: std.version = ver - it diagnoses module too old: - expect (f ('std', '42')).to_raise () + expect(f('std', '42')).to_raise() - it diagnoses module too new: - expect (f ('std', nil, '40')).to_raise () + 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) + 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: @@ -344,30 +344,30 @@ specify std: f = M.ripairs - context with bad arguments: - badargs.diagnose (f, 'std.ripairs (table)') + 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'} + 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} + 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'} + 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'} + 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'} + 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 {} + expect(t).to_equal {} - describe rnpairs: @@ -375,30 +375,30 @@ specify std: f = M.rnpairs - context with bad arguments: - badargs.diagnose (f, 'std.rnpairs (table)') + 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)}). + 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} + 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'} + 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) + 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' + 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 {} + expect(t).to_equal {} - describe tostring: @@ -406,26 +406,26 @@ specify std: f = M.tostring - context with bad arguments: - badargs.diagnose (f, 'std.tostring (?any)') + badargs.diagnose(f, 'std.tostring(?any)') - it renders primitives exactly like system tostring: - expect (f (nil)).to_be (tostring (nil)) - expect (f (false)).to_be (tostring (false)) - expect (f (42)).to_be (tostring (42)) - expect (f (f)).to_be (tostring (f)) - expect (f 'a string').to_be 'a string' + expect(f(nil)).to_be(tostring(nil)) + expect(f(false)).to_be(tostring(false)) + expect(f(42)).to_be(tostring(42)) + expect(f(f)).to_be(tostring(f)) + expect(f 'a string').to_be 'a string' - it renders empty tables as a pair of braces: - expect (f {}).to_be ('{}') + expect(f {}).to_be('{}') - it renders table array part compactly: - expect (f {'one', 'two', 'five'}). + expect(f {'one', 'two', 'five'}). to_be '{one,two,five}' - it renders a table dictionary part compactly: - expect (f { one = true, two = 2, three = {3}}). + expect(f {one=true, two=2, three={3}}). to_be '{one=true,three={3},two=2}' - it renders table keys in table.sort order: - expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }). + expect(f {one=3, two=5, three=4, four=2, five=1}). to_be '{five=1,four=2,one=3,three=4,two=5}' - it renders keys with invalid symbol names compactly: - expect (f { _ = 0, word = 0, ['?'] = 1, ['a-key'] = 1, ['[]'] = 1 }). + expect(f {_=0, word=0, ['?']=1, ['a-key']=1, ['[]']=1}). to_be '{?=1,[]=1,_=0,a-key=1,word=0}' diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 3e76c65..64fab21 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -3,16 +3,16 @@ before: 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', 'render', 'rtrim', 'split', - 'tfind', 'trim', 'wrap' } + extend_base = {'__concat', '__index', + 'caps', 'chomp', 'escape_pattern', 'escape_shell', + 'finds', 'format', 'ltrim', + 'numbertosi', 'ordinal_suffix', 'pad', + 'prettytostring', 'render', 'rtrim', 'split', + 'tfind', 'trim', 'wrap'} - M = require (this_module) - getmetatable ('').__concat = M.__concat - getmetatable ('').__index = M.__index + M = require(this_module) + getmetatable('').__concat = M.__concat + getmetatable('').__index = M.__index specify std.string: - before: @@ -21,39 +21,39 @@ specify std.string: - context when required: - context by name: - it does not touch the global table: - expect (show_apis {added_to=global_table, by=this_module}). + 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}). + 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) + 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'}). + 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'}). + 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) + expect(subject .. ' another string').to_be(target) - it stringifies non-string arguments: - argument = { 'a table' } - expect (subject .. argument). - to_be (string.format ('%s%s', subject, require 'std'.tostring (argument))) + argument = {'a table'} + expect(subject .. argument). + to_be(string.format('%s%s', subject, require 'std'.tostring(argument))) - it stringifies nil arguments: argument = nil - expect (subject .. argument). - to_be (string.format ('%s%s', subject, require 'std'.tostring (argument))) + expect(subject .. argument). + to_be(string.format('%s%s', subject, require 'std'.tostring(argument))) - it does not perturb the original subject: original = subject newstring = subject .. ' concatenate something' - expect (subject).to_be (original) + expect(subject).to_be(original) - describe caps: @@ -61,19 +61,19 @@ specify std.string: f = M.caps - context with bad arguments: - badargs.diagnose (f, 'std.string.caps (string)') + badargs.diagnose(f, 'std.string.caps(string)') - it capitalises words of a string: target = 'A String \n\n' - expect (f (subject)).to_be (target) + expect(f(subject)).to_be(target) - it changes only the first letter of each word: - expect (f 'a stRiNg').to_be 'A StRiNg' + expect(f 'a stRiNg').to_be 'A StRiNg' - it is available as a string metamethod: - expect (('a stRiNg'):caps ()).to_be 'A StRiNg' + 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) + newstring = f(subject) + expect(subject).to_be(original) - describe chomp: @@ -82,50 +82,50 @@ specify std.string: f = M.chomp - context with bad arguments: - badargs.diagnose (f, 'std.string.chomp (string)') + badargs.diagnose(f, 'std.string.chomp(string)') - it removes a single trailing newline from a string: - expect (f (subject)).to_be (target) + 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) + expect(f(subject)).to_be(subject) - it is available as a string metamethod: - expect (subject:chomp ()).to_be (target) + expect(subject:chomp()).to_be(target) - it does not perturb the original subject: original = subject - newstring = f (subject) - expect (subject).to_be (original) + 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 + 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)') + 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) + 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) + expect(f(subject)).to_be(target) - it is available as a string metamethod: - expect (subject:escape_pattern ()).to_be (target) + expect(subject:escape_pattern()).to_be(target) - it does not perturb the original subject: original = subject - newstring = f (subject) - expect (subject).to_be (original) + newstring = f(subject) + expect(subject).to_be(original) - describe escape_shell: @@ -133,29 +133,29 @@ specify std.string: f = M.escape_shell - context with bad arguments: - badargs.diagnose (f, 'std.string.escape_shell (string)') + 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) + s = string.char(c) subject = subject .. s - if s:match ('[][ ()\\\'"]') then target = target .. '\\' end + if s:match('[][ ()\\\'"]') then target = target .. '\\' end target = target .. s end - 'it inserts a \\ before any shell metacharacters': - expect (f (subject)).to_be (target) + expect(f(subject)).to_be(target) - it is available as a string metamethod: - expect (subject:escape_shell ()).to_be (target) + expect(subject:escape_shell()).to_be(target) - it does not perturb the original subject: original = subject - newstring = f (subject) - expect (subject).to_be (original) + 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') + expect(f()).to_raise('string expected') + expect(f {'a table'}).to_raise('string expected') end @@ -165,28 +165,28 @@ specify std.string: f = M.finds - context with bad arguments: - badargs.diagnose (f, 'std.string.finds (string, string, ?int, ?boolean|:plain)') + 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' } } } + target = {{1, 2; capt={'a', 'b'}}, {3, 4; capt={'c', 'd'}}} - it creates a list of pattern captures: - expect ({f (subject, '(.)(.)')}).to_equal ({ target }) + expect({f(subject, '(.)(.)')}).to_equal({target}) - it is available as a string metamethod: - expect ({subject:finds ('(.)(.)')}).to_equal ({ target }) + expect({subject:finds('(.)(.)')}).to_equal({target}) - it creates an empty list where no captures are matched: target = {} - expect ({f (subject, '(x)')}).to_equal ({ 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 }) + 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 }) + 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) + newstring = f(subject, '...') + expect(subject).to_be(original) - describe format: @@ -196,16 +196,16 @@ specify std.string: f = M.format - context with bad arguments: - badargs.diagnose (f, 'std.string.format (string, ?any*)') + badargs.diagnose(f, 'std.string.format(string, ?any*)') - it returns a single argument without attempting formatting: - expect (f (subject)).to_be (subject) + expect(f(subject)).to_be(subject) - it is available as a string metamethod: - expect (subject:format ()).to_be (subject) + expect(subject:format()).to_be(subject) - it does not perturb the original subject: original = subject - newstring = f (subject) - expect (subject).to_be (original) + newstring = f(subject) + expect(subject).to_be(original) - describe ltrim: @@ -215,21 +215,21 @@ specify std.string: f = M.ltrim - context with bad arguments: - badargs.diagnose (f, 'std.string.ltrim (string, ?string)') + 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) + 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) + 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) + 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) + newstring = f(subject, '%W') + expect(subject).to_be(original) - describe numbertosi: @@ -237,19 +237,19 @@ specify std.string: f = M.numbertosi - context with bad arguments: - badargs.diagnose (f, 'std.string.numbertosi (number|string)') + 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)) + m = 10 *(10 ^ n) + table.insert(subject, f(m)) end - expect (subject).to_equal (target) + expect(subject).to_equal(target) - it coerces string arguments to a number: - expect (f '1000').to_be '1k' + expect(f '1000').to_be '1k' - describe ordinal_suffix: @@ -257,23 +257,23 @@ specify std.string: f = M.ordinal_suffix - context with bad arguments: - badargs.diagnose (f, 'std.string.ordinal_suffix (int|string)') + 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' + 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)) + table.insert(target, n .. suffix) + table.insert(subject, n .. f(n)) end - expect (subject).to_equal (target) + expect(subject).to_equal(target) - it coerces string arguments to a number: - expect (f '-91').to_be 'st' + expect(f '-91').to_be 'st' - describe pad: @@ -283,40 +283,40 @@ specify std.string: f = M.pad - context with bad arguments: - badargs.diagnose (f, 'std.string.pad (string, int, ?string)') + 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) + 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) + expect(f(subject, width)).to_be(target) - it is available as a string metamethod: target = 'short string ' - expect (subject:pad (width)).to_be (target) + 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) + 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) + 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) + 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) + newstring = f(subject, width) + expect(subject).to_be(original) - describe prettytostring: @@ -324,38 +324,38 @@ specify std.string: f = M.prettytostring - context with bad arguments: - badargs.diagnose (f, 'std.string.prettytostring (?any, ?string, ?string)') + badargs.diagnose(f, 'std.string.prettytostring(?any, ?string, ?string)') - it renders nil exactly like system tostring: - expect (f (nil)).to_be (tostring (nil)) + 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)) + 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)) + expect(f(n)).to_be(tostring(n)) - it renders functions exactly like system tostring: - expect (f (f)).to_be (tostring (f)) + 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)) + expect(f(s)).to_be(string.format('%q', s)) - it renders empty tables as a pair of braces: - expect (f {}).to_be ('{\n}') + expect(f {}).to_be('{\n}') - it renders an array prettily: a = {'one', 'two', 'three'} - expect (f (a, '')). + 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, '')). + 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, '')). + 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, '')). + 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}' @@ -366,64 +366,64 @@ specify std.string: f = M.rtrim - context with bad arguments: - badargs.diagnose (f, 'std.string.rtrim (string, ?string)') + 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) + 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) + 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) + 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) + newstring = f(subject, '%W') + expect(subject).to_be(original) - describe split: - before: - target = { 'first', 'the second one', 'final entry' } - subject = table.concat (target, ', ') + 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)') + badargs.diagnose(f, 'std.string.split(string, ?string)') - it falls back to '%s+' when no pattern is given: - expect (f (subject)). + 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 {''} + expect(f('', ', ')).to_equal {''} - it makes a table of substrings delimited by a separator: - expect (f (subject, ', ')).to_equal (target) + 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) + 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'} + 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'} + expect(f('xyzyzxy', 'xyz')).to_equal {'', 'yzxy'} - it returns an empty string element when ending with separator: - expect (f ('xyzyzxy', 'zxy')).to_equal {'xyzy', ''} + 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', ''} + 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 '/'). + 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) + newstring = f(subject, 'e') + expect(subject).to_be(original) - it takes a Lua pattern as a separator: - expect (f (subject, '%s+')). + expect(f(subject, '%s+')). to_equal {'first,', 'the', 'second', 'one,', 'final', 'entry'} @@ -434,27 +434,27 @@ specify std.string: f = M.tfind - context with bad arguments: - badargs.diagnose (f, 'std.string.tfind (string, string, ?int, ?boolean|:plain)') + 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) + 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) + 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) + 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) + 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) + 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) + newstring = f(subject, '...') + expect(subject).to_be(original) - describe trim: @@ -464,21 +464,21 @@ specify std.string: f = M.trim - context with bad arguments: - badargs.diagnose (f, 'std.string.trim (string, ?string)') + 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) + 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) + 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) + 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) + newstring = f(subject, '%W') + expect(subject).to_be(original) - describe wrap: @@ -492,7 +492,7 @@ specify std.string: f = M.wrap - context with bad arguments: - badargs.diagnose (f, 'std.string.wrap (string, ?int, ?int, ?int)') + 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' .. @@ -500,21 +500,21 @@ specify std.string: '-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) + 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) + 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) + 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' .. @@ -523,18 +523,18 @@ specify std.string: '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) + 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) + 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) + 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') + 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') + expect(f()).to_raise('string expected') + expect(f {'a table'}).to_raise('string expected') end diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 9bf47d2..a903a3a 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -3,132 +3,132 @@ before: | 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' } + 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) + 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}). + 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}). + expect(show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core table table: apis = {} - for _, v in ipairs (extend_base) do + for _, v in ipairs(extend_base) do if M[v] ~= table[v] then apis[#apis + 1] = v end end - expect (show_apis {from=base_module, not_in=this_module}). - to_contain.a_permutation_of (apis) + expect(show_apis {from=base_module, not_in=this_module}). + to_contain.a_permutation_of(apis) - context via the std module: - it does not touch the global table: - expect (show_apis {added_to=global_table, by='std'}).to_equal {} + 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 {} + 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!'}) + 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)') + badargs.diagnose(f, 'std.table.clone(table, [table], ?boolean|:nometa)') - it does not just return the subject: - expect (f (subject)).not_to_be (subject) + expect(f(subject)).not_to_be(subject) - it does copy the subject: - expect (f (subject)).to_equal (subject) + expect(f(subject)).to_equal(subject) - it only makes a shallow copy of field values: - expect (f (subject).k1).to_be (subject.k1) + 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) + 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)) + expect(getmetatable(f(withmt))).to_be(getmetatable(withmt)) - it treats non-table arg2 as nometa parameter: - expect (getmetatable (f (withmt, ':nometa'))).to_be (nil) + 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, {}))).to_be(getmetatable(withmt)) - it supports 3 arguments with nometa as arg3: - expect (getmetatable (f (withmt, {}, ':nometa'))).to_be (nil) + 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) + 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) + 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!'}) + 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)') + 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) + expect(f(subject)).not_to_be(subject) - it copies the keys selected: - expect (f (subject, {'k1', 'k2'})).to_equal ({ k1 = {'v1'}, k2 = {'v2'} }) + 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) + expect(f(subject, {'k1', 'k2', 'k3'})).to_equal(subject) - it only makes a shallow copy: - expect (f (subject, {'k1'}).k1).to_be (subject.k1) + 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) + 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) + 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)) + 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) + 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) + l = M.enpair(t) f = M.depair - context with bad arguments: - badargs.diagnose (f, 'std.table.depair (list of lists)') + badargs.diagnose(f, 'std.table.depair(list of lists)') - it returns a primitive table: - expect (objtype (f (l))).to_be 'table' + expect(objtype(f(l))).to_be 'table' - it works with an empty table: - expect (f {}).to_equal {} + expect(f {}).to_equal {} - it is the inverse of enpair: - expect (f (l)).to_equal (t) + expect(f(l)).to_equal(t) - describe empty: @@ -136,116 +136,116 @@ specify std.table: f = M.empty - context with bad arguments: - badargs.diagnose (f, 'std.table.empty (table)') + 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) + 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) + 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) + l = M.enpair(t) f = M.enpair - context with bad arguments: - badargs.diagnose (f, 'std.table.enpair (table)') + badargs.diagnose(f, 'std.table.enpair(table)') - it returns a table: - expect (objtype (f (t))).to_be 'table' + expect(objtype(f(t))).to_be 'table' - it works for an empty table: - expect (f {}).to_equal {} + expect(f {}).to_equal {} - it turns a table into a table of pairs: - expect (f (t)).to_equal {{1, 'first'}, {2, 'second'}, {'third', 4}} + expect(f(t)).to_equal {{1, 'first'}, {2, 'second'}, {'third', 4}} - it is the inverse of depair: - expect (f (t)).to_equal (l) + expect(f(t)).to_equal(l) - describe insert: - before: - f, badarg = init (M, this_module, 'insert') + f, badarg = init(M, this_module, 'insert') - context with bad arguments: - badargs.diagnose (f, 'std.table.insert (table, [int], any)') + badargs.diagnose(f, 'std.table.insert(table, [int], any)') examples { - ['it diagnoses more than 2 arguments with no pos'] = function () + ['it diagnoses more than 2 arguments with no pos'] = function() pending '#issue 76' - expect (f ({}, false, false)).to_raise (badarg (3)) + 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' + ['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) + 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'} + 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} + 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'} + 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'} + 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'} + expect(f({}, 1, 'x')).to_equal {'x'} - describe invert: - before: - subject = { k1 = 1, k2 = 2, k3 = 3 } + subject = {k1=1, k2=2, k3=3} f = M.invert - context with bad arguments: - badargs.diagnose (f, 'std.table.invert (table)') + badargs.diagnose(f, 'std.table.invert(table)') - it returns a new table: - expect (f (subject)).not_to_be (subject) + expect(f(subject)).not_to_be(subject) - it inverts keys and values in the returned table: - expect (f (subject)).to_equal { 'k1', 'k2', 'k3' } + expect(f(subject)).to_equal {'k1', 'k2', 'k3'} - it is reversible: - expect (f (f (subject))).to_equal (subject) + 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) + subject = {1, 2, 3} + expect(f(subject)).to_copy(subject) - describe keys: - before: - subject = { k1 = 1, k2 = 2, k3 = 3 } + subject = {k1=1, k2=2, k3=3} f = M.keys - context with bad arguments: - badargs.diagnose (f, 'std.table.keys (table)') + badargs.diagnose(f, 'std.table.keys(table)') - it returns an empty list when subject is empty: - expect (f {}).to_equal {} + 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'} + 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) + for i = 10000, 1, -1 do table.insert(subject, i) end + expect(f(subject)).not_to_equal(subject) - describe maxn: @@ -253,135 +253,135 @@ specify std.table: f = M.maxn - context with bad arguments: - badargs.diagnose (f, 'std.table.maxn (table)') + 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) + 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) + expect(f {}).to_be(0) - it ignores holes: - expect (f {1, 2, [5]=3}).to_be (5) + 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) + 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!'}) + 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 + 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') + f, badarg = init(M, this_module, 'merge') - context with bad arguments: - badargs.diagnose (f, 'std.table.merge (table, table, [table], ?boolean|:nometa)') + badargs.diagnose(f, 'std.table.merge(table, table, [table], ?boolean|:nometa)') examples { - ['it diagnoses more than 2 arguments with no pos'] = function () + ['it diagnoses more than 2 arguments with no pos'] = function() pending '#issue 76' - expect (f ({}, {}, ':nometa', false)).to_raise (badarg (4)) + expect(f({}, {}, ':nometa', false)).to_raise(badarg(4)) end } - it does not create a whole new table: - expect (f (t1, t2)).to_be (t1) + expect(f(t1, t2)).to_be(t1) - it does not change t1 when t2 is empty: - expect (f (t1, {})).to_be (t1) + expect(f(t1, {})).to_be(t1) - it copies t2 when t1 is empty: - expect (f ({}, t1)).to_copy (t1) + expect(f({}, t1)).to_copy(t1) - it merges keys from t2 into t1: - expect (f (t1, t2)).to_equal (target) + 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) + 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) + 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)) + 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) + 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)) + 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) + 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) + 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) + 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' } + 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 + 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') + f, badarg = init(M, this_module, 'merge_select') - context with bad arguments: - badargs.diagnose (f, 'std.table.merge_select (table, table, [table], ?boolean|:nometa)') + badargs.diagnose(f, 'std.table.merge_select(table, table, [table], ?boolean|:nometa)') examples { - ['it diagnoses more than 2 arguments with no pos'] = function () + ['it diagnoses more than 2 arguments with no pos'] = function() pending '#issue 76' - expect (f ({}, {}, ':nometa', false)).to_raise (badarg (4)) + expect(f({}, {}, ':nometa', false)).to_raise(badarg(4)) end } - it does not create a whole new table: - expect (f (t1, t2)).to_be (t1) + expect(f(t1, t2)).to_be(t1) - it does not change t1 when t2 is empty: - expect (f (t1, {})).to_be (t1) + expect(f(t1, {})).to_be(t1) - it does not change t1 when key list is empty: - expect (f (t1, t2, {})).to_be (t1) + expect(f(t1, t2, {})).to_be(t1) - it copies the named fields: - expect (f ({}, t2, t2keys)).to_equal (t2) + expect(f({}, t2, t2keys)).to_equal(t2) - it makes a shallow copy: - expect (f ({}, t1, {'k1'}).k1).to_be (t1.k1) + 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) + expect(f({}, t1, {'k1', 'k2', 'k3'})).to_copy(t1) - it merges keys from t2 into t1: - expect (f (t1, t2, t2keys)).to_equal (target) + 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) + 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)) + 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) + 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)) + 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) + expect(getmetatable(f({}, t1mt, {}, ':nometa'))).to_be(nil) + expect(getmetatable(f({}, t1mt, {'k1'}, ':nometa'))).to_be(nil) - describe new: @@ -389,37 +389,37 @@ specify std.table: f = M.new - context with bad arguments: - badargs.diagnose (f, 'std.table.new (?any, ?table)') + 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 {} + expect(f(default, nil)).to_equal {} - it returns any table passed in: - t = { 'unique table' } - expect (f (default, t)).to_be (t) + 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 {} + expect(f(default, nil)).to_equal {} - it returns any table passed in: - t = { 'unique table' } - expect (f (default, t)).to_be (t) + 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 = f('default') + v = {'unique value'} t[1] = v - expect (t[1]).to_be (v) + expect(t[1]).to_be(v) - it returns the constructor default for unset keys: - t = f ('default') - expect (t[1]).to_be 'default' + 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) + default = {'unique object'} + t = f(default) + expect(t[1]).to_be(default) - describe pack: @@ -428,15 +428,15 @@ specify std.table: t = {'one', 'two', 'five', n=3} f = M.pack - it creates an empty table with no arguments: - expect (f ()).to_equal {n=0} + expect(f()).to_equal {n=0} - it creates a table with arguments as elements: - expect (f ('one', 'two', 'five')).to_equal (t) + expect(f('one', 'two', 'five')).to_equal(t) - it is the inverse operation to unpack: - expect (f (unpack (t))).to_equal (t) + 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) + 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: @@ -450,16 +450,16 @@ specify std.table: f = M.project - context with bad arguments: - badargs.diagnose (f, 'std.table.project (any, list of tables)') + badargs.diagnose(f, 'std.table.project(any, list of tables)') - it returns a table: - expect (objtype (f ('third', l))).to_be 'table' + expect(objtype(f('third', l))).to_be 'table' - it works with an empty table: - expect (f ('third', {})).to_equal {} + 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'} + 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'} + expect(f('first', l)).to_equal {false, 1, '1st'} - describe remove: @@ -467,72 +467,72 @@ specify std.table: f = M.remove - context with bad arguments: - badargs.diagnose (f, 'std.table.remove (table, ?int)') + 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' + ['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' + 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' + 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' + 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'} + 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} + 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 } + -- - 1 - ------- 2 ------- -- 3 -- + subject = {'one', {{'two'}, 'three'}, four=5} f = M.size - context with bad arguments: - badargs.diagnose (f, 'std.table.size (table)') + badargs.diagnose(f, 'std.table.size(table)') - it counts the number of keys in a table: - expect (f (subject)).to_be (3) + expect(f(subject)).to_be(3) - it counts no keys in an empty table: - expect (f {}).to_be (0) + 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 + 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)') + badargs.diagnose(f, 'std.table.sort(table, ?function)') - it sorts elements in place: - f (subject, cmp) - expect (subject).to_equal (target) + f(subject, cmp) + expect(subject).to_equal(target) - it returns the sorted table: - expect (f (subject, cmp)).to_equal (target) + expect(f(subject, cmp)).to_equal(target) - describe unpack: @@ -540,39 +540,39 @@ specify std.table: t = {'one', 'two', 'five'} f = M.unpack - it returns nil for an empty table: - expect (f {}).to_be (nil) + expect(f {}).to_be(nil) - it returns numeric indexed table elements: - expect ({f (t)}).to_equal (t) + expect({f(t)}).to_equal(t) - it respects __len metamethod: - function two (t) - return setmetatable (t, { __len = function () return 2 end}) + 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) + 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} + 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) + expect({f(M.pack('one', 'two', 'five'))}).to_equal(t) - describe values: - before: - subject = { k1 = {1}, k2 = {2}, k3 = {3} } + subject = {k1={1}, k2={2}, k3={3}} f = M.values - context with bad arguments: - badargs.diagnose (f, 'std.table.values (table)') + badargs.diagnose(f, 'std.table.values(table)') - it returns an empty list when subject is empty: - expect (f {}).to_equal {} + 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}} + 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) + for i = 10000, 1, -1 do table.insert(subject, i) end + expect(f(subject)).to_equal(subject) From 7f0f3378135b9c9516d3b49076f80c2a03600964 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Sep 2017 23:21:29 -0700 Subject: [PATCH 691/703] maint: modernize formatting - vertical space. * README.md: Document vertical space style. * lib/std/_base.lua, lib/std/debug.lua, lib/std/init.lua, lib/std/io.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua, specs/debug_spec.yaml, specs/io_spec.yaml, specs/package_spec.yaml, specs/spec_helper.lua, specs/std_spec.yaml, specs/string_spec.yaml, specs/table_spec.yaml: Follow vertical space style. Signed-off-by: Gary V. Vaughan --- README.md | 3 ++ lib/std/_base.lua | 59 +++++++++++++++++++++--------- lib/std/debug.lua | 8 +++-- lib/std/init.lua | 42 +++++++++++++++------- lib/std/io.lua | 12 +++++-- lib/std/package.lua | 16 ++++++--- lib/std/string.lua | 14 +++++--- lib/std/table.lua | 27 +++++++++----- specs/debug_spec.yaml | 8 +++-- specs/io_spec.yaml | 29 +++++++++++---- specs/package_spec.yaml | 16 ++++++--- specs/spec_helper.lua | 79 +++++++++++++++++++++++++++++----------- specs/std_spec.yaml | 80 ++++++++++++++++++++++++++++++----------- specs/string_spec.yaml | 17 ++++++--- specs/table_spec.yaml | 28 +++++++++++---- 15 files changed, 321 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 6b7610b..bf565a2 100644 --- a/README.md +++ b/README.md @@ -85,4 +85,7 @@ points when proposing changes: 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/lib/std/_base.lua b/lib/std/_base.lua index 1127df7..db9a013 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -129,7 +129,9 @@ end 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 + if type(k) == 'number' and k > n then + n = k + end end return n end @@ -164,7 +166,9 @@ end -- --> stdin:1: in main chunk -- --> [C]: in ? local function callable(x) - if type(x) == 'function' then return x end + if type(x) == 'function' then + return x + end return(getmetatable(x) or {}).__call end @@ -197,14 +201,18 @@ end local function copy(dest, src) - if src == nil then dest, src = {}, dest end - for k, v in pairs(src) do dest[k] = v end + if src == nil then + dest, src = {}, dest + end + for k, v in pairs(src) do + dest[k] = v + end return dest end local function escape_pattern(s) - return(s:gsub('[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) + return (s:gsub('[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) end @@ -217,7 +225,9 @@ local function _getfenv(fn) end if getfenv then - if type(fn) == 'number' then fn = fn + 1 end + if type(fn) == 'number' then + fn = fn + 1 + end -- Stack frame count is critical here, so ensure we don't optimise one -- away in LuaJIT... @@ -252,9 +262,8 @@ end local function keysort(a, b) if type(a) == 'number' then return type(b) ~= 'number' or a < b - else - return type(b) ~= 'number' and tostring(a) < tostring(b) end + return type(b) ~= 'number' and tostring(a) < tostring(b) end @@ -273,7 +282,9 @@ end local function merge(dest, src) - for k, v in pairs(src) do dest[k] = dest[k] or v end + for k, v in pairs(src) do + dest[k] = dest[k] or v + end return dest end @@ -288,11 +299,15 @@ local fallbacks = { open = function(x) return '{' end, close = function(x) return '}' end, elem = tostring, - pair = function(x, kp, vp, k, v, kstr, vstr) return kstr .. '=' .. vstr end, + pair = function(x, kp, vp, k, v, kstr, vstr) + return kstr .. '=' .. vstr + end, sep = function(x, kp, vp, kn, vn) return kp ~= nil and kn ~= nil and ',' or '' end, - sort = function(keys) return keys end, + sort = function(keys) + return keys + end, term = function(x) return type(x) ~= 'table' or getmetamethod(x, '__tostring') end, @@ -424,20 +439,28 @@ local tostring_vtable = { -- non-deterministic for non-sequence tables. len = function(x) local m = getmetamethod(x, '__len') - if m then return m(x) end - if type(x) ~= 'table' then return #x end + if m then + return m(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 + if x[i] == nil then + return i -1 + end end return n end getmetamethod = function(x, n) - local m =(getmetatable(x) or {})[n] - if callable(m) then return m end + local m = (getmetatable(x) or {})[n] + if callable(m) then + return m + end end @@ -463,7 +486,9 @@ return { ipairs = ipairs, pairs = pairs, - tostring = function(x) return render(x, tostring_vtable) end, + tostring = function(x) + return render(x, tostring_vtable) + end, base = { copy = copy, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 5089730..99b0d68 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -69,7 +69,9 @@ local function say(n, ...) ((type(_DEBUG.level) == 'number' and _DEBUG.level >= level) or level <= 1) then local t = {} - for k, v in _pairs(argt) do t[k] = _tostring(v) end + for k, v in _pairs(argt) do + t[k] = _tostring(v) + end io_stderr:write(table_concat(t, '\t') .. '\n') end end @@ -80,7 +82,9 @@ local level = 0 local function trace(event) local t = debug.getinfo(3) local s = ' >>> ' - for i = 1, level do s = s .. ' ' end + for i = 1, level do + s = s .. ' ' + end if t ~= nil and t.currentline >= 0 then s = s .. t.short_src .. ':' .. t.currentline .. ' ' end diff --git a/lib/std/init.lua b/lib/std/init.lua index dbd8b11..4137a1c 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -68,7 +68,9 @@ local function elems(t) return function(state, _) local v ctrl, v = fn(state, ctrl) - if ctrl then return v end + if ctrl then + return v + end end, istate, true -- wrapped initial state end @@ -84,7 +86,9 @@ local function ielems(t) return function(state, _) local v ctrl, v = fn(state, ctrl) - if ctrl then return v end + if ctrl then + return v + end end, istate, true -- wrapped initial state end @@ -94,7 +98,9 @@ local function npairs(t) 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 + if i <= n then + return i, t[i] + end end, t, i end @@ -129,14 +135,22 @@ end local vconvert = setmetatable({ - string = function(x) return split(x, '%.') end, - number = function(x) return {x} end, - table = function(x) return x end, + 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, + __call = function(self, x) + local fn = self[type(x)] or function() + return 0 + end + return fn(x) + end, }) @@ -149,7 +163,9 @@ 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 + if type(m) == 'table' then + s = tostring(m.version or m._VERSION or '') + end local v = string_match(s, pattern) or 0 if min then _assert(vcompare(v, min) >= 0, "require '" .. module .. @@ -388,8 +404,8 @@ return setmetatable(M, { __index = function(self, name) local ok, t = pcall(require, 'std.' .. name) if ok then - rawset(self, name, t) - return t + rawset(self, name, t) + return t end end, }) diff --git a/lib/std/io.lua b/lib/std/io.lua index 5c3c065..a821f0a 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -72,7 +72,9 @@ 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 == nil then + argerror('std.io.slurp', 1, err, 2) + end if h then local s = h:read('*a') @@ -84,7 +86,9 @@ end local function readlines(file) local h, err = input_handle(file) - if h == nil then argerror('std.io.readlines', 1, err, 2) end + if h == nil then + argerror('std.io.readlines', 1, err, 2) + end local l = {} for line in h:lines() do @@ -142,7 +146,9 @@ local function warnfmt(msg, ...) prefix = prefix .. _tostring(opts.line) .. ':' end end - if #prefix > 0 then prefix = prefix .. ' ' end + if #prefix > 0 then + prefix = prefix .. ' ' + end return prefix .. string_format(msg, ...) end diff --git a/lib/std/package.lua b/lib/std/package.lua index 056cd16..ed964f2 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -95,11 +95,17 @@ end local function find(pathstrings, patt, init, plain) local paths = split(pathstrings, pathsep) - if plain then patt = escape_pattern(patt) end + if plain then + patt = escape_pattern(patt) + end init = init or 1 - if init < 0 then init = #paths - init end + if init < 0 then + init = #paths - init + end for i = init, #paths do - if paths[i]:find(patt) then return i, paths[i] end + if paths[i]:find(patt) then + return i, paths[i] + end end end @@ -156,7 +162,9 @@ 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 + if r ~= nil then + return r + end end end diff --git a/lib/std/string.lua b/lib/std/string.lua index c68c0ef..b063392 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -99,7 +99,9 @@ end local function caps(s) - return(s:gsub('(%w)([%w]*)', function(l, ls) return l:upper() .. ls end)) + return(s:gsub('(%w)([%w]*)', function(l, ls) + return l:upper() .. ls + end)) end @@ -137,7 +139,7 @@ local function wrap(s, w, ind, ind1) ind = ind or 0 ind1 = ind1 or ind assert(ind1 < w and ind < w, - 'the indents must be less than the line width') + '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 @@ -195,7 +197,9 @@ local function prettytostring(x, indent, spacing) end, elem = function(x) - if type(x) ~= 'string' then return tostring(x) end + if type(x) ~= 'string' then + return tostring(x) + end return string_format('%q', x) end, @@ -297,7 +301,9 @@ M = { -- @treturn string *s* with any single trailing newline removed -- @usage -- line = chomp(line) - chomp = X('chomp(string)', function(s) return(s:gsub('\n$', '')) end), + chomp = X('chomp(string)', function(s) + return(s:gsub('\n$', '')) + end), --- Escape a string to be used as a pattern. -- @function escape_pattern diff --git a/lib/std/table.lua b/lib/std/table.lua index e1c9d9e..e3a8bc0 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -60,9 +60,13 @@ local function merge_allfields(t, u, map, nometa) setmetatable(t, getmetatable(u)) end if map then - for k, v in _pairs(u) do t[map[k] or k] = v end + 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 + for k, v in _pairs(u) do + t[k] = v + end end return t end @@ -76,7 +80,9 @@ local function merge_namedfields(t, u, keys, nometa) if not nometa then setmetatable(t, getmetatable(u)) end - for _, k in _pairs(keys or {}) do t[k] = u[k] end + for _, k in _pairs(keys or {}) do + t[k] = u[k] + end return t end @@ -100,7 +106,9 @@ end local function insert(t, pos, v) - if v == nil then pos, v = len(t) + 1, pos end + 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 @@ -293,8 +301,9 @@ M = { -- @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), + 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. @@ -324,7 +333,9 @@ M = { -- @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), + empty = X('empty(table)', function(t) + return not next(t) + end), --- Make a table with a default value for unset keys. -- @function new @@ -414,7 +425,7 @@ M = { -- @usage -- merge_select(_G, require 'std.debug', {'say'}, false) merge_select = X('merge_select(table, table, [table], ?boolean|:nometa)', - merge_namedfields), + merge_namedfields), } diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 22f2e5c..6e63a65 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -67,7 +67,9 @@ specify std.debug: - before: | function mkwrap(k, v) local fmt = '%s' - if type(v) == 'string' then fmt = '%q' end + if type(v) == 'string' then + fmt = '%q' + end return k, string.format(fmt, require 'std'.tostring(v)) end @@ -105,7 +107,9 @@ specify std.debug: - before: | function mkwrap(k, v) local fmt = '%s' - if type(v) == 'string' then fmt = '%q' end + if type(v) == 'string' then + fmt = '%q' + end return k, string.format(fmt, require 'std'.tostring(v)) end diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index 3c9df62..d6e521f 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -149,16 +149,22 @@ specify std.io: name = 'Makefile' names = {'LICENSE.md', 'Makefile', 'README.md'} ascript = [[ - require 'std.io'.process_files(function(a) print(a) end) + 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) + require 'std.io'.process_files(function(_, i) + print(i) + end) ]] catscript = [[ - require 'std.io'.process_files(function() io.write(io.input():read '*a') end) + require 'std.io'.process_files(function() + io.write(io.input():read '*a') + end) ]] f = M.process_files @@ -199,14 +205,19 @@ specify std.io: - before: | name = 'Makefile' h = io.open(name) - lines = {} for l in h:lines() do lines[#lines + 1] = l end + 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 + if io.type(defaultin) ~= 'closed file' then + io.input(defaultin) + end - context with bad arguments: | badargs.diagnose(f, 'std.io.readlines(?file|string)') @@ -261,7 +272,9 @@ specify std.io: defaultin = io.input() f, badarg = init(M, this_module, 'slurp') - after: - if io.type(defaultin) ~= 'closed file' then io.input(defaultin) end + if io.type(defaultin) ~= 'closed file' then + io.input(defaultin) + end - context with bad arguments: | badargs.diagnose(f, 'std.io.slurp(?file|string)') @@ -379,7 +392,9 @@ specify std.io: defaultout = io.output() f, badarg = init(M, this_module, 'writelines') - after: - if io.type(defaultout) ~= 'closed file' then io.output(defaultout) end + if io.type(defaultout) ~= 'closed file' then + io.output(defaultout) + end h:close() os.remove(name) diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index 4a36be0..8702e3d 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -11,8 +11,12 @@ before: | path = M.normalize('begin', 'middle', 'end') - function catfile(...) return table.concat({...}, M.dirsep) end - function catpath(...) return table.concat({...}, M.pathsep) end + function catfile(...) + return table.concat({...}, M.dirsep) + end + function catpath(...) + return table.concat({...}, M.pathsep) + end specify std.package: @@ -99,7 +103,9 @@ specify std.package: - it calls a function with each path element: t = {} - f(path, function(e) t[#t + 1] = e end) + f(path, function(e) + t[#t + 1] = e + end) expect(t).to_equal(expected) - it passes additional arguments through: | reversed = {} @@ -107,7 +113,9 @@ specify std.package: table.insert(reversed, expected[i]) end t = {} - f(path, function(e, pos) table.insert(t, pos, e) end, 1) + f(path, function(e, pos) + table.insert(t, pos, e) + end, 1) expect(t).to_equal(reversed) diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index 298c11a..afbae76 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -33,12 +33,18 @@ setdebug = require 'std.debug'._setdebug -- 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 + 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 + if x[i] == nil then + return i -1 + end end return n end @@ -49,7 +55,9 @@ end 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 + if type(k) == 'number' and k > n then + n = k + end end return n end @@ -79,7 +87,9 @@ badargs.diagnose = function(...) end badargs.result = badargs.result or function(fname, i, want, got) - if want == nil then i, want = i - 1, i end -- numbers only for narg error + 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)" @@ -101,7 +111,7 @@ badargs.result = badargs.result or function(fname, i, want, got) end return string.format("bad result #%d from '%s'(%s expected, got %s)", - i, fname, showarg(want), got or 'no value') + i, fname, showarg(want), got or 'no value') end @@ -109,8 +119,12 @@ end 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 + function(...) + return badargs.format(name, ...) + end, + function(...) + return badargs.result(name, ...) + end end @@ -135,7 +149,9 @@ function bind(f, fix) end local i = 1 for _, v in pairs {...} do - while arg[i] ~= nil do i = i + 1 end + while arg[i] ~= nil do + i = i + 1 + end arg[i] = v end return f(unpack(arg)) @@ -161,7 +177,9 @@ end -- execution was successful, otherwise nil function luaproc(code, arg, stdin) local f = mkscript(code) - if type(arg) ~= 'table' then arg = {arg} end + 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 @@ -223,11 +241,15 @@ end local function tabulate_output(code) local proc = luaproc(code) - if proc.status ~= 0 then return error(proc.errout) end + 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 + if x ~= '' then + r[x] = true + end end) return r end @@ -273,7 +295,9 @@ function show_apis(argt) end for k in pairs(after) do - if not before[k] then print(k) end + if not before[k] then + print(k) + end end ]]) @@ -285,7 +309,9 @@ function show_apis(argt) 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 + if k ~= 1 and from[k] ~= M[k] then + print(k) + end end ]]) @@ -295,7 +321,9 @@ function show_apis(argt) local M = require ']] .. enhanced_in .. [[' for k, v in pairs(M) do - if from[k] ~= M[k] and from[k] ~= nil then print(k) end + if from[k] ~= M[k] and from[k] ~= nil then + print(k) + end end ]]) @@ -304,12 +332,18 @@ function show_apis(argt) local before, after = {}, {} local from = ]] .. from .. [[ - for k, v in pairs(from) do before[k] = v end + 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(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 + if after[k] ~= nil and after[k] ~= v then + print(k) + end end ]]) end @@ -319,8 +353,9 @@ 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 +capture = inprocess.capture or function(f, arg) + return nil, nil, f(unpack(arg or {})) +end do @@ -335,7 +370,9 @@ do matchers.have_size = Matcher { function(self, actual, expect) local size = 0 - for _ in pairs(actual) do size = size + 1 end + for _ in pairs(actual) do + size = size + 1 + end return size == expect end, diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index 10d3896..e8cd775 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -22,7 +22,9 @@ before: | return t.content:sub(n, n) end end, - __len = function(t) return #t.content end, + __len = function(t) + return #t.content + end, }) M = require(this_module) @@ -36,7 +38,9 @@ specify std: to_equal {} - it exports the documented apis: t = {} - for k in pairs(M) do t[#t + 1] = k end + for k in pairs(M) do + t[#t + 1] = k + end expect(t).to_contain.a_permutation_of(exported_apis) - context when lazy loading: @@ -95,12 +99,16 @@ specify std: 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 + 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 + for e in f {} do + t[#t + 1] = e + end expect(t).to_equal {} @@ -127,7 +135,9 @@ specify std: - context with a table: - before: - method = function() return 'called' end + method = function() + return 'called' + end functor = setmetatable({}, {__call=method}) t = setmetatable({}, { _type='table', _method=method, _functor=functor, @@ -165,11 +175,15 @@ specify std: expect(t).to_equal {'foo', 42} - it respects __len metamethod: t = {} - for v in f(__index) do t[#t + 1] = v end + 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 + for e in f {} do + t[#t + 1] = e + end expect(t).to_equal {} @@ -194,11 +208,15 @@ specify std: expect(t).to_equal {'foo', 42} - it respects __len metamethod: t = {} - for k, v in f(__index) do t[k] = v end + 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 + for i, v in f {} do + t[i] = v + end expect(t).to_equal {} @@ -229,7 +247,9 @@ specify std: 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 + for i, v in f {} do + t[i] = v + end expect(t).to_equal {} @@ -248,12 +268,16 @@ specify std: expect(t).to_equal {'foo', bar='baz', 42} - it respects __pairs metamethod: | t = {} - for k, v in f(__pairs) do t[k] = v end + 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 + for k, v in f {} do + t[k] = v + end expect(t).to_equal {} @@ -351,22 +375,32 @@ specify std: 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 + 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 + 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 + 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 + 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 + for k, v in f {} do + t[k] = v + end expect(t).to_equal {} @@ -383,11 +417,15 @@ specify std: 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 + 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 + 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 = {} @@ -397,7 +435,9 @@ specify std: 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 + for k, v in f {} do + t[k] = v + end expect(t).to_equal {} diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index 64fab21..a2c497f 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -115,7 +115,9 @@ specify std.string: for c = 32, 126 do s = string.char(c) subject = subject .. s - if magic[s] then target = target .. '%' end + if magic[s] then + target = target .. '%' + end target = target .. s end - 'it inserts a % before any non-alphanumeric in a string': @@ -141,7 +143,9 @@ specify std.string: for c = 32, 126 do s = string.char(c) subject = subject .. s - if s:match('[][ ()\\\'"]') then target = target .. '\\' end + if s:match('[][ ()\\\'"]') then + target = target .. '\\' + end target = target .. s end - 'it inserts a \\ before any shell metacharacters': @@ -264,9 +268,12 @@ specify std.string: 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' + 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)) diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index a903a3a..39f62ea 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -238,13 +238,17 @@ specify std.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 + 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 + for i = 10000, 1, -1 do + table.insert(subject, i) + end expect(f(subject)).not_to_equal(subject) @@ -274,8 +278,12 @@ specify std.table: 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 + 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') @@ -335,8 +343,12 @@ specify std.table: 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 + 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') @@ -574,5 +586,7 @@ specify std.table: - 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 + for i = 10000, 1, -1 do + table.insert(subject, i) + end expect(f(subject)).to_equal(subject) From f45f973077668a100a0947c4a4099aa79a1ecb51 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Sep 2017 22:33:00 -0700 Subject: [PATCH 692/703] maint: update copyright notices. Signed-off-by: Gary V. Vaughan --- LICENSE.md | 2 +- NEWS.md | 156 ++++++++++++++++++------------------ README.md | 2 +- doc/config.ld.in | 43 +++++----- lib/std/_base.lua | 5 ++ lib/std/debug.lua | 5 ++ lib/std/debug_init/init.lua | 5 ++ lib/std/init.lua | 5 ++ lib/std/io.lua | 5 ++ lib/std/math.lua | 5 ++ lib/std/package.lua | 5 ++ lib/std/string.lua | 5 ++ lib/std/table.lua | 5 ++ specs/debug_spec.yaml | 3 + specs/io_spec.yaml | 3 + specs/math_spec.yaml | 3 + specs/package_spec.yaml | 3 + specs/spec_helper.lua | 5 ++ specs/std_spec.yaml | 3 + specs/string_spec.yaml | 3 + specs/table_spec.yaml | 3 + 21 files changed, 175 insertions(+), 99 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 047c509..cbfd44a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (C) 2002-2016 stdlib authors +Copyright (C) 2002-2017 stdlib authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/NEWS.md b/NEWS.md index 5300ec5..79a86f9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -116,8 +116,8 @@ 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 + 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 @@ -143,8 +143,8 @@ deprecated api like this: ```lua - if maxn (argt) > 7 then - argerror ("fname", 8, extramsg_toomany ("argument", 7, maxn (argt)), 2) + if maxn(argt) > 7 then + argerror('fname', 8, extramsg_toomany('argument', 7, maxn(argt)), 2) end ``` @@ -187,8 +187,8 @@ - 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" + local a, b = StrBuf {'foo', 'bar'}, StrBuf {'baz', 'quux'} + a = a .. b --> 'foobarbazquux' ``` - `std.strbuf` stringifies lazily, so adding tables to a StrBuf @@ -205,29 +205,29 @@ specified as: ```lua - fn = argscheck ("fname (?any...) => int, table or nil, string", fname) + 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 + - 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)`? + 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" + - 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, @@ -306,13 +306,13 @@ - New `functional.lambda` function for compiling lambda strings: ```lua - table.sort (t, lambda "|a,b| a:prototype () method that returns the contents of the + :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", } + local Object = require 'std.object' + Prototype = Object {_type='Prototype', } ``` - 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 + 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. + 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 + 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. + 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 @@ -965,7 +965,7 @@ release. ```lua - local prototype = (require 'std.object').prototype + local prototype = require 'std.object'.prototype ``` ...makes for more readable code, rather than confusion between the @@ -979,7 +979,7 @@ `_ext` suffix. Instead, you must now use, e.g.: ```lua - local string = require "std.string" + local string = require 'std.string' ``` These names are now stable, and will be available from here for @@ -999,7 +999,7 @@ and now uses prototype functions instead: ```lua - local union = Set.union (set1, set2) + local union = Set.union(set1, set2) ``` @@ -1025,7 +1025,7 @@ except `std.lua` itself. Importing individual modules now involves: ```lua - local list = require "std.list" + local list = require 'std.list' ``` If you want to have all the symbols previously available from the @@ -1033,7 +1033,7 @@ yourself, or import everything with: ```lua - require "std" + require 'std' ``` which still behaves per previous releases. @@ -1060,7 +1060,7 @@ - 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 + - `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 @@ -1097,7 +1097,7 @@ packaging, which requires you to assign the return values from your imports: ```lua - getopt = require "getopt" + getopt = require 'getopt' ``` - Extension modules, table_ext, package_ext etc. return the unextended module @@ -1105,7 +1105,7 @@ return values or save them for programatically backing out the changes: ```lua - table_unextended = require "table_ext" + table_unextended = require 'table_ext' ``` - Additionally, Specl (see http://github.com/gvvaughan/specl/) specifications @@ -1162,7 +1162,7 @@ ## Noteworthy changes in release 26 (2012-02-18) [stable] - - This release improves getoptâs output messages and conformance to + - 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, @@ -1254,7 +1254,7 @@ 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; + 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 @@ -1273,7 +1273,7 @@ ## 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 + '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. @@ -1286,7 +1286,7 @@ 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"'. + (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. diff --git a/README.md b/README.md index bf565a2..459792c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ by the [stdlib project][github] 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 2000-2016 (see the +and 5.3. The libraries are copyright by their authors 2000-2017 (see the [AUTHORS][] file for details), and released under the [MIT license][mit] (the same license as Lua itself). There is no warranty. diff --git a/doc/config.ld.in b/doc/config.ld.in index 9f80f0c..4e37ef4 100644 --- a/doc/config.ld.in +++ b/doc/config.ld.in @@ -1,6 +1,11 @@ --- -*- lua -*- -title = "stdlib @PACKAGE_VERSION@ Reference" -project = "stdlib @PACKAGE_VERSION@" +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] + +title = 'stdlib @PACKAGE_VERSION@ Reference' +project = 'stdlib @PACKAGE_VERSION@' description = [[ # Standard Lua Libraries @@ -19,31 +24,31 @@ 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 = "." +dir = '.' 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", + '../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) +new_type ('corefunction', 'Core_Functions', true) +new_type ('corelibrary', 'Core_Libraries', true) function postprocess_html(s) - s = s:gsub("

    %s*Corefunction (.-)

    ", '

    Module %1

    ') - s = s:gsub("

    %s*Corelibrary (.-)

    ", '

    Module %1

    ') - s = s:gsub("

    Core_Functions

    ", '

    Core Functions

    ') - s = s:gsub("

    Core_Libraries

    ", '

    Core Libraries

    ') + s = s:gsub('

    %s*Corefunction (.-)

    ', '

    Module %1

    ') + s = s:gsub('

    %s*Corelibrary (.-)

    ', '

    Module %1

    ') + s = s:gsub('

    Core_Functions

    ', '

    Core Functions

    ') + s = s:gsub('

    Core_Libraries

    ', '

    Core Libraries

    ') return s end -new_type ("init", "Initialisation", false, "Parameters") +new_type ('init', 'Initialisation', false, 'Parameters') -format = "markdown" +format = 'markdown' backtick_references = false sort = false diff --git a/lib/std/_base.lua b/lib/std/_base.lua index db9a013..0723e6b 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[-- Prevent dependency loops with key function implementations. diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 99b0d68..fdcda0c 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[-- Additions to the core debug module. diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua index 7342d0b..61bcb85 100644 --- a/lib/std/debug_init/init.lua +++ b/lib/std/debug_init/init.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[ Return a table of debug parameters. diff --git a/lib/std/init.lua b/lib/std/init.lua index 4137a1c..27a950b 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[-- Enhanced Lua core functions, and others. diff --git a/lib/std/io.lua b/lib/std/io.lua index a821f0a..c51154f 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[-- Additions to the core io module. diff --git a/lib/std/math.lua b/lib/std/math.lua index de71e01..28675c0 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[-- Additions to the core math module. diff --git a/lib/std/package.lua b/lib/std/package.lua index ed964f2..fb9ebc3 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[-- Additions to the core package module. diff --git a/lib/std/string.lua b/lib/std/string.lua index b063392..ccbfda1 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[-- Additions to the core string module. diff --git a/lib/std/table.lua b/lib/std/table.lua index e3a8bc0..679eb79 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2002-2014 Reuben Thomas +]] --[[-- Extensions to the core table module. diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml index 6e63a65..5405268 100644 --- a/specs/debug_spec.yaml +++ b/specs/debug_spec.yaml @@ -1,3 +1,6 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2017 Gary V. Vaughan + before: | base_module = 'debug' this_module = 'std.debug' diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml index d6e521f..7538712 100644 --- a/specs/io_spec.yaml +++ b/specs/io_spec.yaml @@ -1,3 +1,6 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2017 Gary V. Vaughan + before: | base_module = 'io' this_module = 'std.io' diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml index 2c68aa3..cc5fd20 100644 --- a/specs/math_spec.yaml +++ b/specs/math_spec.yaml @@ -1,3 +1,6 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2017 Gary V. Vaughan + before: base_module = 'math' this_module = 'std.math' diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml index 8702e3d..f07a40b 100644 --- a/specs/package_spec.yaml +++ b/specs/package_spec.yaml @@ -1,3 +1,6 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2017 Gary V. Vaughan + before: | base_module = 'package' this_module = 'std.package' diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua index afbae76..277937a 100644 --- a/specs/spec_helper.lua +++ b/specs/spec_helper.lua @@ -1,3 +1,8 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2017 Gary V. Vaughan +]] + local typecheck have_typecheck, typecheck = pcall(require, 'typecheck') diff --git a/specs/std_spec.yaml b/specs/std_spec.yaml index e8cd775..a3863f6 100644 --- a/specs/std_spec.yaml +++ b/specs/std_spec.yaml @@ -1,3 +1,6 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2017 Gary V. Vaughan + before: | this_module = 'std' global_table = '_G' diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml index a2c497f..5ee1e44 100644 --- a/specs/string_spec.yaml +++ b/specs/string_spec.yaml @@ -1,3 +1,6 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2017 Gary V. Vaughan + before: base_module = 'string' this_module = 'std.string' diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml index 39f62ea..6229717 100644 --- a/specs/table_spec.yaml +++ b/specs/table_spec.yaml @@ -1,3 +1,6 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2017 Gary V. Vaughan + before: | base_module = 'table' this_module = 'std.table' From 0ab996af36a0b2ad55bfddd186b6b5e9b3a29c32 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Sep 2017 22:39:35 -0700 Subject: [PATCH 693/703] maint: parameterize rockspec. * stdlib-git-1.rockspec (_MODREV, _SPECREV): New constants. (version, url, dir): Use them. Signed-off-by: Gary V. Vaughan --- stdlib-git-1.rockspec | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index 63bd1b5..f782a8b 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -1,35 +1,40 @@ -package = "stdlib" -version = "git-1" +local _MODREV, _SPECREV = 'git', '-1' + +package = 'stdlib' +version = _MODREV .. _SPECREV description = { - summary = "General Lua Libraries", + 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", + homepage = 'http://lua-stdlib.github.io/lua-stdlib', + license = 'MIT/X11', } source = { - url = "git://github.com/lua-stdlib/lua-stdlib.git", + url = 'git://github.com/lua-stdlib/lua-stdlib.git', + --url = 'http://github.com/lua-stdlib/lua-stdlib/archive/v' .. _MODREV .. '.zip', + --dir = 'lua-stdlib-' .. _MODREV, } dependencies = { - "lua >= 5.1, < 5.4", + 'lua >= 5.1, < 5.4', + 'ldoc', } build = { - type = "builtin", + type = 'builtin', modules = { - std = "lib/std/init.lua", - ["std._base"] = "lib/std/_base.lua", - ["std.debug"] = "lib/std/debug.lua", - ["std.debug_init"] = "lib/std/debug_init/init.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", + std = 'lib/std/init.lua', + ['std._base'] = 'lib/std/_base.lua', + ['std.debug'] = 'lib/std/debug.lua', + ['std.debug_init'] = 'lib/std/debug_init/init.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', }, } From 2de7a376e8f63d7cde658cfaf7899b40640517ad Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 26 Sep 2017 23:29:27 -0700 Subject: [PATCH 694/703] maint: vastly simplified .travis.yml. * .travis.yml: Use hererocks, and simplify massively. Introduce coverage reporting. * .luacov: Configure coverage reporting. * README.md: Include coverage badge. Signed-off-by: Gary V. Vaughan --- .luacov | 52 +++++++++++++++++++ .travis.yml | 144 ++++++++-------------------------------------------- README.md | 1 + 3 files changed, 73 insertions(+), 124 deletions(-) create mode 100644 .luacov diff --git a/.luacov b/.luacov new file mode 100644 index 0000000..7e20fd1 --- /dev/null +++ b/.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/debug_init/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/.travis.yml b/.travis.yml index 56a264b..6500deb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,138 +1,34 @@ -language: c +language: python -addons: - apt: - packages: - - help2man +sudo: false env: - global: - - _VERSION=42.0.0 - - - LUAJIT_DIR=luajit-2.0 - - LUAJIT_VER=2.0.4 - - LUA53_VER=5.3.2 - - LUAROCKS_VER=2.2.2 - - - _COMPILE="libtool --mode=compile --tag=CC gcc" - - _CFLAGS="-O2 -Wall -DLUA_COMPAT_ALL -DLUA_COMPAT_5_2 -DLUA_USE_LINUX" - - _INSTALL="libtool --mode=install install -p" - - _LINK="libtool --mode=link --tag=CC gcc" - - _LIBS="-lm -Wl,-E -ldl -lreadline" - - - prefix=/usr/local - - bindir=$prefix/bin - - incdir=$prefix/include - - libdir=$prefix/lib - - - _inst=$TRAVIS_BUILD_DIR/_inst - - luadir=$_inst/share/lua - - luaexecdir=$_inst/lib/lua matrix: - - LUA=lua5.3 - - LUA=lua5.2 - - LUA=lua5.1 - - LUA=luajit - -sudo: required - + - LUA="lua=5.3" + - LUA="lua=5.2" + - LUA="lua=5.1" + - LUA="luajit=2.1" + - LUA="luajit=2.0" before_install: - # Put back the links for libyaml, which are missing on recent Travis VMs - - test -f /usr/lib/libyaml.so || - sudo find /usr/lib -name 'libyaml*' -exec ln -s {} /usr/lib \; - - # Fetch Lua sources. - - cd $TRAVIS_BUILD_DIR - - 'if test lua5.3 = "$LUA"; then - curl http://www.lua.org/ftp/lua-$LUA53_VER.tar.gz | tar xz; - cd lua-$LUA53_VER; - fi' - - 'if test lua5.2 = "$LUA"; then - curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz; - cd lua-5.2.4; - fi' - - 'if test lua5.1 = "$LUA"; then - curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz; - cd lua-5.1.5; - fi' - - # Unpack, compile and install Lua. - - 'if test luajit = "$LUA"; then - curl http://luajit.org/download/LuaJIT-$LUAJIT_VER.tar.gz | tar xz; - cd LuaJIT-$LUAJIT_VER; - make && sudo make install; - for header in lua.h luaconf.h lualib.h lauxlib.h luajit.h lua.hpp; do - if test -f /usr/local/include/$LUAJIT_DIR/$header; then - sudo ln -s /usr/local/include/$LUAJIT_DIR/$header /usr/local/include/$header; - fi; - done; - else - for src in src/*.c; do - test src/lua.c = "$src" || test src/luac.c = "$src" || eval $_COMPILE $_CFLAGS -c $src; - done; - eval $_LINK -o lib$LUA.la -version-info 0:0:0 -rpath $libdir *.lo; - sudo mkdir -p $libdir; - eval sudo $_INSTALL lib$LUA.la $libdir/lib$LUA.la; - - eval $_COMPILE $_CFLAGS -c src/lua.c; - eval $_LINK -static -o $LUA lua.lo lib$LUA.la $_LIBS; - sudo mkdir -p $bindir; - eval sudo $_INSTALL $LUA $bindir/$LUA; - - sudo mkdir -p $incdir; - for header in lua.h luaconf.h lualib.h lauxlib.h lua.hpp; do - if test -f src/$header; then - eval sudo $_INSTALL src/$header $incdir/$header; - fi; - done; - fi' - - # Fetch LuaRocks. - - cd $TRAVIS_BUILD_DIR - - 'git clone https://github.com/keplerproject/luarocks.git luarocks-$LUAROCKS_VER' - - cd luarocks-$LUAROCKS_VER - - git checkout v$LUAROCKS_VER - - # Compile and install luarocks. - - if test luajit = "$LUA"; then - ./configure --lua-suffix=jit; - else - ./configure; - fi - - 'make build && sudo make install' - - # Tidy up file droppings. - - cd $TRAVIS_BUILD_DIR - - rm -rf lua-$LUA53_VER lua-5.2.4 lua-5.1.5 LuaJIT-$LUAJIT_VER luarocks-$LUAROCKS_VER - + - pip install hererocks + - hererocks here -r^ --$LUA + - export PATH=$PWD/here/bin:$PATH install: - # Use Lua 5.3 compatible rocks, where available. - - 'for rock in ansicolors ldoc specl""; do - if test -z "$rock"; then break; fi; - if luarocks list | grep "^$rock$" >/dev/null; then continue; fi; - sudo luarocks install --server=http://rocks.moonscript.org/manifests/gvvaughan $rock; - done' - - # Build from rockspec, forcing uninstall of older luarocks installed - # above when testing the git rockspec, both for enforcing backwards - # compatibility by default, and for ease of maintenance. - - if test -f "stdlib-$_VERSION-1.rockspec"; then - sudo luarocks make "stdlib-$_VERSION-1.rockspec" LUA="$LUA"; - else - sudo luarocks make 'stdlib-git-1.rockspec' LUA="$LUA"; - fi - - # Clean up files created by root - - sudo git clean -dfx - - sudo rm -rf /tmp/ldoc - + - luarocks install ldoc + - luarocks install ansicolors + - luarocks install specl + - luarocks install luacov script: - # Verify local build. - - make check LUA=$LUA + - 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/README.md b/README.md index 459792c..1e65bcf 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ by the [stdlib project][github] [![License](http://img.shields.io/:license-mit-blue.svg)](http://mit-license.org) [![travis-ci status](https://secure.travis-ci.org/lua-stdlib/lua-stdlib.png?branch=master)](http://travis-ci.org/lua-stdlib/lua-stdlib/builds) +[![codecov.io](https://codecov.io/gh/lua-stdlib/lua-stdlib/branch/master/graph/badge.svg)](https://codecov.io/gh/lua-stdlib/lua-stdlib) [![Stories in Ready](https://badge.waffle.io/lua-stdlib/lua-stdlib.png?label=ready&title=Ready)](https://waffle.io/lua-stdlib/lua-stdlib) From d8cdefd9b0584916f4783b4be89527958e2a38f4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Sep 2017 00:21:56 -0700 Subject: [PATCH 695/703] maint: Add lib/std/init.lua to coverage reporting. Signed-off-by: Gary V. Vaughan --- .luacov | 1 + 1 file changed, 1 insertion(+) diff --git a/.luacov b/.luacov index 7e20fd1..1418017 100644 --- a/.luacov +++ b/.luacov @@ -29,6 +29,7 @@ return { "lib/std/_base$", "lib/std/debug$", "lib/std/debug_init/init$", + "lib/std/init$", "lib/std/io$", "lib/std/math$", "lib/std/package$", From c82405a83c6baa9849e054df33c45036deeb9fe4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Wed, 27 Sep 2017 00:32:22 -0700 Subject: [PATCH 696/703] maint: rename specs for people with misconfigured gcc. * specs/: Move from here... * spec/: ...to here. * Makefile (check): Adjust. Signed-off-by: Gary V. Vaughan --- Makefile | 2 +- {specs => spec}/debug_spec.yaml | 0 {specs => spec}/io_spec.yaml | 0 {specs => spec}/math_spec.yaml | 0 {specs => spec}/package_spec.yaml | 0 {specs => spec}/spec_helper.lua | 0 {specs => spec}/std_spec.yaml | 0 {specs => spec}/string_spec.yaml | 0 {specs => spec}/table_spec.yaml | 0 9 files changed, 1 insertion(+), 1 deletion(-) rename {specs => spec}/debug_spec.yaml (100%) rename {specs => spec}/io_spec.yaml (100%) rename {specs => spec}/math_spec.yaml (100%) rename {specs => spec}/package_spec.yaml (100%) rename {specs => spec}/spec_helper.lua (100%) rename {specs => spec}/std_spec.yaml (100%) rename {specs => spec}/string_spec.yaml (100%) rename {specs => spec}/table_spec.yaml (100%) diff --git a/Makefile b/Makefile index 7f4a5f5..75a811e 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,6 @@ doc/config.ld: doc/config.ld.in CHECK_ENV = LUA=$(LUA) check: - LUA=$(LUA) $(SPECL) $(SPECL_OPTS) specs/*_spec.yaml + LUA=$(LUA) $(SPECL) $(SPECL_OPTS) spec/*_spec.yaml .FORCE: diff --git a/specs/debug_spec.yaml b/spec/debug_spec.yaml similarity index 100% rename from specs/debug_spec.yaml rename to spec/debug_spec.yaml diff --git a/specs/io_spec.yaml b/spec/io_spec.yaml similarity index 100% rename from specs/io_spec.yaml rename to spec/io_spec.yaml diff --git a/specs/math_spec.yaml b/spec/math_spec.yaml similarity index 100% rename from specs/math_spec.yaml rename to spec/math_spec.yaml diff --git a/specs/package_spec.yaml b/spec/package_spec.yaml similarity index 100% rename from specs/package_spec.yaml rename to spec/package_spec.yaml diff --git a/specs/spec_helper.lua b/spec/spec_helper.lua similarity index 100% rename from specs/spec_helper.lua rename to spec/spec_helper.lua diff --git a/specs/std_spec.yaml b/spec/std_spec.yaml similarity index 100% rename from specs/std_spec.yaml rename to spec/std_spec.yaml diff --git a/specs/string_spec.yaml b/spec/string_spec.yaml similarity index 100% rename from specs/string_spec.yaml rename to spec/string_spec.yaml diff --git a/specs/table_spec.yaml b/spec/table_spec.yaml similarity index 100% rename from specs/table_spec.yaml rename to spec/table_spec.yaml From d272ca370752aab6bb689eddd6eb007e6778172e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 8 Oct 2017 21:39:47 -0700 Subject: [PATCH 697/703] doc: keep the LDoc configuration out of luarcoks doc tree. * doc/config.ld.in: Move from here... * build-aux/config.ld.in: ...to here. * Makefile (doc, config.ld): Adjust accordingly. * .gitignore: Update. Signed-off-by: Gary V. Vaughan --- .gitignore | 9 +++++---- Makefile | 9 ++++----- {doc => build-aux}/config.ld.in | 26 +++++++++++++------------- 3 files changed, 22 insertions(+), 22 deletions(-) rename {doc => build-aux}/config.ld.in (68%) diff --git a/.gitignore b/.gitignore index c731682..dbac14c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ +*~ .DS_Store -/.gitmodules +/*.src.rock /Makefile +/build-aux/config.ld /doc -!/doc/config.ld.in /lib/std/version.lua -/stdlib-*.rockspec -!/stdlib-git-*.rockspec +/luacov.*.out +/stdlib-*.tar.gz diff --git a/Makefile b/Makefile index 75a811e..7746d8b 100644 --- a/Makefile +++ b/Makefile @@ -31,12 +31,11 @@ $(luadir)/version.lua: .FORCE mv '$@T' '$@'; \ fi -doc: doc/config.ld $(SOURCES) - $(LDOC) -c doc/config.ld . +doc: build-aux/config.ld $(SOURCES) + $(LDOC) -c build-aux/config.ld . -doc/config.ld: doc/config.ld.in - version=`LUA_PATH=$$(pwd)'/lib/?.lua;;' $(LUA) -e 'io.stdout:write(require"std.version")'`; \ - $(SED) -e "s,@PACKAGE_VERSION@,$$version," '$<' > '$@' +build-aux/config.ld: build-aux/config.ld.in + $(SED) -e "s,@PACKAGE_VERSION@,$(VERSION)," '$<' > '$@' CHECK_ENV = LUA=$(LUA) diff --git a/doc/config.ld.in b/build-aux/config.ld.in similarity index 68% rename from doc/config.ld.in rename to build-aux/config.ld.in index 4e37ef4..a39d174 100644 --- a/doc/config.ld.in +++ b/build-aux/config.ld.in @@ -24,27 +24,27 @@ 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 = '.' +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', + '../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('

    %s*Corefunction (.-)

    ', '

    Module %1

    ') - s = s:gsub('

    %s*Corelibrary (.-)

    ', '

    Module %1

    ') - s = s:gsub('

    Core_Functions

    ', '

    Core Functions

    ') - s = s:gsub('

    Core_Libraries

    ', '

    Core Libraries

    ') - return s + s = s:gsub('

    %s*Corefunction (.-)

    ', '

    Module %1

    ') + s = s:gsub('

    %s*Corelibrary (.-)

    ', '

    Module %1

    ') + s = s:gsub('

    Core_Functions

    ', '

    Core Functions

    ') + s = s:gsub('

    Core_Libraries

    ', '

    Core Libraries

    ') + return s end new_type ('init', 'Initialisation', false, 'Parameters') From e9ef1bbf4b69679e90c30fa7317d9ca5f306b167 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sat, 30 Sep 2017 23:04:59 -0700 Subject: [PATCH 698/703] refactor: use std.normalize instead of local reimplementations. * lib/std/_base.lua (copy, debug.argerror, debug.getfenv) (debug.setfenv, getmetamethod, ipairs, merge, operator.len) (pairs, table.pack): Remove. (_ENV): Use std.normalize. Simplify accordingly. * lib/std/debug.lua, lib/std/init.lua, lib/std/io.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua: Likewise. * spec/package_spec.yaml, spec/spec_helper.lua, spec/table_spec.yaml: Adjust accordingly. * stdlib-git-1.rockspec (dependencies): Add std.normalize. we rely on now. * NEWS.md (Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 3 + lib/std/_base.lua | 244 ++++------------------------------------- lib/std/debug.lua | 41 +++---- lib/std/init.lua | 46 +++----- lib/std/io.lua | 75 ++++++------- lib/std/math.lua | 24 ++-- lib/std/package.lua | 55 ++++------ lib/std/string.lua | 49 ++++----- lib/std/table.lua | 60 ++++------ spec/io_spec.yaml | 4 +- spec/package_spec.yaml | 14 +-- spec/spec_helper.lua | 17 ++- spec/table_spec.yaml | 8 +- stdlib-git-1.rockspec | 49 +++++---- 14 files changed, 212 insertions(+), 477 deletions(-) diff --git a/NEWS.md b/NEWS.md index 79a86f9..d287283 100644 --- a/NEWS.md +++ b/NEWS.md @@ -105,6 +105,9 @@ the new compact format, including stringification of 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] diff --git a/lib/std/_base.lua b/lib/std/_base.lua index 0723e6b..bb1a2a9 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -26,37 +26,18 @@ ]] -local _ENV = _ENV -local dirsep = string.match(package.config, '^(%S+)\n') -local error = error -local getfenv = getfenv or false -local getmetatable = getmetatable -local next = next -local pairs = pairs -local rawget = rawget -local select = select -local setmetatable = setmetatable -local tonumber = tonumber -local tostring = tostring -local type = type - -local coroutine_wrap = coroutine.wrap -local coroutine_yield = coroutine.yield -local debug_getinfo = debug.getinfo -local debug_getupvalue = debug.getupvalue -local debug_setfenv = debug.setfenv -local debug_setupvalue = debug.setupvalue -local debug_upvaluejoin = debug.upvaluejoin -local math_huge = math.huge -local math_min = math.min -local string_find = string.find -local string_format = string.format -local table_concat = table.concat -local table_insert = table.insert -local table_maxn = table.maxn -local table_pack = table.pack -local table_sort = table.sort -local table_unpack = table.unpack or unpack +local _ENV = require 'std.normalize' { + concat = 'table.concat', + dirsep = 'package.dirsep', + find = 'string.find', + insert = 'table.insert', + min = 'math.min', + shallow_copy = 'table.merge', + sort = 'table.sort', + table_maxn = table.maxn, + wrap = 'coroutine.wrap', + yield = 'coroutine.yield', +} @@ -102,35 +83,12 @@ end --[[ Enhanced Core Lua functions. ]]-- --[[ ============================ ]]-- --- Forward declarations for Helper functions below. - -local getmetamethod, len -- 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). --- Iterate over keys 1..n, where n is the key before the first nil --- valued ordinal key(like Lua 5.3). -local function ipairs(l) - return function(l, n) - n = n + 1 - if l[n] ~= nil then - return n, l[n] - end - end, l, 0 -end - - -local _pairs = pairs - --- Respect __pairs metamethod, even in Lua 5.1. -local function pairs(t) - return(getmetamethod(t, '__pairs') or _pairs)(t) -end - - local maxn = table_maxn or function(t) local n = 0 for k in pairs(t) do @@ -148,16 +106,6 @@ end --[[ ============================ ]]-- -local function argerror(name, i, extramsg, level) - level = level or 1 - local s = string_format("bad argument #%d to '%s'", i, name) - if extramsg ~= nil then - s = s .. '(' .. extramsg .. ')' - end - error(s, level + 1) -end - - -- No need to recurse because functables are second class citizens in -- Lua: -- func = function() print 'called' end @@ -174,18 +122,18 @@ local function callable(x) if type(x) == 'function' then return x end - return(getmetatable(x) or {}).__call + return (getmetatable(x) or {}).__call end local function catfile(...) - return table_concat({...}, dirsep) + return concat({...}, dirsep) end local function compare(l, m) local lenl, lenm = len(l), len(m) - for i = 1, math_min(lenl, lenm) do + 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] @@ -205,55 +153,11 @@ local function compare(l, m) end -local function copy(dest, src) - if src == nil then - dest, src = {}, dest - end - for k, v in pairs(src) do - dest[k] = v - end - return dest -end - - local function escape_pattern(s) return (s:gsub('[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) end -local function _getfenv(fn) - fn = fn or 1 - - -- Unwrap functable: - if type(fn) == 'table' then - fn = fn.call or(getmetatable(fn) or {}).__call - end - - if getfenv then - if type(fn) == 'number' then - fn = fn + 1 - end - - -- Stack frame count is critical here, so ensure we don't optimise one - -- away in LuaJIT... - return getfenv(fn), nil - - else - if type(fn) == 'number' then - fn = debug_getinfo(fn + 1, 'f').func - end - - local name, env - local up = 0 - repeat - up = up + 1 - name, env = debug_getupvalue(fn, up) - until name == '_ENV' or name == nil - return env - end -end - - local function invert(t) local i = {} for k, v in pairs(t) do @@ -279,23 +183,10 @@ local function leaves(it, tr) visit(v) end else - coroutine_yield(n) + yield(n) end end - return coroutine_wrap(visit), tr -end - - -local function merge(dest, src) - for k, v in pairs(src) do - dest[k] = dest[k] or v - end - return dest -end - - -local pack = table_pack or function(...) - return {n=select('#', ...), ...} + return wrap(visit), tr end @@ -333,7 +224,7 @@ local function render(x, fns, roots) roots = roots or {} local function stop_roots(x) - return roots[x] or render(x, fns, copy(roots)) + return roots[x] or render(x, fns, shallow_copy(roots)) end if fns.term(x) then @@ -360,56 +251,29 @@ local function render(x, fns, roots) buf[#buf + 1] = sep(x, kp, vp) -- buffer << trailing separator buf[#buf + 1] = fns.close(x) -- buffer << table close - return table_concat(buf) -- stringify buffer + return concat(buf) -- stringify buffer end end local function sortkeys(t) - table_sort(t, keysort) + sort(t, keysort) return t end -local function _setfenv(fn, env) - -- Unwrap functable: - if type(fn) == 'table' then - fn = fn.call or(getmetatable(fn) or {}).__call - end - - if debug_setfenv then - return debug_setfenv(fn, env) - - else - -- From http://lua-users.org/lists/lua-l/2010-06/msg00313.html - local name - local up = 0 - repeat - up = up + 1 - name = debug_getupvalue(fn, up) - until name == '_ENV' or name == nil - if name then - debug_upvaluejoin(fn, up, function() return name end, 1) - debug_setupvalue(fn, up, env) - end - - return fn - end -end - - local function split(s, sep) local r, patt = {} if sep == '' then patt = '(.)' - table_insert(r, '') + insert(r, '') else patt = '(.-)' ..(sep or '%s+') end local b, slen = 0, len(s) while b <= slen do - local e, n, m = string_find(s, patt, b + 1) - table_insert(r, m or s:sub(b + 1, slen)) + local e, n, m = find(s, patt, b + 1) + insert(r, m or s:sub(b + 1, slen)) b = n or slen + 1 end return r @@ -429,47 +293,6 @@ local tostring_vtable = { } ---[[ ================= ]]-- ---[[ Helper functions. ]]-- ---[[ ================= ]]-- - --- The bare minumum of functions required to support implementation of --- Enhanced Core Lua functions, with forward declarations near the start --- of the file. - - --- Lua < 5.2 doesn't call `__len` automatically! --- Also PUC-Rio Lua #operation can return any numerically indexed --- element with an immediately following nil valued element, which is --- non-deterministic for non-sequence tables. -len = function(x) - local m = getmetamethod(x, '__len') - if m then - return m(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 - - -getmetamethod = function(x, n) - local m = (getmetatable(x) or {})[n] - if callable(m) then - return m - end -end - - - --[[ ============= ]]-- --[[ Internal API. ]]-- --[[ ============= ]]-- @@ -487,27 +310,15 @@ return { strict = strict, typecheck = typecheck, - getmetamethod = getmetamethod, - ipairs = ipairs, - pairs = pairs, - tostring = function(x) return render(x, tostring_vtable) end, base = { - copy = copy, - merge = merge, sortkeys = sortkeys, toqstring = toqstring, }, - debug = { - argerror = argerror, - getfenv = _getfenv, - setfenv = _setfenv, - }, - io = { catfile = catfile, }, @@ -521,14 +332,6 @@ return { mapfields = mapfields, }, - operator = { - len = len, - }, - - package = { - dirsep = dirsep, - }, - string = { escape_pattern = escape_pattern, render = render, @@ -538,7 +341,6 @@ return { table = { invert = invert, maxn = maxn, - pack = pack, }, tree = { diff --git a/lib/std/debug.lua b/lib/std/debug.lua index fdcda0c..5ea8919 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -16,29 +16,22 @@ ]] -local debug = debug -local setmetatable = setmetatable -local type = type - -local io_stderr = io.stderr -local math_huge = math.huge -local math_max = math.max -local table_concat = table.concat - - local _ = require 'std._base' local _DEBUG = _._DEBUG -local _getfenv = _.debug.getfenv -local _pairs = _.pairs -local _setfenv = _.debug.setfenv local _tostring = _.tostring -local merge = _.base.merge - -local _ENV = _.strict and _.strict {} or {} _ = nil +local _ENV = require 'std.normalize' { + 'debug', + concat = 'table.concat', + huge = 'math.huge', + max = 'math.max', + merge = 'table.merge', + stderr = 'io.stderr', +} + --[[ =============== ]]-- @@ -70,14 +63,14 @@ local function say(n, ...) if type(n) ~= 'number' then level, argt = 1, {n, ...} end - if _DEBUG.level ~= math_huge and + 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 + for k, v in pairs(argt) do t[k] = _tostring(v) end - io_stderr:write(table_concat(t, '\t') .. '\n') + stderr:write(concat(t, '\t') .. '\n') end end @@ -97,7 +90,7 @@ local function trace(event) if event == 'call' then level = level + 1 else - level = math_max(level - 1, 0) + level = max(level - 1, 0) end if t.what == 'main' then if event == 'call' then @@ -111,7 +104,7 @@ local function trace(event) else s = s .. event .. ' ' ..(t.name or '(C)') .. ' [' .. t.what .. ']' end - io_stderr:write(s .. '\n') + stderr:write(s .. '\n') end -- Set hooks according to _DEBUG @@ -129,14 +122,14 @@ local M = { -- @function getfenv -- @tparam int|function|functable fn target function, or stack level -- @treturn table environment of *fn* - getfenv = _getfenv, + 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, + setfenv = setfenv, --- Functions @@ -185,4 +178,4 @@ local metatable = { } -return setmetatable(merge(M, debug), metatable) +return setmetatable(merge(debug, M), metatable) diff --git a/lib/std/init.lua b/lib/std/init.lua index 27a950b..ab6e08e 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -19,39 +19,23 @@ ]] -local error = error -local ipairs = ipairs -local loadstring = loadstring or load -local pairs = pairs -local pcall = pcall -local rawset = rawset -local require = require -local setmetatable = setmetatable -local tostring = tostring -local type = type - -local string_format = string.format -local string_match = string.match - - local _ = require 'std._base' -local _ipairs = _.ipairs -local _pairs = _.pairs local _tostring = _.tostring local argscheck = _.typecheck and _.typecheck.argscheck local compare = _.list.compare -local copy = _.base.copy -local getmetamethod = _.getmetamethod local maxn = _.table.maxn -local merge = _.base.merge local split = _.string.split -local _ENV = _.strict and _.strict {} or {} - _ = nil +local _ENV = require 'std.normalize' { + format = 'string.format', + match = 'string.match', +} + + --[[ =============== ]]-- --[[ Implementation. ]]-- @@ -62,14 +46,14 @@ local M local function _assert(expect, fmt, arg1, ...) - local msg =(arg1 ~= nil) and string_format(fmt, arg1, ...) or fmt or '' + 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) + -- capture pairs iterator initial state + local fn, istate, ctrl = pairs(t) return function(state, _) local v ctrl, v = fn(state, ctrl) @@ -81,13 +65,13 @@ end local function eval(s) - return loadstring('return ' .. s)() + return load('return ' .. s)() end local function ielems(t) - -- capture _pairs iterator initial state - local fn, istate, ctrl = _ipairs(t) + -- capture pairs iterator initial state + local fn, istate, ctrl = ipairs(t) return function(state, _) local v ctrl, v = fn(state, ctrl) @@ -171,7 +155,7 @@ local function _require(module, min, too_big, pattern) if type(m) == 'table' then s = tostring(m.version or m._VERSION or '') end - local v = string_match(s, pattern) or 0 + 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) @@ -323,7 +307,7 @@ M = { -- --> 1 foo -- --> 2 bar -- std.functional.map(print, std.ipairs, {'foo', 'bar', [4]='baz', d=5}) - ipairs = X('ipairs(table)', _ipairs), + ipairs = X('ipairs(table)', ipairs), --- Ordered iterator for integer keyed values. -- Like ipairs, but does not stop until the __len or maxn of *t*. @@ -355,7 +339,7 @@ M = { -- --> 4 baz -- --> d 5 -- std.functional.map(print, std.pairs, {'foo', 'bar', [4]='baz', d=5}) - pairs = X('pairs(table)', _pairs), + pairs = X('pairs(table)', pairs), --- An iterator like ipairs, but in reverse. -- Apart from the order of the elements returned, this function follows diff --git a/lib/std/io.lua b/lib/std/io.lua index c51154f..d649369 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -16,46 +16,35 @@ ]] -local _G = _G -local arg = arg -local error = error -local getmetatable = getmetatable -local io = io -local rawget = rawget -local setmetatable = debug.setmetatable -local type = type - -local io_input = io.input -local io_open = io.open -local io_output = io.output -local io_popen = io.popen -local io_stderr = io.stderr -local io_stdin = io.stdin -local io_type = io.type -local io_write = io.write -local string_format = string.format -local table_concat = table.concat -local table_insert = table.insert - - local _ = require 'std._base' -local _ipairs = _.ipairs local _tostring = _.tostring -local argerror = _.debug.argerror local argscheck = _.typecheck and _.typecheck.argscheck local catfile = _.io.catfile -local dirsep = _.package.dirsep local leaves = _.tree.leaves -local len = _.operator.len -local merge = _.base.merge local split = _.string.split -local _ENV = _.strict and _.strict {} or {} - _ = 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', + 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. ]]-- @@ -67,9 +56,9 @@ local M local function input_handle(h) if h == nil then - return io_input() + return input() elseif type(h) == 'string' then - return io_open(h) + return open(h) end return h end @@ -106,10 +95,10 @@ end local function writelines(h, ...) if io_type(h) ~= 'file' then - io_write(h, '\n') - h = io_output() + write(h, '\n') + h = output() end - for v in leaves(_ipairs, {...}) do + for v in leaves(ipairs, {...}) do h:write(v, '\n') end end @@ -118,13 +107,13 @@ end local function process_files(fn) -- N.B. 'arg' below refers to the global array of command-line args if len(arg) == 0 then - table_insert(arg, '-') + insert(arg, '-') end - for i, v in _ipairs(arg) do + for i, v in ipairs(arg) do if v == '-' then - io_input(io_stdin) + input(stdin) else - io_input(v) + input(v) end fn(v, i) end @@ -154,12 +143,12 @@ local function warnfmt(msg, ...) if #prefix > 0 then prefix = prefix .. ' ' end - return prefix .. string_format(msg, ...) + return prefix .. format(msg, ...) end local function warn(msg, ...) - writelines(io_stderr, warnfmt(msg, ...)) + writelines(stderr, warnfmt(msg, ...)) end @@ -221,7 +210,7 @@ M = { -- @usage -- dirpath = catdir('', 'absolute', 'directory') catdir = X('catdir(string...)', function(...) - return(table_concat({...}, dirsep):gsub('^$', dirsep)) + return(concat({...}, dirsep):gsub('^$', dirsep)) end), --- Concatenate one or more directories and a filename into a path. @@ -293,7 +282,7 @@ M = { -- @see os.execute -- @usage -- users = shell [[cat /etc/passwd | awk -F: '{print $1;}']] - shell = X('shell(string)', function(c) return slurp(io_popen(c)) end), + shell = X('shell(string)', function(c) return slurp(popen(c)) end), --- Slurp a file handle. -- @function slurp @@ -316,7 +305,7 @@ M = { } -return merge(M, io) +return merge(io, M) diff --git a/lib/std/math.lua b/lib/std/math.lua index 28675c0..85cb08b 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -16,21 +16,19 @@ ]] -local math = math - -local math_floor = math.floor - - local _ = require 'std._base' local argscheck = _.typecheck and _.typecheck.argscheck -local merge = _.base.merge - -local _ENV = _.strict and _.strict {} or {} _ = nil +local _ENV = require 'std.normalize' { + 'math', + merge = 'table.merge', +} + + --[[ ================= ]]-- --[[ Implementatation. ]]-- @@ -40,18 +38,20 @@ _ = nil local M +local _floor = math.floor + local function floor(n, p) if(p or 0) == 0 then - return math_floor(n) + return _floor(n) end local e = 10 ^ p - return math_floor(n * e) / e + return _floor(n * e) / e end local function round(n, p) local e = 10 ^(p or 0) - return math_floor(n * e + 0.5) / e + return _floor(n * e + 0.5) / e end @@ -90,4 +90,4 @@ M = { } -return merge(M, math) +return merge(math, M) diff --git a/lib/std/package.lua b/lib/std/package.lua index fb9ebc3..8dddad6 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -14,10 +14,10 @@ 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 + onto `package.dirsep` and `package.pathmark`, for easy addition of new paths. For example, instead of all this: - lib = std.io.catfile('.', 'lib', package.path_mark .. '.lua') + 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... @@ -41,31 +41,27 @@ ]] -local ipairs = ipairs -local package = package - -local package_config = package.config -local string_match = string.match -local table_concat = table.concat -local table_insert = table.insert -local table_remove = table.remove -local table_unpack = table.unpack or unpack - - 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 len = _.operator.len -local merge = _.base.merge local split = _.string.split -local _ENV = _.strict and _.strict {} or {} - _ = nil +local _ENV = require 'std.normalize' { + 'package', + concat = 'table.concat', + dirsep = 'package.dirsep', + table_insert = 'table.insert', + merge = 'table.merge', + pathmark = 'package.pathmark', + pathsep = 'package.pathsep', + table_remove = 'table.remove', +} + --[[ =============== ]]-- @@ -78,17 +74,15 @@ _ = nil -- @table package -- @string dirsep directory separator -- @string pathsep path separator --- @string path_mark string that marks substitution points in a path template +-- @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 dirsep, pathsep, path_mark, execdir, igmark = - string_match(package_config, '^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)') local function pathsub(path) return path:gsub('%%?.', function(capture) if capture == '?' then - return path_mark + return pathmark elseif capture == '/' then return dirsep else @@ -116,7 +110,7 @@ end local function normalize(...) - local i, paths, pathstrings = 1, {}, table_concat({...}, pathsep) + local i, paths, pathstrings = 1, {}, concat({...}, pathsep) for _, path in ipairs(split(pathstrings, pathsep)) do path = pathsub(path): gsub(catfile('^[^', ']'), catfile('.', '%0')): @@ -153,14 +147,14 @@ local function normalize(...) paths[path], i = i, i + 1 end end - return table_concat(invert(paths), pathsep) + return concat(invert(paths), pathsep) end local function insert(pathstrings, ...) local paths = split(pathstrings, pathsep) table_insert(paths, ...) - return normalize(table_unpack(paths, 1, len(paths))) + return normalize(unpack(paths, 1, len(paths))) end @@ -177,7 +171,7 @@ end local function remove(pathstrings, pos) local paths = split(pathstrings, pathsep) table_remove(paths, pos) - return table_concat(paths, pathsep) + return concat(paths, pathsep) end @@ -234,7 +228,7 @@ local M = { -- 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 - -- `path_mark`(unless immediately preceded by a `%` character). + -- `pathmark`(unless immediately preceded by a `%` character). -- @function normalize -- @param ... path elements -- @treturn string a single normalized `pathsep` delimited paths string @@ -254,14 +248,7 @@ local M = { } -M.dirsep = dirsep -M.execdir = execdir -M.igmark = igmark -M.path_mark = path_mark -M.pathsep = pathsep - - -return merge(M, package) +return merge(package, M) --- Types diff --git a/lib/std/string.lua b/lib/std/string.lua index ccbfda1..9a7dfd0 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -16,38 +16,29 @@ ]] -local assert = assert -local getmetatable = getmetatable -local string = string -local tonumber = tonumber -local tostring = tostring -local type = type - -local io_stderr = io.stderr -local math_abs = math.abs -local math_floor = math.floor -local table_concat = table.concat -local table_insert = table.insert -local string_format = string.format - - local _ = require 'std._base' local _tostring = _.tostring local argscheck = _.typecheck and _.std.typecheck.argscheck -local copy = _.base.copy local escape_pattern = _.string.escape_pattern -local len = _.operator.len -local merge = _.base.merge local render = _.string.render local sortkeys = _.base.sortkeys local split = _.string.split -local _ENV = _.strict and _.strict {} or _ENV - _ = nil +local _ENV = require 'std.normalize' { + 'string', + abs = 'math.abs', + concat = 'table.concat', + floor = 'math.floor', + insert = 'table.insert', + format = 'string.format', + merge = 'table.merge', +} + + --[[ =============== ]]-- --[[ Implementation. ]]-- @@ -95,7 +86,7 @@ local function finds(s, p, i, ...) repeat from, to, r = tfind(s, p, i, ...) if from ~= nil then - table_insert(l, {from, to, capt=r}) + insert(l, {from, to, capt=r}) i = to + 1 end until not from @@ -116,7 +107,7 @@ end local function ordinal_suffix(n) - n = math_abs(n) % 100 + n = abs(n) % 100 local d = n % 10 if d == 1 and n ~= 11 then return 'st' @@ -131,7 +122,7 @@ end local function pad(s, w, p) - p = string.rep(p or ' ', math_abs(w)) + p = string.rep(p or ' ', abs(w)) if w < 0 then return string.sub(p .. s, w) end @@ -156,14 +147,14 @@ local function wrap(s, w, ind, ind1) while s[j] == ' ' do j = j - 1 end - table_insert(r, s:sub(i, j)) + insert(r, s:sub(i, j)) i = ni if i < lens then - table_insert(r, '\n' .. string.rep(' ', ind)) + insert(r, '\n' .. string.rep(' ', ind)) lstart = ind end end - return table_concat(r) + return concat(r) end @@ -178,7 +169,7 @@ local function numbertosi(n) local t = _format('% #.2e', n) local _, _, m, e = t:find('.(.%...)e(.+)') local man, exp = tonumber(m), tonumber(e) - local siexp = math_floor(exp / 3) + local siexp = floor(exp / 3) local shift = exp - siexp * 3 local s = SIprefix[siexp] or 'e' .. tostring(siexp) man = man *(10 ^ shift) @@ -205,7 +196,7 @@ local function prettytostring(x, indent, spacing) if type(x) ~= 'string' then return tostring(x) end - return string_format('%q', x) + return format('%q', x) end, pair = function(x, _, _, k, v, kstr, vstr) @@ -482,7 +473,7 @@ M = { } -return merge(M, string) +return merge(string, M) diff --git a/lib/std/table.lua b/lib/std/table.lua index 679eb79..59f76df 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -16,36 +16,20 @@ ]] -local getmetatable = getmetatable -local next = next -local setmetatable = setmetatable -local table = table -local tonumber = tonumber -local type = type - -local math_min = math.min -local table_insert = table.insert -local table_unpack = table.unpack or unpack - - local _ = require 'std._base' -local _ipairs = _.ipairs -local _pairs = _.pairs -local argerror = _.debug.argerror local argscheck = _.typecheck and _.typecheck.argscheck -local copy = _.base.copy -local getmetamethod = _.getmetamethod local invert = _.table.invert -local len = _.operator.len local maxn = _.table.maxn -local merge = _.base.merge -local pack = _.table.pack - -local _ENV = _.strict and _.strict {} or _ENV _ = nil +local _ENV = require 'std.normalize' { + 'table', + merge = 'table.merge', + min = 'math.min', +} + --[[ =============== ]]-- @@ -65,11 +49,11 @@ local function merge_allfields(t, u, map, nometa) setmetatable(t, getmetatable(u)) end if map then - for k, v in _pairs(u) do + for k, v in pairs(u) do t[map[k] or k] = v end else - for k, v in _pairs(u) do + for k, v in pairs(u) do t[k] = v end end @@ -85,7 +69,7 @@ local function merge_namedfields(t, u, keys, nometa) if not nometa then setmetatable(t, getmetatable(u)) end - for _, k in _pairs(keys or {}) do + for _, k in pairs(keys or {}) do t[k] = u[k] end return t @@ -94,7 +78,7 @@ end local function depair(ls) local t = {} - for _, v in _ipairs(ls) do + for _, v in ipairs(ls) do t[v[1]] = v[2] end return t @@ -103,13 +87,15 @@ end local function enpair(t) local tt = {} - for i, v in _pairs(t) do + 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 @@ -117,14 +103,14 @@ local function insert(t, pos, v) if pos < 1 or pos > len(t) + 1 then argerror('std.table.insert', 2, 'position ' .. pos .. ' out of bounds', 2) end - table_insert(t, pos, v) + _insert(t, pos, v) return t end local function keys(t) local l = {} - for k in _pairs(t) do + for k in pairs(t) do l[#l + 1] = k end return l @@ -140,7 +126,7 @@ end local function project(fkey, tt) local r = {} - for _, t in _ipairs(tt) do + for _, t in ipairs(tt) do r[#r + 1] = t[fkey] end return r @@ -149,7 +135,7 @@ end local function size(t) local n = 0 - for _ in _pairs(t) do + for _ in pairs(t) do n = n + 1 end return n @@ -170,26 +156,28 @@ local _remove = table.remove local function remove(t, pos) local lent = len(t) pos = pos or lent - if pos < math_min(1, lent) or pos > lent + 1 then -- +1? whu? that's what 5.2.3 does!?! + 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 table_unpack(t, tonumber(i) or 1, tonumber(j)) + return _unpack(t, tonumber(i) or 1, tonumber(j)) end local function values(t) local l = {} - for _, v in _pairs(t) do + for _, v in pairs(t) do l[#l + 1] = v end return l @@ -402,7 +390,7 @@ M = { -- globals = keys(_G) keys = X('keys(table)', keys), - --- Destructively merge another table's fields into another. + --- Destructively merge one table's fields into another. -- @function merge -- @tparam table t destination table -- @tparam table u table with fields to merge @@ -434,7 +422,7 @@ M = { } -return merge(M, table) +return merge(table, M) diff --git a/spec/io_spec.yaml b/spec/io_spec.yaml index 7538712..445589d 100644 --- a/spec/io_spec.yaml +++ b/spec/io_spec.yaml @@ -178,8 +178,8 @@ specify std.io: 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 'io_input' (not-an-existing-file:", -- Lua 5.1 + "cannot open file 'not-an-existing-file'", -- Lua 5.2 + "bad argument #1 to 'input' (not-an-existing-file:", -- Lua 5.1 } end } diff --git a/spec/package_spec.yaml b/spec/package_spec.yaml index f07a40b..198edaa 100644 --- a/spec/package_spec.yaml +++ b/spec/package_spec.yaml @@ -6,9 +6,7 @@ before: | this_module = 'std.package' global_table = '_G' - extend_base = {'dirsep', 'execdir', 'find', 'igmark', 'insert', - 'mappath', 'normalize', 'pathsep', 'path_mark', - 'remove'} + extend_base = {'find', 'insert', 'mappath', 'normalize', 'remove'} M = require(this_module) @@ -144,9 +142,9 @@ specify std.package: 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 path_mark: + - it normalizes ? to platform pathmark: expect(f '?.lua'). - to_be(catfile('.', M.path_mark .. '.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: @@ -163,9 +161,9 @@ specify std.package: - it normalizes / to platform dirsep: expect(f('/foo/bar', 'x')). to_be(catpath(catfile('', 'foo', 'bar'), catfile('.', 'x'))) - - it normalizes ? to platform path_mark: + - it normalizes ? to platform pathmark: expect(f('?.lua', 'x')). - to_be(catpath(catfile('.', M.path_mark .. '.lua'), catfile('.', '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'))) @@ -200,5 +198,5 @@ specify std.package: - it splits package.config up: expect(string.format('%s\n%s\n%s\n%s\n%s\n', - M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark) + M.dirsep, M.pathsep, M.pathmark, M.execdir, M.igmark) ).to_contain(package.config) diff --git a/spec/spec_helper.lua b/spec/spec_helper.lua index 277937a..d11cd50 100644 --- a/spec/spec_helper.lua +++ b/spec/spec_helper.lua @@ -308,8 +308,10 @@ function show_apis(argt) elseif from and not_in then return tabulate_output([[ - local from = ]] .. from .. [[ - local M = require ']] .. not_in .. [[' + 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 @@ -322,8 +324,10 @@ function show_apis(argt) elseif from and enhanced_in then return tabulate_output([[ - local from = ]] .. from .. [[ - local M = require ']] .. enhanced_in .. [[' + 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 @@ -334,9 +338,10 @@ function show_apis(argt) elseif from and enhanced_after then return tabulate_output([[ + local _ENV = require 'std.normalize' { + from = ']] .. from .. [[', + } local before, after = {}, {} - local from = ]] .. from .. [[ - for k, v in pairs(from) do before[k] = v end diff --git a/spec/table_spec.yaml b/spec/table_spec.yaml index 6229717..2496f41 100644 --- a/spec/table_spec.yaml +++ b/spec/table_spec.yaml @@ -25,14 +25,8 @@ specify std.table: expect(show_apis {added_to=base_module, by=this_module}). to_equal {} - it contains apis from the core table table: - apis = {} - for _, v in ipairs(extend_base) do - if M[v] ~= table[v] then - apis[#apis + 1] = v - end - end expect(show_apis {from=base_module, not_in=this_module}). - to_contain.a_permutation_of(apis) + to_contain.a_permutation_of(extend_base) - context via the std module: - it does not touch the global table: diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index f782a8b..1123866 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -4,37 +4,38 @@ 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', + 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 = { - url = 'git://github.com/lua-stdlib/lua-stdlib.git', - --url = 'http://github.com/lua-stdlib/lua-stdlib/archive/v' .. _MODREV .. '.zip', - --dir = 'lua-stdlib-' .. _MODREV, + url = 'git://github.com/lua-stdlib/lua-stdlib.git', + --url = 'http://github.com/lua-stdlib/lua-stdlib/archive/v' .. _MODREV .. '.zip', + --dir = 'lua-stdlib-' .. _MODREV, } dependencies = { - 'lua >= 5.1, < 5.4', - 'ldoc', + 'lua >= 5.1, < 5.4', + 'ldoc', + 'std.normalize >= 2.0', } build = { - type = 'builtin', - modules = { - std = 'lib/std/init.lua', - ['std._base'] = 'lib/std/_base.lua', - ['std.debug'] = 'lib/std/debug.lua', - ['std.debug_init'] = 'lib/std/debug_init/init.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', - }, + type = 'builtin', + modules = { + std = 'lib/std/init.lua', + ['std._base'] = 'lib/std/_base.lua', + ['std.debug'] = 'lib/std/debug.lua', + ['std.debug_init']= 'lib/std/debug_init/init.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', + }, } From c1eba3187f8d5b56513d30d6f58945ececb3a655 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 1 Oct 2017 21:27:11 -0700 Subject: [PATCH 699/703] refactor: don't use colon notation for global string methods. Otherwise, this code can break in mysterious ways when used with another module that monkey patches the string metatable. * lib/std/_base.lua, lib/std/io.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua: Import string functions and call them with the string operand as first argument instead of using ':' notation. Signed-off-by: Gary V. Vaughan --- lib/std/_base.lua | 6 ++++-- lib/std/io.lua | 5 +++-- lib/std/package.lua | 26 ++++++++++++++------------ lib/std/string.lua | 35 ++++++++++++++++++++--------------- lib/std/table.lua | 2 +- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/lib/std/_base.lua b/lib/std/_base.lua index bb1a2a9..93bcfc3 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -30,10 +30,12 @@ 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', @@ -154,7 +156,7 @@ end local function escape_pattern(s) - return (s:gsub('[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) + return (gsub(s, '[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) end @@ -273,7 +275,7 @@ local function split(s, sep) local b, slen = 0, len(s) while b <= slen do local e, n, m = find(s, patt, b + 1) - insert(r, m or s:sub(b + 1, slen)) + insert(r, m or sub(s, b + 1, slen)) b = n or slen + 1 end return r diff --git a/lib/std/io.lua b/lib/std/io.lua index d649369..90cb905 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -33,6 +33,7 @@ local _ENV = require 'std.normalize' { concat = 'table.concat', dirsep = 'package.dirsep', format = 'string.format', + gsub = 'string.gsub', input = 'io.input', insert = 'table.insert', io_type = 'io.type', @@ -210,7 +211,7 @@ M = { -- @usage -- dirpath = catdir('', 'absolute', 'directory') catdir = X('catdir(string...)', function(...) - return(concat({...}, dirsep):gsub('^$', dirsep)) + return(gsub(concat({...}, dirsep), '^$', dirsep)) end), --- Concatenate one or more directories and a filename into a path. @@ -231,7 +232,7 @@ M = { -- @usage -- dir = dirname '/base/subdir/filename' dirname = X('dirname(string)', function(path) - return(path:gsub(catfile('', '[^', ']*$'), '')) + return(gsub(path, catfile('', '[^', ']*$'), '')) end), --- Split a directory path into components. diff --git a/lib/std/package.lua b/lib/std/package.lua index 8dddad6..4e93284 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -55,10 +55,12 @@ local _ENV = require 'std.normalize' { 'package', concat = 'table.concat', dirsep = 'package.dirsep', - table_insert = 'table.insert', + gsub = 'string.gsub', merge = 'table.merge', pathmark = 'package.pathmark', pathsep = 'package.pathsep', + string_find = 'string.find', + table_insert = 'table.insert', table_remove = 'table.remove', } @@ -80,13 +82,13 @@ local _ENV = require 'std.normalize' { local function pathsub(path) - return path:gsub('%%?.', function(capture) + return gsub(path, '%%?.', function(capture) if capture == '?' then return pathmark elseif capture == '/' then return dirsep else - return capture:gsub('^%%', '', 1) + return gsub(capture, '^%%', '', 1) end end) end @@ -102,7 +104,7 @@ local function find(pathstrings, patt, init, plain) init = #paths - init end for i = init, #paths do - if paths[i]:find(patt) then + if string_find(paths[i], patt) then return i, paths[i] end end @@ -112,17 +114,16 @@ end local function normalize(...) local i, paths, pathstrings = 1, {}, concat({...}, pathsep) for _, path in ipairs(split(pathstrings, pathsep)) do - path = pathsub(path): - gsub(catfile('^[^', ']'), catfile('.', '%0')): - gsub(catfile('', '%.', ''), dirsep): - gsub(catfile('', '%.$'), ''): - gsub(catfile('^%.', '%..', ''), catfile('..', '')): - gsub(catfile('', '$'), '') + 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 = path:gsub(catfile('', '([^', ']+)', '%.%.', ''), + path = gsub(path, catfile('', '([^', ']+)', '%.%.', ''), function(dir1) if dir1 == '..' then -- don't remove /../../ return catfile('', '..', '..', '') @@ -130,7 +131,8 @@ local function normalize(...) again = true return dirsep end - end):gsub(catfile('', '([^', ']+)', '%.%.$'), + end) + path = gsub(path, catfile('', '([^', ']+)', '%.%.$'), function(dir1) if dir1 == '..' then -- don't remove /../.. return catfile('', '..', '..') diff --git a/lib/std/string.lua b/lib/std/string.lua index 9a7dfd0..bd0cd5e 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -32,10 +32,15 @@ local _ENV = require 'std.normalize' { 'string', abs = 'math.abs', concat = 'table.concat', + find = 'string.find', floor = 'math.floor', - insert = 'table.insert', format = 'string.format', + gsub = 'string.gsub', + insert = 'table.insert', + match = 'string.match', merge = 'table.merge', + sub = 'string.sub', + upper = 'string.upper', } @@ -55,7 +60,7 @@ end local function __index(s, i) if type(i) == 'number' then - return s:sub(i, i) + return sub(s, i, i) else -- Fall back to module metamethods return M[i] @@ -75,7 +80,7 @@ local function tpack(from, to, ...) end local function tfind(s, ...) - return tpack(s:find(...)) + return tpack(find(s, ...)) end @@ -95,14 +100,14 @@ end local function caps(s) - return(s:gsub('(%w)([%w]*)', function(l, ls) - return l:upper() .. ls + return(gsub(s, '(%w)([%w]*)', function(l, ls) + return upper(l) .. ls end)) end local function escape_shell(s) - return(s:gsub('([ %(%)%\\%[%]\'"])', '\\%1')) + return(gsub(s, '([ %(%)%\\%[%]\'"])', '\\%1')) end @@ -147,7 +152,7 @@ local function wrap(s, w, ind, ind1) while s[j] == ' ' do j = j - 1 end - insert(r, s:sub(i, j)) + insert(r, sub(s, i, j)) i = ni if i < lens then insert(r, '\n' .. string.rep(' ', ind)) @@ -167,7 +172,7 @@ local function numbertosi(n) [8]='Y' } local t = _format('% #.2e', n) - local _, _, m, e = t:find('.(.%...)e(.+)') + local _, _, m, e = find(t, '.(.%...)e(.+)') local man, exp = tonumber(m), tonumber(e) local siexp = floor(exp / 3) local shift = exp - siexp * 3 @@ -202,7 +207,7 @@ local function prettytostring(x, indent, spacing) pair = function(x, _, _, k, v, kstr, vstr) local type_k = type(k) local s = spacing - if type_k ~= 'string' or k:match '[^%w_]' then + if type_k ~= 'string' or match(k, '[^%w_]') then s = s .. '[' if type_k == 'table' then s = s .. '\n' @@ -240,7 +245,7 @@ end local function trim(s, r) r = r or '%s+' - return(s:gsub('^' .. r, ''):gsub(r .. '$', '')) + return (gsub(gsub(s, '^' .. r, ''), r .. '$', '')) end @@ -272,7 +277,7 @@ M = { -- @function __index -- @string s string -- @tparam int|string i index or method name - -- @return `s:sub(i, i)` if i is a number, otherwise + -- @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 @@ -298,7 +303,7 @@ M = { -- @usage -- line = chomp(line) chomp = X('chomp(string)', function(s) - return(s:gsub('\n$', '')) + return(gsub(s, '\n$', '')) end), --- Escape a string to be used as a pattern. @@ -306,7 +311,7 @@ M = { -- @string s any string -- @treturn string *s* with active pattern characters escaped -- @usage - -- substr = inputstr:match(escape_pattern(literal)) + -- substr = match(inputstr, escape_pattern(literal)) escape_pattern = X('escape_pattern(string)', escape_pattern), --- Escape a string to be used as a shell token. @@ -351,7 +356,7 @@ M = { -- @usage -- print('got: ' .. ltrim(userinput)) ltrim = X('ltrim(string, ?string)', function(s, r) - return(s:gsub('^' ..(r or '%s+'), '')) + return (gsub(s, '^' ..(r or '%s+'), '')) end), --- Write a number using SI suffixes. @@ -423,7 +428,7 @@ M = { -- @usage -- print('got: ' .. rtrim(userinput)) rtrim = X('rtrim(string, ?string)', function(s, r) - return(s:gsub((r or '%s+') .. '$', '')) + return (gsub(s, (r or '%s+') .. '$', '')) end), --- Split a string at a given separator. diff --git a/lib/std/table.lua b/lib/std/table.lua index 59f76df..4b6ef7a 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -227,7 +227,7 @@ M = { -- @return list-like table, with tuple-size in field `n` -- @usage -- --> {1, 2, 'ax', n=3} - -- pack(('ax1'):find '(%D+)') + -- pack(find('ax1', '(%D+)')) pack = pack, --- Enhance core *table.remove* to respect `__len` when *pos* is omitted. From 7a14a9f86540fda3a5b32c48580b43002012bf57 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Sun, 15 Oct 2017 19:32:41 -0700 Subject: [PATCH 700/703] debug_init: split into its own package. * stdlib.git-1.rockspec (dependencies): Add 'std._debug'. (build.modules): Remove 'std.debug_init'. * Makefile (SOURCES): Remove debug_init/init.lua. * .luacov (include): Likewise. * lib/std/debug_init/init.lua: Remove file. * lib/std/base.lua (_DEBUG): Remove dead code. * lib/std/debug.lua: Simplify accordingly. * spec/debug_spec.yaml (debug, say): Adjust to new 'std._debug' API, and remove unused setup code. * spec/spec_helper.lua (setdebug): Remove dead code. * NEWS.md (New features): Updated. Signed-off-by: Gary V. Vaughan --- .luacov | 1 - .travis.yml | 2 +- Makefile | 1 - NEWS.md | 21 ++- lib/std/_base.lua | 42 ------ lib/std/debug.lua | 54 ++----- lib/std/debug_init/init.lua | 40 ----- spec/debug_spec.yaml | 281 +++++++++++++++--------------------- spec/spec_helper.lua | 10 +- stdlib-git-1.rockspec | 2 +- 10 files changed, 152 insertions(+), 302 deletions(-) delete mode 100644 lib/std/debug_init/init.lua diff --git a/.luacov b/.luacov index 1418017..40b9d6a 100644 --- a/.luacov +++ b/.luacov @@ -28,7 +28,6 @@ return { ["include"] = { "lib/std/_base$", "lib/std/debug$", - "lib/std/debug_init/init$", "lib/std/init$", "lib/std/io$", "lib/std/math$", diff --git a/.travis.yml b/.travis.yml index 6500deb..92f4f73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ env: before_install: - pip install hererocks - - hererocks here -r^ --$LUA + - hererocks here -r^ --$LUA --patch - export PATH=$PWD/here/bin:$PATH install: diff --git a/Makefile b/Makefile index 7746d8b..4fb4b3a 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,6 @@ luadir = lib/std SOURCES = \ $(luadir)/_base.lua \ $(luadir)/debug.lua \ - $(luadir)/debug_init/init.lua \ $(luadir)/init.lua \ $(luadir)/io.lua \ $(luadir)/math.lua \ diff --git a/NEWS.md b/NEWS.md index d287283..3ebb93a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,18 +11,25 @@ are always welcome! - With this release, stdlib is much more focused, and non-core modules - `std.functional`, `std.maturity`, `std.operator`, `std.optparse`, - `std.strict` and `std.tuple` have been moved into their own packages - and release cycle. You can still install them separately from their - own projects or using Luarocks: + `optparse`, `std.functional`, 'std.prototype', `std.strict` and + `typecheck` have been moved into their own packages and release + cycle. Also, the shared debug initialization, API deprecation and + Lua host normalization code have been split out into new 'std._debug', + `maturity` and 'std.normalize' 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 strict + luarocks install std.functional + luarocks install std.prototype + luarocks install std.strict + luarocks install typecheck ``` - - All support for deprecated APIs has been removed, reducing the - install size even further. + - All support for previously deprecated APIs has been removed, reducing + the install size even further. - `std.string.render` now takes a table of named arguments as documented; the `pairs` function is now supplied with the key and value of the diff --git a/lib/std/_base.lua b/lib/std/_base.lua index 93bcfc3..afe3e98 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -43,44 +43,6 @@ local _ENV = require 'std.normalize' { ---[[ ================== ]]-- ---[[ Initialize _DEBUG. ]]-- ---[[ ================== ]]-- - - -local _DEBUG = require 'std.debug_init'._DEBUG - -local strict, typecheck -do - local ok - - -- Unless strict was disabled (`_DEBUG = false`), or that module is not - -- available, check for use of undeclared variables in this module... - if _DEBUG.strict then - ok, strict = pcall(require, 'strict') - if ok then - _ENV = strict {} - else - -- ...otherwise, the strict function is not available at all! - _DEBUG.strict = false - strict = false - end - end - - -- Unless strict was disabled (`_DEBUG = false`), or that module is not - -- available, check for use of undeclared variables in this module... - if _DEBUG.argcheck then - ok, typecheck = pcall(require, 'typecheck') - if not ok then - -- ...otherwise, the strict function is not available at all! - _DEBUG.argcheck = false - typecheck = false - end - end -end - - - --[[ ============================ ]]-- --[[ Enhanced Core Lua functions. ]]-- --[[ ============================ ]]-- @@ -308,10 +270,6 @@ local tostring_vtable = { -- public API here too, which means everything looks relatively normal -- when importing the functions into stdlib implementation modules. return { - _DEBUG = _DEBUG, - strict = strict, - typecheck = typecheck, - tostring = function(x) return render(x, tostring_vtable) end, diff --git a/lib/std/debug.lua b/lib/std/debug.lua index 5ea8919..c0a2e72 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -16,15 +16,9 @@ ]] -local _ = require 'std._base' - -local _DEBUG = _._DEBUG -local _tostring = _.tostring - -_ = nil - local _ENV = require 'std.normalize' { 'debug', + _debug = require 'std._debug', concat = 'table.concat', huge = 'math.huge', max = 'math.max', @@ -39,36 +33,17 @@ local _ENV = require 'std.normalize' { --[[ =============== ]]-- ---- Control std.debug function behaviour. --- To declare debugging state, set _DEBUG either to `false` to disable all --- runtime debugging; to any "truthy" value(equivalent to enabling everything --- except *call*, or as documented below. --- @class table --- @name _DEBUG --- @tfield[opt=true] boolean argcheck honor argcheck and argscheck calls --- @tfield[opt=false] boolean call do call trace debugging --- @field[opt=nil] deprecate if `false`, deprecated APIs are defined, --- and do not issue deprecation warnings when used; if `nil` issue a --- deprecation warning each time a deprecated api is used; any other --- value causes deprecated APIs not to be defined at all --- @tfield[opt=1] int level debugging level --- @tfield[opt=true] boolean strict enforce strict variable declaration --- before use **in stdlib internals**(if `require 'strict'` works) --- @usage --- _DEBUG = {argcheck=false, level=9, strict=false} - - 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) + 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] = _tostring(v) + t[k] = str(v) end stderr:write(concat(t, '\t') .. '\n') end @@ -107,8 +82,8 @@ local function trace(event) stderr:write(s .. '\n') end --- Set hooks according to _DEBUG -if type(_DEBUG) == 'table' and _DEBUG.call then +-- Set hooks according to _debug +if _debug.call then debug.sethook(trace, 'cr') end @@ -137,26 +112,27 @@ local M = { --- Print a debugging message to `io.stderr`. -- Display arguments passed through `std.tostring` and separated by tab - -- characters when `_DEBUG` is `true` and *n* is 1 or less; or `_DEBUG.level` - -- is a number greater than or equal to *n*. If `_DEBUG` is false or - -- nil, nothing is written. + -- 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_init'._DEBUG - -- _DEBUG.level = 3 - -- say(2, '_DEBUG table contents:', _DEBUG) + -- 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 `_DEBUG.call` is set. + -- 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 - -- _DEBUG = {call=true} + -- local _debug = require 'std._debug' + -- _debug.call = true -- local debug = require 'std.debug' trace = trace, } diff --git a/lib/std/debug_init/init.lua b/lib/std/debug_init/init.lua deleted file mode 100644 index 61bcb85..0000000 --- a/lib/std/debug_init/init.lua +++ /dev/null @@ -1,40 +0,0 @@ ---[[ - General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas -]] ---[[ - Return a table of debug parameters. - - Before loading this module, set the global `_DEBUG` according to what - debugging features you wish to use until the application exits. -]] - - -local function choose(t) - for k, v in pairs(t) do - if _DEBUG == false then - t[k] = v.fast - elseif _DEBUG == nil then - t[k] = v.default - elseif type(_DEBUG) ~= 'table' then - t[k] = v.safe - elseif _DEBUG[k] ~= nil then - t[k] = _DEBUG[k] - else - t[k] = v.default - end - end - return t -end - - -return { - _DEBUG = choose { - argcheck = { default=true, safe=true, fast=false}, - call = { default=false, safe=false, fast=false}, - deprecate = { default=nil, safe=true, fast=false}, - level = { default=1, safe=1, fast=math.huge}, - strict = { default=true, safe=true, fast=false}, - }, -} diff --git a/spec/debug_spec.yaml b/spec/debug_spec.yaml index 5405268..8ea0d83 100644 --- a/spec/debug_spec.yaml +++ b/spec/debug_spec.yaml @@ -11,39 +11,6 @@ before: | M = require(this_module) - table_unpack = table.unpack or unpack - - function map(mapfn, ...) - local argt, r = pack(...), {} - - local nextfn, state, k = pairs(table_unpack(argt, 1, argt.n)) - local mapargs = pack(nextfn(state, k)) - - local arity = 1 - while mapargs[1] ~= nil do - local d, v = mapfn(table_unpack(mapargs, 1, mapargs.n)) - if v ~= nil then - arity, r = 2, {} break - end - r[#r + 1] = d - mapargs = {nextfn(state, mapargs[1])} - end - - if arity > 1 then - -- No need to start over here, because either: - -- (i) arity was never 1, and the original value of mapargs is correct - -- (ii) arity used to be 1, but we only consumed nil values, so the - -- current mapargs with arity > 1 is the correct next value to use - while mapargs[1] ~= nil do - local k, v = mapfn(table_unpack(mapargs, 1, mapargs.n)) - r[k] = v - mapargs = pack(nextfn(state, mapargs[1])) - end - end - return r - end - - specify std.debug: - context when required: - context by name: @@ -67,176 +34,164 @@ specify std.debug: - describe debug: - - before: | - function mkwrap(k, v) - local fmt = '%s' - if type(v) == 'string' then - fmt = '%q' - end - return k, string.format(fmt, require 'std'.tostring(v)) - end - - function mkdebug(debugp, ...) - return string.format([[ - _DEBUG = %s - require 'std.debug'(%s) - ]], - require 'std'.tostring(debugp), - table.concat(map(mkwrap, {...}), ', ')) - end - - - it does nothing when _DEBUG is disabled: - expect(luaproc(mkdebug(false, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - - it writes to stderr when _DEBUG is not set: - expect(luaproc(mkdebug(nil, 'debugging'))). - to_contain_error 'debugging' - - it writes to stderr when _DEBUG is enabled: - expect(luaproc(mkdebug(true, 'debugging'))). - to_contain_error 'debugging' - - it writes to stderr when _DEBUG.level is not set: - expect(luaproc(mkdebug({}, 'debugging'))). - to_contain_error 'debugging' - - it writes to stderr when _DEBUG.level is specified: - expect(luaproc(mkdebug({level=0}, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mkdebug({level=1}, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mkdebug({level=2}, 'debugging'))). - to_contain_error 'debugging' + - 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: - - before: | - function mkwrap(k, v) - local fmt = '%s' - if type(v) == 'string' then - fmt = '%q' - end - return k, string.format(fmt, require 'std'.tostring(v)) - end - - function mksay(debugp, ...) - return string.format([[ - _DEBUG = %s - require 'std.debug'.say(%s) - ]], - require 'std'.tostring(debugp), - table.concat(map(mkwrap, {...}), ', ')) - end - - f = M.say - - it uses stdlib tostring: expect(luaproc [[require 'std.debug'.say {'debugging'}]]). to_contain_error(require 'std'.tostring {'debugging'}) - - context when _DEBUG is disabled: + - context when std._debug is disabled: + - before: + preamble = [[ + require 'std._debug'(false) + ]] - it does nothing when message level is not set: - expect(luaproc(mksay(false, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' + 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: - expect(luaproc(mksay(false, -999, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - expect(luaproc(mksay(false, 0, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - expect(luaproc(mksay(false, 1, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - expect(luaproc(mksay(false, 2, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - expect(luaproc(mksay(false, 999, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - - context when _DEBUG is not 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(mksay(nil, 'debugging'))). - to_contain_error 'debugging' + expect(luaproc [[ + require 'std.debug'.say 'debugging' + ]]).to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect(luaproc(mksay(nil, -999, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mksay(nil, 0, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mksay(nil, 1, 'debugging'))). - to_contain_error 'debugging' + 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: - expect(luaproc(mksay(nil, 2, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - expect(luaproc(mksay(nil, 999, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - - context when _DEBUG is enabled: + 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(mksay(true, 'debugging'))). - to_contain_error 'debugging' + expect(luaproc(preamble .. [[ + require 'std.debug'.say 'debugging' + ]])).to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect(luaproc(mksay(true, -999, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mksay(true, 0, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mksay(true, 1, 'debugging'))). - to_contain_error 'debugging' + 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: - expect(luaproc(mksay(true, 2, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - expect(luaproc(mksay(true, 999, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - - context when _DEBUG.level is not set: + 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(mksay({}, 'debugging'))). - to_contain_error 'debugging' + expect(luaproc [[ + require 'std.debug'.say 'debugging' + ]]).to_contain_error 'debugging' - it writes to stderr when message level is 1 or lower: - expect(luaproc(mksay({}, -999, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mksay({}, 0, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mksay({}, 1, 'debugging'))). - to_contain_error 'debugging' + 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: - expect(luaproc(mksay({}, 2, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - expect(luaproc(mksay({}, 999, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' - - context when _DEBUG.level is specified: + 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: - expect(luaproc(mksay({level=0}, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mksay({level=1}, 'debugging'))). - to_contain_error 'debugging' - expect(luaproc(mksay({level=2}, 'debugging'))). - to_contain_error 'debugging' + 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(mksay({level=2}, 3, 'nothing to see here'))). - not_to_contain_error 'nothing to see here' + 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(mksay({level=2}, 2, 'debugging'))). - to_contain_error 'debugging' + 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(mksay({level=2}, 1, 'debugging'))). - to_contain_error 'debugging' + 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 is disabled: + - it does nothing when debug hint is disabled: expect(luaproc [[ - _DEBUG = false + require 'std._debug'(false) require 'std.debug' os.exit(0) ]]).to_succeed_with '' - - it does nothing when _DEBUG is not set: + - 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 is enabled: + - it does nothing when debug hint is enabled: expect(luaproc [[ - _DEBUG = true + require 'std._debug'(true) require 'std.debug' os.exit(0) ]]).to_succeed_with '' - - it enables automatically when _DEBUG.call is set: | + - it enables automatically when std._debug.call is set: | expect(luaproc [[ - _DEBUG = {call=true} - local debug = require 'std.debug' + 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: | diff --git a/spec/spec_helper.lua b/spec/spec_helper.lua index d11cd50..1ebc62f 100644 --- a/spec/spec_helper.lua +++ b/spec/spec_helper.lua @@ -30,10 +30,6 @@ package.path = std.package.normalize( local LUA = os.getenv 'LUA' or 'lua' --- Tweak _DEBUG without tripping over Specl nested environments. -setdebug = require 'std.debug'._setdebug - - -- Simplified version for specifications, does not support functable -- valued __len metamethod, so don't write examples that need that! function len(x) @@ -200,7 +196,7 @@ end -- 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 `_DEBUG.deprecate` +-- @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 @@ -213,14 +209,14 @@ function deprecation(deprecate, module, fname, args, objectinit) local script if objectinit == nil then script = string.format([[ - _DEBUG = {deprecate=%s} + 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([[ - _DEBUG = {deprecate=%s} + require 'std._debug'.deprecate = %s local M = require '%s' local P = M.prototype local obj = P(%s) diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index 1123866..d91ee99 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -22,6 +22,7 @@ source = { dependencies = { 'lua >= 5.1, < 5.4', 'ldoc', + 'std._debug', 'std.normalize >= 2.0', } @@ -31,7 +32,6 @@ build = { std = 'lib/std/init.lua', ['std._base'] = 'lib/std/_base.lua', ['std.debug'] = 'lib/std/debug.lua', - ['std.debug_init']= 'lib/std/debug_init/init.lua', ['std.io'] = 'lib/std/io.lua', ['std.math'] = 'lib/std/math.lua', ['std.package'] = 'lib/std/package.lua', From a79d79bc86625fd866b34e1840c8ec1cb1b9a6b4 Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Tue, 17 Oct 2017 13:47:22 -0700 Subject: [PATCH 701/703] refactor: reuse normalize.string.render instead of duplicating here. * lib/std/_base.lua (string.render, tostring): Remove. * lib/std/init.lua (tostring): Remove. * lib/std/io.lua (warnfmt): Use normalize.str instead of std.tostring. * lib/std/string.lua (__concat, prettytostring): Reimprement on top of normalize.string.render. (render): Remove. * spec/debug_spec.yaml, spec/std_spec.yaml, spec/string_spec.yaml: Update accordingly. * NEWS.md (New features, Incompatible changes): Update. Signed-off-by: Gary V. Vaughan --- NEWS.md | 37 +++------ lib/std/_base.lua | 104 ------------------------ lib/std/init.lua | 10 --- lib/std/io.lua | 7 +- lib/std/string.lua | 185 ++++++++++++++---------------------------- spec/debug_spec.yaml | 4 +- spec/std_spec.yaml | 32 +------- spec/string_spec.yaml | 7 +- 8 files changed, 78 insertions(+), 308 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3ebb93a..fdbf720 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,9 +13,9 @@ - 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, API deprecation and - Lua host normalization code have been split out into new 'std._debug', - `maturity` and 'std.normalize' respectively, and are pulled in + 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: @@ -31,28 +31,8 @@ - All support for previously deprecated APIs has been removed, reducing the install size even further. - - `std.string.render` now takes a table of named arguments as documented; - the `pairs` function is now supplied with the key and value of the - preceding key/value pair. There is also support for two new named - functions: `sort`, which can change the rendering order of keys; and - `term`, a predicate function to determine whether the argument can be - rendered directly by the `elem` function. - - There are sensible fallbacks for functions not passed in the new - function table. Among other advantages of this improved API, this - means render can be called without ceremony to perform basic object - rendering: - - ```lua - std.string.render (thing) - ``` - - - `std.tostring` uses the more powerful features of `std.string.render` - to return a more compact representation of table arguments, that uses - significantly less horizontal space for sequences. - - - `std.string.prettytostring` continues to use `std.string.render` for - more legible deeply nested table output, identically to previous + - `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. @@ -108,8 +88,11 @@ If `__len` is not present, or gives the same result as `maxn` then `npairs` continues to behave as in the previous release. - - The output format of `std.tostring` skips initial sequence keys in - the new compact format, including stringification of Objects and + - `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 diff --git a/lib/std/_base.lua b/lib/std/_base.lua index afe3e98..23a9396 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -131,15 +131,6 @@ local function invert(t) end --- Sort numbers first then asciibetically -local function keysort(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 function leaves(it, tr) local function visit(n) if type(n) == 'table' then @@ -154,78 +145,6 @@ local function leaves(it, tr) end -local fallbacks = { - __index = { - open = function(x) return '{' end, - close = function(x) return '}' end, - elem = tostring, - pair = function(x, kp, vp, k, v, kstr, vstr) - return kstr .. '=' .. vstr - end, - sep = function(x, kp, vp, kn, vn) - return kp ~= nil and kn ~= nil and ',' or '' - end, - sort = function(keys) - return keys - end, - term = function(x) - return type(x) ~= 'table' or getmetamethod(x, '__tostring') - end, - }, -} - --- Write pretty-printing based on: --- --- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators --- --- Based on "The Design of a Pretty-printing Library in Advanced --- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925 --- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps --- Heavily modified by Simon Peyton Jones, Dec 96 - -local function render(x, fns, roots) - fns = setmetatable(fns or {}, fallbacks) - roots = roots or {} - - local function stop_roots(x) - return roots[x] or render(x, fns, shallow_copy(roots)) - end - - if fns.term(x) then - return fns.elem(x) - - else - local buf, keys = {fns.open(x)}, {} -- pre-buffer table open - roots[x] = fns.elem(x) -- recursion protection - - for k in pairs(x) do -- collect keys - keys[#keys + 1] = k - end - keys = fns.sort(keys) - - local pair, sep = fns.pair, fns.sep - local kp, vp -- previous key and value - for _, k in ipairs(keys) do - local v = x[k] - buf[#buf + 1] = sep(x, kp, vp, k, v) -- | buffer << separator - buf[#buf + 1] = pair(x, kp, vp, k, v, stop_roots(k), stop_roots(v)) - -- | buffer << key/value pair - kp, vp = k, v - end - buf[#buf + 1] = sep(x, kp, vp) -- buffer << trailing separator - buf[#buf + 1] = fns.close(x) -- buffer << table close - - return concat(buf) -- stringify buffer - end -end - - -local function sortkeys(t) - sort(t, keysort) - return t -end - - local function split(s, sep) local r, patt = {} if sep == '' then @@ -244,19 +163,6 @@ local function split(s, sep) end -local tostring_vtable = { - pair = function(x, kp, vp, k, v, kstr, vstr) - if k == 1 or type(k) == 'number' and k -1 == kp then - return vstr - end - return kstr .. '=' .. vstr - end, - - -- need to sort numeric keys to be able to skip printing them. - sort = sortkeys, -} - - --[[ ============= ]]-- --[[ Internal API. ]]-- --[[ ============= ]]-- @@ -270,15 +176,6 @@ local tostring_vtable = { -- public API here too, which means everything looks relatively normal -- when importing the functions into stdlib implementation modules. return { - tostring = function(x) - return render(x, tostring_vtable) - end, - - base = { - sortkeys = sortkeys, - toqstring = toqstring, - }, - io = { catfile = catfile, }, @@ -294,7 +191,6 @@ return { string = { escape_pattern = escape_pattern, - render = render, split = split, }, diff --git a/lib/std/init.lua b/lib/std/init.lua index ab6e08e..d183efb 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -21,7 +21,6 @@ local _ = require 'std._base' -local _tostring = _.tostring local argscheck = _.typecheck and _.typecheck.argscheck local compare = _.list.compare local maxn = _.table.maxn @@ -219,15 +218,6 @@ M = { -- clone = std.getmetamethod(std.object.prototype, '__call') getmetamethod = X('getmetamethod(?any, string)', getmetamethod), - --- Enhance core `tostring` to render table contents as a string. - -- @function tostring - -- @param x object to convert to string - -- @treturn string compact string rendering of *x* - -- @usage - -- -- {1=baz,foo=bar} - -- print(std.tostring {foo='bar','baz'}) - tostring = X('tostring(?any)', _tostring), - --- Module Functions -- @section modulefuncs diff --git a/lib/std/io.lua b/lib/std/io.lua index 90cb905..2b68e0c 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -18,7 +18,6 @@ local _ = require 'std._base' -local _tostring = _.tostring local argscheck = _.typecheck and _.typecheck.argscheck local catfile = _.io.catfile local leaves = _.tree.leaves @@ -128,17 +127,17 @@ local function warnfmt(msg, ...) if prog.name then prefix = prog.name .. ':' if prog.line then - prefix = prefix .. _tostring(prog.line) .. ':' + prefix = prefix .. str(prog.line) .. ':' end elseif prog.file then prefix = prog.file .. ':' if prog.line then - prefix = prefix .. _tostring(prog.line) .. ':' + prefix = prefix .. str(prog.line) .. ':' end elseif opts.program then prefix = opts.program .. ':' if opts.line then - prefix = prefix .. _tostring(opts.line) .. ':' + prefix = prefix .. str(opts.line) .. ':' end end if #prefix > 0 then diff --git a/lib/std/string.lua b/lib/std/string.lua index bd0cd5e..3f30e4c 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -18,11 +18,8 @@ local _ = require 'std._base' -local _tostring = _.tostring local argscheck = _.typecheck and _.std.typecheck.argscheck local escape_pattern = _.string.escape_pattern -local render = _.string.render -local sortkeys = _.base.sortkeys local split = _.string.split _ = nil @@ -39,6 +36,8 @@ local _ENV = require 'std.normalize' { insert = 'table.insert', match = 'string.match', merge = 'table.merge', + render = 'string.render', + sort = 'table.sort', sub = 'string.sub', upper = 'string.upper', } @@ -53,8 +52,36 @@ local _ENV = require 'std.normalize' { 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) - return _tostring(s) .. _tostring(o) + -- Don't use normalize.str here, because we don't want ASCII escape rendering. + return render(s, concatvfns) .. render(o, concatvfns) end @@ -182,10 +209,36 @@ local function numbertosi(n) 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, { + 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 @@ -197,13 +250,6 @@ local function prettytostring(x, indent, spacing) return spacing .. '}' end, - elem = function(x) - if type(x) ~= 'string' then - return tostring(x) - end - return format('%q', x) - end, - pair = function(x, _, _, k, v, kstr, vstr) local type_k = type(k) local s = spacing @@ -237,9 +283,7 @@ local function prettytostring(x, indent, spacing) end return s end, - - sort = sortkeys, - }) + }, render_fallbacks)) end @@ -400,26 +444,6 @@ M = { -- print(prettytostring(std, ' ')) prettytostring = X('prettytostring(?any, ?string, ?string)', prettytostring), - --- Turn tables into strings with recursion detection. - -- N.B. Functions calling render should not recurse, or recursion - -- detection will not work. - -- @function render - -- @param x object to convert to string - -- @tparam[opt] rendercbs fns default rendering function overrides - -- @return string representation of *x* - -- @usage - -- function tostablestring(x) - -- return render(x, { - -- sort = function(keys) - -- table.sort(keys, lambda '=tostring(_1) < tostring(_2)') - -- return keys - -- end, - -- }) - -- end - render = X('render(?any, ?table)', function(x, rendercbs, roots) - return render(x, rendercbs, roots) - end), - --- Remove trailing matter from a string. -- @function rtrim -- @string s any string @@ -480,94 +504,3 @@ M = { return merge(string, M) - - ---- Types --- @section Types - ---- Table of default render callback functions. --- @table rendercbs --- @tfield[opt] opentablecb open open table rendering function --- @tfield[opt] closetablecb close close table rendering function --- @tfield[opt] elementcb elem element rendering function --- @tfield[opt] paircb pair pair rendering function --- @tfield[opt] separatorcb sep separator rendering function --- @tfield[opt] sortcb sort key sorting function --- @tfield[opt] termcb term terminal predicate --- @see render --- @usage --- function tostringstable(x) --- return render(x, {sort=some_sequence_reordering_fn}) --- end - - ---- Signature of @{render} open table callback. --- @function opentablecb --- @tparam table t table about to be rendered --- @treturn string open table rendering --- @see render --- @usage --- function open(t) return '{' end - - ---- Signature of @{render} close table callback. --- @function closetablecb --- @tparam table t table just rendered --- @treturn string close table rendering --- @see render --- @usage --- function close(t) return '}' end - - ---- Signature of @{render} element callback. --- @function elementcb --- @param x element to render --- @treturn string element rendering --- @see render --- @usage --- function element(e) return require 'std'.tostring(e) end - - ---- Signature of @{render} pair callback. --- Trying to re-render *key* or *value* here will break recursion --- detection, use *strkey* and *strvalue* pre-rendered values instead. --- @function paircb --- @tparam table t table containing pair being rendered --- @param key key part of key being rendered --- @param value value part of key being rendered --- @string keystr prerendered *key* --- @string valuestr prerendered *value* --- @treturn string pair rendering --- @see render --- @usage --- function pair(_, _, _, key, value) return key .. '=' .. value end - - ---- Signature of @{render} separator callback. --- @function separatorcb --- @tparam table t table currently being rendered --- @param pk *t* key preceding separator, or `nil` for first key --- @param pv *t* value preceding separator, or `nil` for first value --- @param fk *t* key following separator, or `nil` for last key --- @param fv *t* value following separator, or `nil` for last value --- @treturn string separator rendering --- @usage --- function separator(_, _, _, fk) return fk and ',' or '' end - - ---- Signature of @{render} key sorting callback. --- @function sortcb --- @tparam sequence keys all keys from rendering table --- @treturn sequence *keys* in desired display order --- @usage --- function unsorted(keys) return keys end - - ---- Signature of @{render} terminal predicate callback. --- @function termcb --- @param x an element to be rendered --- @treturn boolean whether *x* can be rendered by @{elementcb} --- @usage --- function term(x) --- return type(x) ~= 'table' or getmetamethod(x, '__tostring') --- end diff --git a/spec/debug_spec.yaml b/spec/debug_spec.yaml index 8ea0d83..5e56eb6 100644 --- a/spec/debug_spec.yaml +++ b/spec/debug_spec.yaml @@ -69,9 +69,9 @@ specify std.debug: - describe say: - - it uses stdlib tostring: + - it uses normalize.str: expect(luaproc [[require 'std.debug'.say {'debugging'}]]). - to_contain_error(require 'std'.tostring {'debugging'}) + to_contain_error(require 'std.normalize'.str {'debugging'}) - context when std._debug is disabled: - before: preamble = [[ diff --git a/spec/std_spec.yaml b/spec/std_spec.yaml index a3863f6..a728f68 100644 --- a/spec/std_spec.yaml +++ b/spec/std_spec.yaml @@ -7,7 +7,7 @@ before: | exported_apis = {'assert', 'elems', 'eval', 'getmetamethod', 'ielems', 'ipairs', 'npairs', 'pairs', - 'require', 'ripairs', 'rnpairs', 'tostring'} + 'require', 'ripairs', 'rnpairs'} -- Tables with iterator metamethods used by various examples. __pairs = setmetatable({content='a string'}, { @@ -442,33 +442,3 @@ specify std: t[k] = v end expect(t).to_equal {} - - -- describe tostring: - - before: - f = M.tostring - - - context with bad arguments: - badargs.diagnose(f, 'std.tostring(?any)') - - - it renders primitives exactly like system tostring: - expect(f(nil)).to_be(tostring(nil)) - expect(f(false)).to_be(tostring(false)) - expect(f(42)).to_be(tostring(42)) - expect(f(f)).to_be(tostring(f)) - expect(f 'a string').to_be 'a string' - - it renders empty tables as a pair of braces: - expect(f {}).to_be('{}') - - it renders table array part compactly: - expect(f {'one', 'two', 'five'}). - to_be '{one,two,five}' - - it renders a table dictionary part compactly: - expect(f {one=true, two=2, three={3}}). - to_be '{one=true,three={3},two=2}' - - it renders table keys in table.sort order: - expect(f {one=3, two=5, three=4, four=2, five=1}). - to_be '{five=1,four=2,one=3,three=4,two=5}' - - it renders keys with invalid symbol names compactly: - expect(f {_=0, word=0, ['?']=1, ['a-key']=1, ['[]']=1}). - to_be '{?=1,[]=1,_=0,a-key=1,word=0}' - diff --git a/spec/string_spec.yaml b/spec/string_spec.yaml index 5ee1e44..cd8b57f 100644 --- a/spec/string_spec.yaml +++ b/spec/string_spec.yaml @@ -10,7 +10,7 @@ before: 'caps', 'chomp', 'escape_pattern', 'escape_shell', 'finds', 'format', 'ltrim', 'numbertosi', 'ordinal_suffix', 'pad', - 'prettytostring', 'render', 'rtrim', 'split', + 'prettytostring', 'rtrim', 'split', 'tfind', 'trim', 'wrap'} M = require(this_module) @@ -47,12 +47,11 @@ specify std.string: expect(subject .. ' another string').to_be(target) - it stringifies non-string arguments: argument = {'a table'} - expect(subject .. argument). - to_be(string.format('%s%s', subject, require 'std'.tostring(argument))) + 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'.tostring(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' From 15ac20938a1f0a1ec3e228f38ad34aa36ceb528e Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 1 Jan 2018 20:37:29 -0800 Subject: [PATCH 702/703] maint: update copyright statements. * LICENSE.md, README.md: Add 2018 to copyright years. * build-aux/config.ld.in, lib/std/_base.lua, lib/std/debug.lua, lib/std/init.lua, lib/std/io.lua, lib/std/math.lua, lib/std/package.lua, lib/std/string.lua, lib/std/table.lua, spec/debug_spec.yaml, spec/io_spec.yaml, spec/math_spec.yaml, spec/package_spec.yaml, spec/spec_helper.lua, spec/std_spec.yaml, spec/string_spec.yaml, spec/table_spec.yaml: Likewise. Remove individual authors from copyright statements. * Makefile: Add copyright statement. Signed-off-by: Gary V. Vaughan --- LICENSE.md | 2 +- Makefile | 4 ++++ README.md | 8 ++++---- build-aux/config.ld.in | 3 +-- lib/std/_base.lua | 3 +-- lib/std/debug.lua | 3 +-- lib/std/init.lua | 3 +-- lib/std/io.lua | 3 +-- lib/std/math.lua | 3 +-- lib/std/package.lua | 3 +-- lib/std/string.lua | 3 +-- lib/std/table.lua | 3 +-- spec/debug_spec.yaml | 2 +- spec/io_spec.yaml | 2 +- spec/math_spec.yaml | 2 +- spec/package_spec.yaml | 2 +- spec/spec_helper.lua | 2 +- spec/std_spec.yaml | 2 +- spec/string_spec.yaml | 2 +- spec/table_spec.yaml | 2 +- 20 files changed, 26 insertions(+), 31 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index cbfd44a..c2b4620 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (C) 2002-2017 stdlib authors +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 diff --git a/Makefile b/Makefile index 4fb4b3a..7b8dd8c 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + LDOC = ldoc LUA = lua MKDIR = mkdir -p @@ -27,6 +30,7 @@ $(luadir)/version.lua: .FORCE if cmp -s '$@' '$@T'; then \ rm -f '$@T'; \ else \ + echo 'echo return "General Lua libraries / $(VERSION)" > $@'; \ mv '$@T' '$@'; \ fi diff --git a/README.md b/README.md index 1e65bcf..49b0328 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Standard Lua libraries ====================== -by the [stdlib project][github] +Copyright (C) 2000-2018 [stdlib authors][github] [![License](http://img.shields.io/:license-mit-blue.svg)](http://mit-license.org) [![travis-ci status](https://secure.travis-ci.org/lua-stdlib/lua-stdlib.png?branch=master)](http://travis-ci.org/lua-stdlib/lua-stdlib/builds) @@ -10,9 +10,9 @@ by the [stdlib project][github] 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 2000-2017 (see the -[AUTHORS][] file for details), and released under the [MIT license][mit] -(the same license as Lua itself). There is no warranty. +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 diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in index a39d174..20ee48b 100644 --- a/build-aux/config.ld.in +++ b/build-aux/config.ld.in @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] title = 'stdlib @PACKAGE_VERSION@ Reference' diff --git a/lib/std/_base.lua b/lib/std/_base.lua index 23a9396..0c623d4 100644 --- a/lib/std/_base.lua +++ b/lib/std/_base.lua @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] --[[-- Prevent dependency loops with key function implementations. diff --git a/lib/std/debug.lua b/lib/std/debug.lua index c0a2e72..cab29ff 100644 --- a/lib/std/debug.lua +++ b/lib/std/debug.lua @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] --[[-- Additions to the core debug module. diff --git a/lib/std/init.lua b/lib/std/init.lua index d183efb..732d41f 100644 --- a/lib/std/init.lua +++ b/lib/std/init.lua @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] --[[-- Enhanced Lua core functions, and others. diff --git a/lib/std/io.lua b/lib/std/io.lua index 2b68e0c..1a2b79f 100644 --- a/lib/std/io.lua +++ b/lib/std/io.lua @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] --[[-- Additions to the core io module. diff --git a/lib/std/math.lua b/lib/std/math.lua index 85cb08b..d955862 100644 --- a/lib/std/math.lua +++ b/lib/std/math.lua @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] --[[-- Additions to the core math module. diff --git a/lib/std/package.lua b/lib/std/package.lua index 4e93284..e3e8243 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] --[[-- Additions to the core package module. diff --git a/lib/std/string.lua b/lib/std/string.lua index 3f30e4c..6ad9014 100644 --- a/lib/std/string.lua +++ b/lib/std/string.lua @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] --[[-- Additions to the core string module. diff --git a/lib/std/table.lua b/lib/std/table.lua index 4b6ef7a..7bda608 100644 --- a/lib/std/table.lua +++ b/lib/std/table.lua @@ -1,7 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan - Copyright (C) 2002-2014 Reuben Thomas + Copyright (C) 2002-2018 stdlib authors ]] --[[-- Extensions to the core table module. diff --git a/spec/debug_spec.yaml b/spec/debug_spec.yaml index 5e56eb6..5102723 100644 --- a/spec/debug_spec.yaml +++ b/spec/debug_spec.yaml @@ -1,5 +1,5 @@ # General Lua Libraries for Lua 5.1, 5.2 & 5.3 -# Copyright (C) 2011-2017 Gary V. Vaughan +# Copyright (C) 2011-2018 stdlib authors before: | base_module = 'debug' diff --git a/spec/io_spec.yaml b/spec/io_spec.yaml index 445589d..67b850f 100644 --- a/spec/io_spec.yaml +++ b/spec/io_spec.yaml @@ -1,5 +1,5 @@ # General Lua Libraries for Lua 5.1, 5.2 & 5.3 -# Copyright (C) 2011-2017 Gary V. Vaughan +# Copyright (C) 2011-2018 stdlib authors before: | base_module = 'io' diff --git a/spec/math_spec.yaml b/spec/math_spec.yaml index cc5fd20..ed08753 100644 --- a/spec/math_spec.yaml +++ b/spec/math_spec.yaml @@ -1,5 +1,5 @@ # General Lua Libraries for Lua 5.1, 5.2 & 5.3 -# Copyright (C) 2011-2017 Gary V. Vaughan +# Copyright (C) 2011-2018 stdlib authors before: base_module = 'math' diff --git a/spec/package_spec.yaml b/spec/package_spec.yaml index 198edaa..23ce961 100644 --- a/spec/package_spec.yaml +++ b/spec/package_spec.yaml @@ -1,5 +1,5 @@ # General Lua Libraries for Lua 5.1, 5.2 & 5.3 -# Copyright (C) 2011-2017 Gary V. Vaughan +# Copyright (C) 2011-2018 stdlib authors before: | base_module = 'package' diff --git a/spec/spec_helper.lua b/spec/spec_helper.lua index 1ebc62f..642712f 100644 --- a/spec/spec_helper.lua +++ b/spec/spec_helper.lua @@ -1,6 +1,6 @@ --[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 - Copyright (C) 2011-2017 Gary V. Vaughan + Copyright (C) 2011-2018 stdlib authors ]] local typecheck diff --git a/spec/std_spec.yaml b/spec/std_spec.yaml index a728f68..b747abf 100644 --- a/spec/std_spec.yaml +++ b/spec/std_spec.yaml @@ -1,5 +1,5 @@ # General Lua Libraries for Lua 5.1, 5.2 & 5.3 -# Copyright (C) 2011-2017 Gary V. Vaughan +# Copyright (C) 2011-2018 stdlib authors before: | this_module = 'std' diff --git a/spec/string_spec.yaml b/spec/string_spec.yaml index cd8b57f..2fa47f2 100644 --- a/spec/string_spec.yaml +++ b/spec/string_spec.yaml @@ -1,5 +1,5 @@ # General Lua Libraries for Lua 5.1, 5.2 & 5.3 -# Copyright (C) 2011-2017 Gary V. Vaughan +# Copyright (C) 2011-2018 stdlib authors before: base_module = 'string' diff --git a/spec/table_spec.yaml b/spec/table_spec.yaml index 2496f41..d9fed0c 100644 --- a/spec/table_spec.yaml +++ b/spec/table_spec.yaml @@ -1,5 +1,5 @@ # General Lua Libraries for Lua 5.1, 5.2 & 5.3 -# Copyright (C) 2011-2017 Gary V. Vaughan +# Copyright (C) 2011-2018 stdlib authors before: | base_module = 'table' From a632078f216ac6b9994449b7f1435a419172b44f Mon Sep 17 00:00:00 2001 From: "Gary V. Vaughan" Date: Mon, 1 Jan 2018 20:51:44 -0800 Subject: [PATCH 703/703] maint: self-configure rockspec for release versions. * stdlib-git-1.rockspec (source, dependencies): Set contents based on _MODREV value. Signed-off-by: Gary V. Vaughan --- stdlib-git-1.rockspec | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec index d91ee99..504aee8 100644 --- a/stdlib-git-1.rockspec +++ b/stdlib-git-1.rockspec @@ -13,19 +13,29 @@ description = { license = 'MIT/X11', } -source = { - url = 'git://github.com/lua-stdlib/lua-stdlib.git', - --url = 'http://github.com/lua-stdlib/lua-stdlib/archive/v' .. _MODREV .. '.zip', - --dir = 'lua-stdlib-' .. _MODREV, -} +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', - 'ldoc', 'std._debug', 'std.normalize >= 2.0', } +if _MODREV == 'git' then + dependencies[#dependencies + 1] = 'ldoc' +end + build = { type = 'builtin', modules = {